بہت سے ڈویلپرز فرض کرتے ہیں کہ ایک بار جب وہ ORM استعمال کرتے ہیں تو، SQL انجیکشن اب کوئی مسئلہ نہیں رہتا ہے۔ لیکن یہ دور نہیں ہوتا ہے۔ ہم ابھی منتقل ہو گئے۔
ORMs جیسے Sequelize، Prisma، TypeORM، اور Knex خود بخود بلٹ سوالات کو پیرامیٹرائز کرتے ہیں۔ وہ حصہ کام کرتا ہے۔
مسئلہ وہاں ظاہر ہوتا ہے جہاں ایپ محفوظ راستے سے ہٹ جاتی ہے۔ یعنی، ایک خام سوال جس کا ORM کا استفسار بلڈر واضح طور پر اظہار نہیں کرسکتا، ایک متحرک ترتیب والا کالم، یا واحد مددگار فنکشن جس تک آپ بغیر سوچے سمجھے پہنچ جاتے ہیں۔
یہ پہلی جگہ ہے جہاں حملہ آور نظر آتے ہیں، اور ڈویلپرز کے لیے اس سے محروم ہونا آسان ہے کیونکہ وہ محسوس کرتے ہیں کہ باقی کوڈ بیس محفوظ ہے۔
یہاں ہم ان میں سے چار نمونوں پر توجہ مرکوز کرتے ہیں۔ ہر ایک کو ایک حقیقی دنیا کی مثال ملتی ہے جو بگ کا سبب بنتی ہے، اس بات کا تجزیہ کہ بگ کیوں فائدہ مند ہے، اور ایک دوبارہ لکھا ہوا ورژن جو اسے بند کرتا ہے۔ اس میں یہ نوٹ بھی شامل ہیں کہ بالکل کیا منتقل کیا گیا اور کیوں۔
آپ کو پیروی کرنے کے لیے کسی سیکیورٹی پس منظر کی ضرورت نہیں ہے۔ کوڈ کو پڑھنے کے لیے آپ کو صرف Node.js، SQL، اور کم از کم ایک ORM سے کافی حد تک واقف ہونا ضروری ہے۔
شرطیں
مثالوں سے اندازہ ہوتا ہے کہ آپ درج ذیل سے واقف ہیں:
-
Node.js اور ایکسپریس کی بنیادی باتیں
-
بنیادی SQL سوالات لکھنا
-
ORM کا بنیادی استعمال (تمام کوڈ کی مثالیں Sequelize کا استعمال کرتی ہیں، لیکن پیٹرن Prisma، TypeORM، Knex وغیرہ پر لاگو ہوتا ہے)
-
HTTP درخواست/رسپانس سائیکل کیسے کام کرتا ہے۔
کوڈ کی مثالوں پر نوٹس: اس گائیڈ کے دوران sequelize فرض کیا جاتا ہے کہ یہ پہلے سے شروع کردہ سیکویلائز مثال ہے۔ QueryTypes اور Op سے درآمد 'sequelize' اس کے ساتھ، اور User/Product ایپ میں کہیں اور بیان کردہ ایک سیکوئلائز ماڈل۔ چونکہ یہاں حوالہ دیا گیا کچھ APIs بڑے ورژن کے درمیان بدل گئے ہیں، اس لیے مثالیں Sequelize v6+ نحو (موجودہ اہم ورژن) کا استعمال کرتی ہیں۔ آپ اپنے استعمال کردہ ورژن اور ORM کے مطابق نحو کو ایڈجسٹ کر سکتے ہیں۔
انڈیکس
1. خام سوال فرار ہیچ
تمام بڑے ORMs ان سوالات کے لیے "Escape hatches” فراہم کرتے ہیں جن کا استفسار کرنے والا واضح طور پر اظہار نہیں کر سکتا۔ sequelize.query()پریزما کا $queryRawUnsafeیا TypeORM query(). یہ اچھی وجوہات کی بنا پر موجود ہے، جیسے پیچیدہ جوائنز، ونڈو فنکشنز، اور وینڈر کے لیے مخصوص SQL۔
مسئلہ اس وقت شروع ہوتا ہے جب ڈیولپرز فرار ہونے والے ہیچز کو باقی ORM کی طرح ٹریٹ کرتے ہیں اور صارف کا ان پٹ براہ راست بلٹ سٹرنگ میں داخل کرتے ہیں۔
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
عام فلٹر شدہ رپورٹ کے اختتامی نکات میں شامل ہیں:
// Express.js - Vulnerable
app.get('/reports', async (req, res) => {
const { region } = req.query;
const results = await sequelize.query(
`SELECT * FROM sales WHERE region = '${region}'`,
{ type: QueryTypes.SELECT }
);
res.json(results);
});
آپ کو یہ سٹرنگ استفسار بلڈر میں نظر نہیں آئے گی۔ یہ بالکل اسی طرح ڈیٹا بیس ڈرائیور کو بھیج دیا جاتا ہے۔
یہ کیوں اہمیت رکھتا ہے۔
اس طرح کی درخواست: ?region=' OR '1'='1 اپنے استفسار کو اس سے تبدیل کریں: SELECT * FROM sales WHERE region = '' OR '1'='1'یہ ہمیشہ سچ ہے۔ ٹیبل میں تمام قطاریں دوبارہ نمودار ہوں گی، قطع نظر خطے کے۔
اس سے بھی بدتر، کیونکہ یہ کوڈ ORM پر مبنی پروجیکٹ کے اندر رہتا ہے، اس لیے اسے اکثر ویسی جانچ نہیں ملتی جتنی مقامی طور پر ہوتی ہے۔ mysql.query() کال کوڈبیس سے کی جاتی ہے، ORM سے نہیں۔ جائزہ لینے والے فرض کرتے ہیں کہ ORM نے پہلے ہی اس کا خیال رکھا ہے۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
سٹرنگ کو براہ راست لکھنے کے بجائے، آپ قیمت کو متبادل/بائنڈنگ میکانزم کے ذریعے منتقل کرتے ہیں جو کہ خام سوال API پہلے سے فراہم کرتا ہے۔
// Express.js - Secure
app.get('/reports', async (req, res) => {
const { region } = req.query;
const results = await sequelize.query(
'SELECT * FROM sales WHERE region = :region',
{
replacements: { region },
type: QueryTypes.SELECT
}
);
res.json(results);
});
خام سوالات سے مکمل طور پر گریز کرنا درست نہیں ہے، کیونکہ بعض اوقات خام سوالات واقعی ضروری ہوتے ہیں۔ جب بائنڈنگ میکانزم وہاں موجود ہو تو آپ براہ راست ایس کیو ایل سٹرنگز نہیں لکھتے ہیں۔
صارف کے ان پٹ کو خام استفسار کے اسٹرنگ میں مت جوڑیں۔ خام استفسار کے طریقوں کے ذریعہ پہلے سے فراہم کردہ فال بیک/بائنڈنگ APIs کا استعمال کریں۔
2. غیر پیرامیٹرائز ایبل شناخت کنندگان
پیرامیٹرائزڈ سوالات کے ساتھ تحفظ قدر. وہ حفاظت نہیں کرتے شناخت کنندہ ٹیبل کا نام، کالم کا نام، یا ORDER BY سمت یہ خود SQL سٹرنگ کا حصہ ہونا چاہیے۔
یہی وجہ ہے کہ ڈائنامک چھانٹنے کے فنکشنز سب سے عام جگہوں میں سے ایک ہیں جہاں انجیکشن اچھی طرح سے لکھے ہوئے ORM کوڈ میں داخل ہوتے ہیں۔
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
ایک عام ترتیب دینے والی فہرست کا اختتامی نقطہ یہ ہوگا:
// Express.js - Vulnerable
app.get('/users', async (req, res) => {
const { sortBy = 'created_at' } = req.query;
const users = await sequelize.query(
`SELECT * FROM users ORDER BY ${sortBy}`,
{ type: QueryTypes.SELECT }
);
res.json(users);
});
sortBy براہ راست استفسار کے تار سے ORDER BY آیات کے درمیان کچھ نہیں ہے۔
یہ کیوں اہمیت رکھتا ہے۔
sortBy یہ ایک بے ضرر UI سہولت کی طرح لگتا ہے جب تک کہ کوئی اسے مجھے نہ بھیجے۔ created_at; DROP TABLE users; --. آیا صحیح پے لوڈ کو عمل میں لایا گیا ہے اس کا انحصار ڈیٹا بیس ڈرائیور پر ہے۔ پوسٹگریس کا pg ڈرائیور اس طرح کے اسٹیک اسٹیٹمنٹ کو بطور ڈیفالٹ انجام دیتا ہے، جبکہ MySQL کرتا ہے۔ mysql2 اسے مسدود کریں جب تک کہ آپ ایسا نہ کریں۔ multipleStatements: true یہ واضح طور پر مقرر کیا گیا ہے.
بہر حال، بنیادی مسئلہ ایک ہی ہے۔ اس کا مطلب یہ ہے کہ آپ کے پاس ایسی جگہ پر صوابدیدی SQL ہے جس کا مقصد صرف کالم کے نام رکھنا ہے۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
شناخت کنندگان کو پیرامیٹرز کے طور پر پابند نہیں کیا جا سکتا، لہذا واحد محفوظ آپشن وائٹ لسٹ ہے۔
// Express.js - Secure
const ALLOWED_SORT_COLUMNS = ['created_at', 'name', 'email'];
app.get('/users', async (req, res) => {
const { sortBy = 'created_at' } = req.query;
const column = ALLOWED_SORT_COLUMNS.includes(sortBy) ? sortBy : 'created_at';
const users = await sequelize.query(
`SELECT * FROM users ORDER BY ${column}`,
{ type: QueryTypes.SELECT }
);
res.json(users);
});
صارف کے ان پٹ کو شناخت کنندہ کے مقام تک نہ پہنچائیں، یہاں تک کہ پہلے اسے "صاف صاف” کرنے کے بعد۔ شناخت کنندگان پر حذف کرنا اقدار کو حذف کرنے کے مقابلے میں غلط ہونا بہت آسان ہے۔
شناخت کنندگان کو پیرامیٹرائز نہیں کیا جا سکتا۔ اگر کسی کالم یا ٹیبل کے نام کا تعین صارف کے ان پٹ کی بنیاد پر کیا جاتا ہے، تو اسے وائٹ لسٹ میں شامل کریں اور اسے حذف نہ کریں۔
3. ذخیرہ شدہ ڈیٹا کے ذریعے سیکنڈری انجیکشن
یہ ٹیم کو چوکس کر دیتی ہے کیونکہ ان کے پاس ان پٹ ہے۔ تھا پیرامیٹرائزڈ…پہلے ڈیٹا بیس میں ریکارڈ کیا گیا۔
انجکشن بعد میں ہوتا ہے جب پہلے سے ذخیرہ شدہ قدروں کو کسی اور غیر پیرامیٹرائزڈ استفسار میں دوبارہ استعمال کیا جاتا ہے۔
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
ذیل میں رجسٹریشن کا بہاؤ ہے، اور کوڈ بیس میں کہیں اور ایڈمن سرچ فنکشن موجود ہے۔
// Express.js - Vulnerable
// Step 1: user registration, correctly parameterized
app.post('/register', async (req, res) => {
await User.create({ username: req.body.username });
res.json({ success: true });
});
// Step 2: somewhere else in the codebase, an admin search feature
app.get('/admin/search', async (req, res) => {
const user = await User.findByPk(req.params.id);
const results = await sequelize.query(
`SELECT * FROM audit_log WHERE actor="${user.username}"`,
{ type: QueryTypes.SELECT }
);
res.json(results);
});
مرحلہ 1 خود مکمل طور پر محفوظ ہے۔ مرحلہ 2 وہ جگہ ہے جہاں مسئلہ پیدا ہوتا ہے۔
یہ کیوں اہمیت رکھتا ہے۔
صارف کا نام درج ذیل ہے۔ admin' OR '1'='1 بغیر کسی پریشانی کے مرحلہ 1 پاس کریں۔ Sequelize کی create() آپ اسے پیرامیٹرائز کرتے ہیں، لہذا یہ بالکل اسی طرح محفوظ ہوجاتا ہے جیسے آپ نے اسے درج کیا ہے۔ یہ ایک ڈیٹا بیس میں ہے جو مکمل طور پر عام لگتا ہے۔
پے لوڈ صرف ان اقدار کو مرحلہ 2 سے واپس حاصل کرکے اور انہیں خام استفسار کے سٹرنگ میں ڈال کر ہی عمل میں لایا جاتا ہے۔ "یہ پہلے سے ہی ہمارے ڈیٹا بیس میں ہے” وہی چیز نہیں ہے جیسے "یہ محفوظ ہے۔” اگر یہ صارف کے ان پٹ سے کہیں بھی اوپر کی طرف شروع ہوتا ہے، حملہ آور اب بھی کنٹرول میں ہے۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
پیٹرن #1 کے برابر ہونے کے لیے ترمیم کی گئی۔ اقدار کو جوڑنے کے بجائے ان کو جوڑیں۔
// Express.js - Secure
// Step 1 (registration) is unchanged — it was already parameterized
app.get('/admin/search', async (req, res) => {
const user = await User.findByPk(req.params.id);
const results = await sequelize.query(
'SELECT * FROM audit_log WHERE actor = :actor',
{
replacements: { actor: user.username },
type: QueryTypes.SELECT
}
);
res.json(results);
});
پیٹرن # 1 کے مقابلے میں یہاں بگ کو تلاش کرنا مشکل ہے۔ اس کی وجہ یہ ہے کہ آلودہ ڈیٹا اسے خطرناک ہونے سے پہلے ڈیٹا بیس کے چکر لگاتا ہے۔
آپ کے اپنے ڈیٹا بیس میں موجود ڈیٹا خود بخود محفوظ نہیں ہے۔ اسے جہاں کہیں بھی استعمال کیا جاتا ہے اسے پیرامیٹرائز کیا جانا چاہیے، چاہے اس کی ابتدا کہیں اوپر کی طرف سے صارف کے ان پٹ سے ہو۔
4. خام SQL کو خفیہ طور پر باقاعدہ ORM کالوں میں انجیکشن لگایا جاتا ہے۔
پیٹرن نمبر 1 ہے۔ واضح Raw query escape hatches حقیقی SQL تک پہنچنے کا ایک جان بوجھ کر طریقہ ہے جب اس کی ضرورت ہو۔ یہ ایک تھوڑا زیادہ کپٹی ہے. انجکشن کالوں کے اندر چھپا ہوا ہے جو پوری طرح سے ORM کے زیر انتظام دکھائی دیتی ہے۔ یہ خام SQL نہیں ہے، ہے نا؟ یہ بہت سے مددگار افعال میں سے ایک ہے۔
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
ذیل میں ایک عام فلٹر شدہ مصنوعات کی فہرست ہے:
// Express.js - Vulnerable
app.get('/products', async (req, res) => {
const { minPrice } = req.query;
const products = await Product.findAll({
where: sequelize.literal(`price > ${minPrice}`)
});
res.json(products);
});
Product.findAll() یہ مکمل طور پر محفوظ، پیرامیٹرائزڈ ORM کال کی طرح لگتا ہے۔ sequelize.literal() اس میں ظاہر ہوتا ہے۔
یہ کیوں اہمیت رکھتا ہے۔
literal() Sequelize سے کہو، "اسے مت چھوئے۔ اسے ایس کیو ایل میں بالکل اسی طرح داخل کریں۔” کوئی بھی چیز جو انٹرپولیٹ کی جاتی ہے وہ بالکل پیٹرن # 1 کی طرح انجیکشن کے قابل ہوتی ہے، بھیس میں صرف ایک سادہ نظر آنے والا ORM طریقہ۔
جانے دو ?minPrice=0 OR 1=1 اور WHERE شق غیر مشروط طور پر درست ہے۔ تمام قطاریں دوبارہ ظاہر ہوں گی، قطع نظر اس سے کہ آپ کے پاس قیمت کا فلٹر ہے یا نہیں۔ اس وقت اسٹیک اسٹیٹمنٹ کی ضرورت نہیں ہے۔ چونکہ یہ ایک واحد بولین اظہار ہے، یہ ڈیٹا بیس ڈرائیور سے قطع نظر ایک جیسا برتاؤ کرتا ہے۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
پہنچنا بند کرو literal(). Sequelize کا اپنا آپریٹر API پہلے ہی اس کیس کا احاطہ کرتا ہے۔
// Express.js - Secure
app.get('/products', async (req, res) => {
const minPrice = Number(req.query.minPrice);
if (!Number.isFinite(minPrice)) {
return res.status(400).json({ error: 'minPrice must be a number' });
}
const products = await Product.findAll({
where: { price: { [Op.gt]: minPrice } }
});
res.json(products);
});
Op.gt، Op.between، Op.inSequelize کا باقی آپریٹر API پہلے سے ہی زیادہ تر معاملات کا احاطہ کرتا ہے جو لوگ پہنچتے ہیں۔ literal() کے لئے اور ڈیفالٹ کی طرف سے صحیح طریقے سے پیرامیٹرائز کیا جاتا ہے. تصدیق minPrice استفسار پر پہنچنے سے پہلے یہ درحقیقت ایک عدد ہے، لہذا اگر اسے مستقبل کے ریفیکٹر میں دوبارہ متعارف کرایا جاتا ہے، تو یہ خلا کم ہو جائے گا۔ literal() کہیں اور۔
پکڑنا literal(، fn(اور ORM کے دیگر خام فرار ہیچز ہر جگہ ظاہر ہوتے ہیں جہاں وہ کوڈ بیس میں ظاہر ہوتے ہیں، نہ کہ صرف اندر کے سوالات جو ظاہر ہے کہ "کچی” ہیں۔
خلاصہ
تیزی سے خلاصہ کرنے کے لیے، آئیے دیکھتے ہیں کہ چار پیٹرن کیسے ملتے ہیں:
| پیٹرن | بنیادی وجہ | بنیادی درستگی |
|---|---|---|
| خام سوال فرار ہیچ | صارف کا ان پٹ خام استفسار کے اسٹرنگ کے طور پر مربوط ہے۔ | تبدیلی/پیرامیٹر API کے ذریعے بائنڈنگ اقدار |
| شناخت کنندہ انجکشن | کالم/ٹیبل/سانٹ ان پٹس کو پیرامیٹرائز نہیں کیا جا سکتا۔ | وائٹ لسٹ میں شناخت کنندگان کی واضح طور پر اجازت ہے۔ |
| دوسرا انجکشن | ذخیرہ شدہ ڈیٹا قابل اعتماد ہے کیونکہ اسے ایک بار پیرامیٹرائز کیا گیا ہے۔ | تمام سوالات کو پیرامیٹرائز کریں، یہاں تک کہ وہ بھی جو آپ کا اپنا ذخیرہ شدہ ڈیٹا استعمال کرتے ہیں۔ |
| لفظی انجکشن | خام SQL کو ORM مینجمنٹ کالز میں اسمگل کیا گیا۔ | بچنا literal()/raw()اس کے بجائے، اپنے ORM کا آپریٹر API استعمال کریں۔ |
ان چار کیڑوں میں سے کسی کو تلاش کرنے کے لیے ایک ہنر مند حملہ آور کی ضرورت ہوگی۔ ہر ایک صرف ایک قدر ہے جو ORM کے پیرامیٹرائزیشن پاتھ میں شامل نہیں ہے۔
اپنے ورک فلو کا حصہ بنانے کے قابل چیزیں: پیرامیٹرائز ویلیوز، وائٹ لسٹ شناخت کنندگان، اپنے ڈیٹا بیس سے نکالے گئے ڈیٹا کو نئی درخواستوں سے کم محفوظ سمجھیں، literal()/raw() کال بالکل اتنا ہی خطرناک ہے جتنا کہ ایک وقف شدہ خام استفسار کا طریقہ (چاہے آس پاس کا کوڈ کتنا ہی محفوظ کیوں نہ ہو)۔
ان خلاء کو دور کرنا صرف آدھی تصویر ہے۔ یہ جاننا کہ کوئی شخص درحقیقت ان کی تلاش کیسے کر رہا ہے (اور حقیقی تشخیص کے دوران ان سے کیسے جڑنا ہے) وہ نصف ہے جو زیادہ تر ڈویلپرز کبھی نہیں دیکھتے۔
اگر آپ جارحانہ نقطہ نظر کے بارے میں جاننا چاہتے ہیں، تو ORM کے ذریعے ایس کیو ایل انجیکشن میں یہ گہرا غوطہ آپ کو اس بات کے بارے میں بتاتا ہے کہ یہ چار نمونے حقیقی وقت میں دخول کی جانچ میں کس طرح استعمال کیے جاتے ہیں۔