سیکیورٹی میں نئے ڈویلپرز کو حیران کر دینے والی چیزیں یہ ہیں: ویب کی زیادہ تر کمزوریاں نفیس حملوں کا نتیجہ نہیں ہیں۔ یہ کوڈ کے نمونوں سے آتے ہیں جو بالکل معقول معلوم ہوتے ہیں، جیسے یو آر ایل کی قدر پر بھروسہ کرنا، درخواست کے باڈی کو ڈیٹا بیس اپ ڈیٹ پر لاگو کرنا، یا دو سوالات پر عمل کرنا جہاں ایک بار کافی ہوتا۔
یہ گائیڈ ان میں سے چھ نمونوں کا احاطہ کرتا ہے۔ ہر ایک کے لیے، آپ ایک ترمیم شدہ ورژن دیکھ سکتے ہیں جس میں اصل کوڈ کی ایک مثال شامل ہے جو خطرے کو پیدا کرتا ہے، اس کی وضاحت جو اسے خطرناک بناتی ہے، اور اس کے بارے میں نوٹ کرتا ہے کہ کیا تبدیل کیا گیا اور کیوں۔
یہاں کسی حفاظتی پس منظر کی ضرورت نہیں ہے۔ Node.js اور SQL سے کچھ واقفیت حاصل کرنا مددگار ثابت ہوگا۔
شرطیں
مثالوں سے اندازہ ہوتا ہے کہ آپ درج ذیل سے واقف ہیں:
-
Node.js اور Express.js کی بنیادی باتیں
-
SQL استفسار
-
HTTP درخواستیں اور جوابات کیسے کام کرتے ہیں۔
-
توثیق کے بنیادی تصورات (سیشن، ٹوکن)
کوڈ کی مثالوں پر نوٹس: اس پورے ٹیوٹوریل کے دوران db.query() ایک فرضی ڈیٹا بیس مددگار جو ایک قطار والی چیز کو لوٹاتا ہے۔ SELECT استفسار (یا null اگر نہیں ملا) اور نتیجے میں آبجیکٹ INSERT/UPDATE استفسار کہ connection.query() ریس کے حالات کا سیکشن براہ راست mysql2 وعدہ API کا استعمال کرتا ہے۔ query() رپورٹ [rows, fields]. آپ جو ڈیٹا بیس ڈرائیور استعمال کرتے ہیں اس کے مطابق نحو کو ایڈجسٹ کریں۔
انڈیکس
1. ٹوٹا ہوا رسائی کنٹرول اور IDOR
ٹوٹا ہوا رسائی کنٹرول 2021 کے لیے OWASP ٹاپ 10 میں رہا ہے، اور اس کی وجہ دیکھنا مشکل نہیں ہے۔ سب سے عام شکل ہے غیر محفوظ IDOR (براہ راست آبجیکٹ حوالہ): ایپلیکیشن یو آر ایل میں ڈیٹابیس آئی ڈی کو ظاہر کرتی ہے، صارف نمبر تبدیل کرتا ہے، اور اچانک وہ کسی اور کا ڈیٹا دیکھ رہا ہوتا ہے۔
اصلاحات ماضی میں واضح نظر آتی ہیں۔ تاہم، یہ اب بھی پروڈکشن کوڈ میں ظاہر ہوتا ہے کیونکہ یہ تصدیق اور اجازت کو الجھا دیتا ہے۔ صارف کے لاگ ان ہونے کی تصدیق کرنا اس بات کی تصدیق کرنے سے مختلف ہے کہ صارف کو کسی مخصوص وسائل تک رسائی حاصل ہے۔
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
عام صارف پروفائل اختتامی نکات میں شامل ہیں:
// Express.js - Vulnerable
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
const userId = req.params.id;
const user = await db.query(
'SELECT id, name, email, address FROM users WHERE id = ?',
[userId]
);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
کہ authenticate مڈل ویئر تصدیق کرتا ہے کہ درخواست میں ایک درست ٹوکن ہے۔ تاہم، یہ اس بات کی تصدیق نہیں کرتا کہ آیا مستند صارف درخواست کردہ وسائل تک رسائی حاصل کر سکتا ہے۔
کوئی بھی مستند صارف درخواست کر سکتا ہے۔ /api/users/1/profile، /api/users/2/profileدوسرے صارفین کا ڈیٹا بازیافت کریں وغیرہ۔
یہ کیوں اہمیت رکھتا ہے۔
تصدیق کی تصدیق کریں۔ ڈبلیو ایچ او تم ہو منظوری کی تصدیق کریں۔ جو آپ کو کرنے کی اجازت ہے۔. اوپر والا کوڈ پہلا کرتا ہے اور دوسرے کو مکمل طور پر چھوڑ دیتا ہے۔
ترتیب وار عددی IDs کو متجسس صارف کے لیے کسی خاص ٹولز کی ضرورت نہیں ہوتی ہے۔ وہ صرف تبدیل کر سکتے ہیں 1 کو 2 URL سے۔ تاہم، اگر ملکیت کی تصدیق غائب ہے تو UUIDs یا slugs کے ساتھ بھی یہی مسئلہ موجود ہے۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
سرور سائیڈ پر چیک کرتا ہے کہ آیا تصدیق شدہ صارف درخواست کردہ وسائل کا مالک ہے یا اسے اس تک رسائی کی واضح اجازت ہے۔
// Express.js - Secure
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
// Reject anything that isn't a string of digits — parseInt("12abc") would return 12
if (!/^\d+$/.test(req.params.id)) {
return res.status(400).json({ error: 'Invalid user ID' });
}
const requestedId = Number(req.params.id);
if (requestedId < 1) {
return res.status(400).json({ error: 'Invalid user ID' });
}
// The authenticated user's ID is set by the authenticate middleware
const authenticatedId = req.user.id;
// Enforce ownership: users can only access their own profile
if (requestedId !== authenticatedId) {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await db.query(
'SELECT id, name, email, address FROM users WHERE id = ?',
[requestedId]
);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
مینجمنٹ اینڈ پوائنٹس کے لیے جو قانونی طور پر تمام صارفین تک رسائی کی ضرورت ہوتی ہے، واضح طور پر کردار پر مبنی تصدیق کو نافذ کریں۔
// Admin endpoint with explicit role check
app.get('/api/admin/users/:id', authenticate, requireRole('admin'), async (req, res) => {
if (!/^\d+$/.test(req.params.id)) {
return res.status(400).json({ error: 'Invalid user ID' });
}
const userId = Number(req.params.id);
const user = await db.query(
'SELECT id, name, email, role FROM users WHERE id = ?',
[userId]
);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
یہاں کیا تبدیل ہوا ہے اور کیوں:
-
/^\d+$/.test(req.params.id)کسی بھی چیز کو مسترد کرتا ہے جو خالص عددی تار نہیں ہے۔parseInt("12abc", 10)خاموشی سے واپس آ جائیں گے12اور اضافی ٹیسٹ پاس کریں۔ باقاعدہ اظہار اس کو روکتا ہے۔ -
Number(req.params.id)پہلے سے تصدیق شدہ تاروں کو محفوظ طریقے سے نمبروں میں تبدیل کرتا ہے۔ -
موازنہ
requestedId !== authenticatedIdملکیت کو نافذ کرتا ہے۔ -
مینجمنٹ فنکشن ایک علیحدہ اختتامی نقطہ ہے جس کے اپنے تصدیقی چیک ہیں۔
URL پیرامیٹرز سے تصدیق کا اندازہ نہ لگائیں۔ مستند سیشن سے ماخوذ۔
2. بلک مختص
بلک ایلوکیشن ان کمزوریوں میں سے ایک ہے جو لکھتے وقت بمشکل قابل توجہ ہوتی ہے۔ آپ موثر ہو رہے ہیں، ٹھیک ہے؟ جب آپ پوری آبجیکٹ کو پاس کرسکتے ہیں تو دستی طور پر فیلڈز کے ذریعے کیوں اعادہ کریں؟
مسئلہ یہ ہے کہ ڈیٹا بیس ٹیبل جانتا ہے کہ صارف کو کن فیلڈز کو کبھی ہاتھ نہیں لگانا چاہیے۔
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
صارف پروفائل اپ ڈیٹ کا اختتامی نقطہ یہ ہے:
// Express.js - Vulnerable
app.put('/api/users/me', authenticate, async (req, res) => {
const userId = req.user.id;
// req.body contains everything the client sends
const updates = req.body;
await db.query(
'UPDATE users SET ? WHERE id = ?',
[updates, userId]
);
res.json({ success: true });
});
کہ users ٹیبل میں درج ذیل کالم ہیں:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
bio TEXT,
role ENUM('user', 'moderator', 'admin') DEFAULT 'user',
credits INT DEFAULT 0,
is_banned BOOLEAN DEFAULT false
);
ڈویلپرز کا مقصد صارفین کو اپ ڈیٹ کرنا تھا۔ name، emailاور bio. لیکن role، creditsاور is_banned یہ بھی میز پر ہے۔ استفسار کلائنٹ کے ذریعہ بھیجے گئے تمام فیلڈز کو اپ ڈیٹ کرتا ہے۔
یہ کیوں اہمیت رکھتا ہے۔
درخواست کا باڈی براہ راست SQL استفسار میں جاتا ہے۔ کہ users ایک میز بھی ہے۔ role، creditsاور is_banned - اور استفسار یہ نہیں جانتا یا اس کی پرواہ نہیں کرتا ہے کہ ڈویلپر کن فیلڈز کو بے نقاب کرنا چاہتا ہے۔
صارف اسے بھیج رہا ہے:
{
"name": "Alice",
"role": "admin",
"credits": 100000,
"is_banned": false
}
آپ کو ابھی مینیجر کے طور پر ترقی ملی ہے، آپ کی پابندی ہٹا دی گئی ہے، اور آپ کو ایک لاکھ کریڈٹس ملے ہیں۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
واضح وائٹ لسٹ کا استعمال کرتے ہوئے براہ راست فیلڈ بہ فیلڈ کی بنیاد پر اپ ڈیٹ اشیاء بنائیں۔
// Express.js - Secure
app.put('/api/users/me', authenticate, async (req, res) => {
const userId = req.user.id;
// Only these fields may be updated by the user
const ALLOWED_FIELDS = ['name', 'email', 'bio'];
const updates = {};
for (const field of ALLOWED_FIELDS) {
if (req.body[field] !== undefined) {
updates[field] = req.body[field];
}
}
if (Object.keys(updates).length === 0) {
return res.status(400).json({ error: 'No valid fields provided' });
}
// Validate individual fields
// isValidEmail is a simple helper: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
if (updates.email && !isValidEmail(updates.email)) {
return res.status(400).json({ error: 'Invalid email format' });
}
if (updates.name && (typeof updates.name !== 'string' || updates.name.length > 100)) {
return res.status(400).json({ error: 'Name must be a string of 100 characters or fewer' });
}
await db.query(
'UPDATE users SET ? WHERE id = ?',
[updates, userId]
);
res.json({ success: true });
});
ایسے انتظامی کاموں کے لیے جو حساس فیلڈز کو قانونی طور پر اپ ڈیٹ کرتے ہیں، اس کی اپنی تصدیق کے ساتھ ایک علیحدہ اختتامی نقطہ استعمال کریں۔
// Admin-only endpoint for changing user roles
app.put('/api/admin/users/:id/role', authenticate, requireRole('admin'), async (req, res) => {
if (!/^\d+$/.test(req.params.id)) {
return res.status(400).json({ error: 'Invalid user ID' });
}
const userId = Number(req.params.id);
const { role } = req.body;
const VALID_ROLES = ['user', 'moderator', 'admin'];
if (!VALID_ROLES.includes(role)) {
return res.status(400).json({ error: 'Invalid role' });
}
await db.query(
'UPDATE users SET role = ? WHERE id = ?',
[role, userId]
);
res.json({ success: true });
});
نہ پھیلاؤ req.body ڈیٹا بیس کے استفسار کے ساتھ۔ فیلڈ کے لحاظ سے آبجیکٹ فیلڈز کو اپ ڈیٹ کرتا ہے۔
3. پروٹوٹائپ آلودگی
پروٹوٹائپ بدعنوانی اس وقت ہوتی ہے جب ناقابل اعتماد ڈیٹا کو بار بار کسی آبجیکٹ میں ضم کیا جاتا ہے، جس سے حملہ آور کو آبجیکٹ میں خصوصیات داخل کرنے کی اجازت ملتی ہے۔ Object.prototype (بیس آبجیکٹ جس سے تمام باقاعدہ جاوا اسکرپٹ آبجیکٹ وراثت میں ملتے ہیں)
OWASP ٹاپ 10 اس کا احاطہ کرتا ہے سافٹ ویئر اور ڈیٹا انٹیگریٹی ایررز (A08:2021)۔
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
کنفیگریشن انضمام کی افادیت جو صارف کی فراہم کردہ ترتیبات کو سنبھالتی ہے:
// Vulnerable recursive merge function
function mergeConfig(target, source) {
for (const key of Object.keys(source)) {
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = {};
mergeConfig(target[key], source[key]); // recursive call
} else {
target[key] = source[key]; // triggers __proto__ setter via bracket notation
}
}
}
app.post('/api/settings', authenticate, (req, res) => {
const userSettings = {};
mergeConfig(userSettings, req.body); // merge untrusted input
applySettings(userSettings);
res.json({ success: true });
});
یہ کیوں اہمیت رکھتا ہے۔
حملہ آور مندرجہ ذیل درخواست بھیجتا ہے:
{
"__proto__": {
"isAdmin": true
}
}
تکراری mergeConfig فنکشن ہے __proto__ کلید اور چلائیں target['__proto__']['isAdmin'] = true. کیونکہ __proto__ یہ JavaScript میں ایک پروٹو ٹائپ ایکسر ہے۔ یہ اگلی بار براہ راست لکھا جائے گا۔ Object.prototype. انضمام کے بعد:
const anyObject = {};
console.log(anyObject.isAdmin); // true — inherited from Object.prototype
چل رہی ایپلیکیشن میں تمام عام اشیاء اب وراثت میں ملی ہیں۔ isAdmin: true. اگر منظوری کی تصدیق ہے:
if (user.isAdmin) { /* grant admin access */ }
اب یہ چیک تمام صارفین کے لیے گزر جائے گا چاہے ان کے اصل کردار سے قطع نظر۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
صارف کی حالت کو سرور سائیڈ پر اسٹور کریں اور ہر فیلڈ کو انفرادی طور پر درست کریں۔ ناقابل اعتماد ان پٹس کو بار بار ضم کرنے سے گریز کریں۔
// Express.js - Secure
app.post('/api/settings', authenticate, async (req, res) => {
// Load current settings from the database — never from the client
const current = await db.query(
'SELECT theme, language, notifications FROM user_settings WHERE user_id = ?',
[req.user.id]
);
// Validate each field against an explicit allowlist
const safeSettings = {
theme: validateEnum(req.body.theme, ['light', 'dark'], current.theme),
language: validateEnum(req.body.language, ['en', 'es', 'fr', 'de'], current.language),
notifications: typeof req.body.notifications === 'boolean'
? req.body.notifications
: current.notifications
};
await db.query(
'UPDATE user_settings SET ? WHERE user_id = ?',
[safeSettings, req.user.id]
);
res.json({ success: true });
});
function validateEnum(value, allowed, defaultValue) {
return allowed.includes(value) ? value : defaultValue;
}
اگر آپ کو انضمام کی افادیت کی ضرورت ہے، تو اسے استعمال کریں: Object.create(null) پہلے سے طے شدہ - چونکہ کوئی پروٹو ٹائپ نہیں ہے۔ __proto__ وہ کلیدیں جو داغدار اور واضح طور پر وائٹ لسٹ نہیں کی جا سکتی ہیں:
// Safe merge: base object with no prototype
function safeMerge(allowedKeys, source) {
const result = Object.create(null); // no prototype = no pollution possible
for (const key of allowedKeys) {
if (key in source && typeof source[key] !== 'object') {
result[key] = source[key];
}
}
return result;
}
حکمرانی:
-
غیر بھروسہ مند ان پٹ کو باقاعدہ اشیاء میں بار بار ضم کرنے سے گریز کریں۔
-
سرور سائڈ پر ایپلیکیشن اسٹیٹ اسٹور کریں۔ اسے لے جانے کے لیے اپنے گاہکوں پر بھروسہ نہ کریں۔
-
استعمال کریں
Object.create(null)ڈیٹا کنٹینرز کے لیے جن میں غیر بھروسہ مند کلیدیں ہیں۔ -
ہر فیلڈ کو استعمال کرنے سے پہلے قسم اور اجازت شدہ اقدار کے لحاظ سے درست کریں۔
4. ریس کے حالات
ریس کی حالت مشکل ہے کیونکہ کوڈ بالکل درست ہے۔ جو چیز اسے توڑتی ہے وہ ٹائمنگ ہے۔ دو درخواستیں تقریباً ایک ہی وقت پر پہنچتی ہیں، دونوں ایک جیسی شرائط کی جانچ کرتے ہیں، دونوں درست نتائج کی جانچ کرتے ہیں، اور دونوں آگے بڑھتے ہیں۔ نتیجے کے طور پر، کچھ جو صرف ایک بار ہونا چاہئے دو بار ہوتا ہے.
یہ ہے چیک کرنے کا وقت بمقابلہ استعمال کا وقت (TOCTOU) مسئلہ: میں نے جس حیثیت کی جانچ کی ہے وہ اب کام کرنے کی حیثیت نہیں ہے۔
اپنے کوڈ میں اس پیٹرن کی شناخت کیسے کریں۔
سنگل استعمال کوپن ریڈمپشن اینڈ پوائنٹ:
// Express.js - Vulnerable
app.post('/api/redeem-coupon', authenticate, async (req, res) => {
const { couponCode } = req.body;
const userId = req.user.id;
// Step 1: Check if the coupon is still valid
const coupon = await db.query(
'SELECT id, discount_amount, used FROM coupons WHERE code = ? AND used = false',
[couponCode]
);
if (!coupon) {
return res.status(400).json({ error: 'Invalid or already used coupon' });
}
// Time gap: another request can pass Step 1 here before Step 3 runs
// Step 2: Apply the discount
await applyDiscountToOrder(userId, coupon.discount_amount);
// Step 3: Mark coupon as used
await db.query(
'UPDATE coupons SET used = true, used_by = ? WHERE code = ?',
[userId, couponCode]
);
res.json({ success: true });
});
یہ کیوں اہمیت رکھتا ہے۔
تقریباً ایک ہی وقت میں ایک ہی کوپن کوڈ کا استعمال کرتے ہوئے دو درخواستیں پہنچتی ہیں۔ دونوں لیول 1 تک پہنچ جاتے ہیں اس سے پہلے کہ کوئی بھی لیول 3 تک پہنچ جائے۔ میں دونوں کو پڑھتا ہوں۔ used = false. رعایت دونوں پر لاگو ہوتی ہے۔ ایک کوپن دو بار استعمال کیا جا سکتا ہے۔
پڑھنے لکھنے کے لیے ایک ہی کھڑکی ہر جگہ موجود ہے۔ کٹوتیوں سے پہلے بیلنس چیک کریں، بکنگ سے پہلے اسٹاک چیک کریں، ووٹ بڑھانے سے پہلے چیک کریں۔ یا تو اسی طرح استحصال کیا جا سکتا ہے۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
تصدیق کے بعد، ہم عملدرآمد کے پیٹرن کو ایٹم آپریشن میں تبدیل کرتے ہیں۔ ایٹم ڈیٹا بیس کی تازہ کاری اس بات کو یقینی بناتی ہے کہ حالت کی جانچ پڑتال اور تحریریں ایک واحد، ناقابل تقسیم اکائی کے طور پر ہوتی ہیں۔
// Express.js - Secure
app.post('/api/redeem-coupon', authenticate, async (req, res) => {
const { couponCode } = req.body;
const userId = req.user.id;
const connection = await db.getConnection();
try {
await connection.beginTransaction();
// Atomic: only one request can update a row where used = false.
// The database row lock ensures only one request succeeds.
const [result] = await connection.query(
`UPDATE coupons
SET used = true, used_by = ?, used_at = NOW()
WHERE code = ? AND used = false`,
[userId, couponCode]
);
if (result.affectedRows === 0) {
await connection.rollback();
return res.status(400).json({ error: 'Invalid or already used coupon' });
}
const [couponRows] = await connection.query(
'SELECT discount_amount FROM coupons WHERE code = ?',
[couponCode]
);
const coupon = couponRows[0];
await applyDiscountToOrder(userId, coupon.discount_amount, connection);
await connection.commit();
res.json({ success: true, discount: coupon.discount_amount });
} catch (error) {
await connection.rollback();
console.error('Coupon redemption failed:', error);
res.status(500).json({ error: 'Could not process the coupon' });
} finally {
connection.release();
}
});
کلیدی تبدیلی ہے۔ UPDATE ... WHERE code = ? AND used = false. ڈیٹا بیس اپ ڈیٹس کے دوران قطار کے تالے حاصل کرتا ہے۔ صرف ایک ہم آہنگی کی درخواست کامیاب ہو سکتی ہے۔ دوسری درخواست یہ ہے: affectedRows = 0 غلطی لوٹاتا ہے — درست طریقے سے۔
جب بھی آپ کسی قدر کو لکھنے سے پہلے فیصلہ کرنے کے لیے پڑھتے ہیں، تو یہ ممکنہ دوڑ کی شرط ہے۔ چیک کریں اور ایک جوہری تحریر کریں۔
5. کاروباری منطق کی خرابی۔
کاروباری منطق کے نقائص کی نشاندہی کرنا سب سے مشکل زمرہ ہے۔ چونکہ کوڈ بالکل لکھا ہوا کام کرتا ہے، اس لیے خودکار اسکینرز اسے نہیں ڈھونڈ پائیں گے اور کوڈ کے جائزے اس سے محروم ہوسکتے ہیں۔ مسئلہ یہ نہیں ہے کہ کوڈ کیسے پرفارم کرتا ہے، بلکہ یہ ہے کہ اسے کیا کرنے کے لیے ڈیزائن کیا گیا ہے۔
سب سے عام شکل یہ ہے کہ مؤکل کو معقول عددی اقدار بھیجنے پر بھروسہ کیا جائے۔
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
ای کامرس ادائیگی کے اختتامی پوائنٹس:
// Express.js - Vulnerable
app.post('/api/checkout', authenticate, async (req, res) => {
const { items } = req.body;
let total = 0;
const processedItems = [];
for (const item of items) {
const product = await db.query(
'SELECT id, price FROM products WHERE id = ?',
[item.productId]
);
if (!product) {
return res.status(400).json({ error: `Product not found: ${item.productId}` });
}
// Trust the quantity value from the client
const itemTotal = product.price * item.quantity;
total += itemTotal;
processedItems.push({ productId: product.id, quantity: item.quantity, price: product.price });
}
if (total > 100) {
total = total * 0.9; // 10% discount
}
await createOrder(req.user.id, processedItems, total);
res.json({ success: true, total });
});
یہ کیوں اہمیت رکھتا ہے۔
مسئلہ 1 - منفی مقداریں:کوڈ یہ جانچے بغیر کہ آیا مقدار مثبت ہے، سرور کی قیمت کو کلائنٹ کی مقدار سے ضرب دیتا ہے۔ صارف بھیجتا ہے۔ "quantity": -5 مہنگی اشیاء کو۔ مجموعی طور پر ان کی شراکت منفی ہو جاتی ہے، جس سے مجموعی مجموعی کم ہو جاتی ہے۔
مسئلہ 2 - فلوٹنگ پوائنٹ ریاضی: 0.1 + 0.2 جاوا اسکرپٹ میں 0.30000000000000004. راؤنڈنگ کے بغیر، مالی حسابات میں وقت کے ساتھ ساتھ غلطیاں جمع ہوتی رہتی ہیں۔
مسئلہ 3 - رعایتی ہیرا پھیری: اگر ایک علیحدہ اختتامی نقطہ ادائیگی کے بعد کل کا دوبارہ حساب لگائے بغیر آرڈر میں ترمیم کی اجازت دیتا ہے، تو صارف رعایت حاصل کرنے کے لیے آئٹمز شامل کر سکتے ہیں اور پھر رعایتی قیمت کو برقرار رکھتے ہوئے آئٹمز کو ہٹا سکتے ہیں۔
اس کمزوری کو کیسے ٹھیک کیا جائے۔
یہ تمام عددی آدانوں کو چیک کرتا ہے اور سرور کی طرف تمام رقوم کا دوبارہ حساب لگاتا ہے۔ فلوٹنگ پوائنٹ کی غلطیوں سے بچنے کے لیے، پیسے (سینٹس) پر عددی ریاضی کا استعمال کریں۔
// Express.js - Secure
app.post('/api/checkout', authenticate, async (req, res) => {
const { items } = req.body;
if (!Array.isArray(items) || items.length === 0) {
return res.status(400).json({ error: 'Cart must contain at least one item' });
}
if (items.length > 50) {
return res.status(400).json({ error: 'Cart cannot contain more than 50 items' });
}
let totalCents = 0; // Integer arithmetic avoids floating point errors
const processedItems = [];
for (const item of items) {
if (!/^\d+$/.test(String(item.productId))) {
return res.status(400).json({ error: `Invalid product ID: ${item.productId}` });
}
const productId = Number(item.productId);
// Validate quantity: must be a string of digits only, between 1 and 10
// parseInt("3abc", 10) returns 3 — we use regex to prevent this
if (!/^\d+$/.test(String(item.quantity))) {
return res.status(400).json({
error: `Invalid quantity for product ${productId}`
});
}
const quantity = Number(item.quantity);
if (quantity < 1 || quantity > 10) {
return res.status(400).json({
error: `Quantity for product ${productId} must be between 1 and 10`
});
}
const product = await db.query(
'SELECT id, name, price_cents, stock FROM products WHERE id = ? AND active = true',
[productId]
);
if (!product) {
return res.status(400).json({ error: `Product not found: ${productId}` });
}
if (product.stock < quantity) {
return res.status(400).json({
error: `Insufficient stock for "${product.name}"`
});
}
// Use the server's price — never trust a price from the client
totalCents += product.price_cents * quantity;
processedItems.push({
productId: product.id,
name: product.name,
quantity,
unitPriceCents: product.price_cents
});
}
// Integer arithmetic for the discount calculation
const discountMultiplier = totalCents > 10000 ? 90 : 100; // 10000 cents = $100
const finalTotalCents = Math.round(totalCents * discountMultiplier / 100);
await createOrder(req.user.id, processedItems, finalTotalCents);
res.json({
success: true,
total: (finalTotalCents / 100).toFixed(2),
currency: 'USD'
});
});
کیا بدلا اور کیوں:
-
انٹیجر (سینٹی) ریاضی فلوٹنگ پوائنٹ کی غلطیوں کو ختم کرتا ہے۔
10000 + 3000انٹیجر سینٹ ہمیشہ درست ہوتے ہیں۔ -
quantity < 1 || quantity > 10منفی مقدار اور غیر معقول بڑے آرڈرز سے پرہیز کریں۔ -
items.length > 50بہت بڑی درخواستوں سے پرہیز کریں۔ -
product.stock < quantityیقینی بنائیں کہ آپ کے آرڈرز دستیاب انوینٹری سے زیادہ نہیں ہیں۔ -
تمام ٹوٹل، چھوٹ اور حتمی قیمتوں کا حساب سرور سائیڈ قیمتوں کی بنیاد پر کیا جاتا ہے۔
کسی بھی عددی ان پٹ کے لیے ایک درست رینج کی وضاحت کرتا ہے۔ تمام سرور سائیڈ ٹوٹل کا دوبارہ حساب لگائیں۔ گاہک قیمت یا مقدار کے لیے قابل اعتماد ذریعہ نہیں ہیں۔
6. JWT کنفیگریشن کی خرابی۔
JWTs Node.js API میں ہر جگہ موجود ہیں۔ jsonwebtoken لائبریری اسے استعمال کرنا آسان بناتی ہے۔ یہ اچھی چیز بھی ہے اور بری بھی۔ یہ صحیح طریقے سے استعمال کرنا آسان ہے، لیکن ان طریقوں سے بھی جو اچھے لگتے ہیں جب تک کہ وہ نہ ہوں۔
OWASP ٹاپ 10 تصدیق کی ناکامیوں کو شناخت اور تصدیق کی ناکامیوں (A07:2021) کے طور پر درجہ بندی کرتا ہے۔ تین غلط ترتیبیں بار بار ظاہر ہوتی ہیں:
اپنے کوڈ میں اس خطرے کی نشاندہی کیسے کریں۔
غلطی 1 - الگورتھم متعین نہیں ہے۔ verify():
// Vulnerable
const decoded = jwt.verify(token, process.env.JWT_SECRET);
بغیر algorithms اختیارات آپ کو کچھ JWT نفاذ کو ایک ٹوکن قبول کرنے کے لیے چال کرنے کی اجازت دیتے ہیں جس میں اعلان کیا گیا ہے: "alg": "none" ہیڈر میں - یعنی کسی بھی دستخط کی ضرورت نہیں ہے۔
غلطی 2 - کمزور یا سخت کوڈ شدہ راز:
// Vulnerable
const token = jwt.sign({ userId: user.id }, 'secret');
ایک بار جب حملہ آور ایک درست ٹوکن حاصل کر لیتا ہے تو مختصر یا متوقع HS256 رازوں کو زبردستی آف لائن کیا جا سکتا ہے۔
غلطی 3 - پے لوڈ میں حساس ڈیٹا ہوتا ہے۔:
// Vulnerable
const token = jwt.sign({
userId: user.id,
passwordHash: user.passwordHash, // never do this
role: user.role
}, secret);
JWT پے لوڈ غیر انکرپٹڈ اور بیس 64 انکوڈڈ ہے۔ کوئی بھی جس کے پاس ٹوکن ہے وہ پے لوڈ کو ڈی کوڈ کر سکتا ہے اور اس کے مواد کو پڑھ سکتا ہے۔
ان مسائل کو کیسے حل کریں۔
// Secure JWT implementation
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
// Generate a strong secret once and store it as an environment variable:
// node -e "console.log(crypto.randomBytes(64).toString('hex'))"
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET || Buffer.from(JWT_SECRET, 'hex').length < 32) {
throw new Error('JWT_SECRET must be at least 32 random bytes');
}
function signToken(userId) {
return jwt.sign(
{ sub: userId }, // 'sub' is the standard claim for the user identifier
JWT_SECRET,
{
algorithm: 'HS256', // always declare the algorithm explicitly
expiresIn: '15m', // short-lived tokens reduce the window if a token is stolen
issuer: 'your-app-name',
audience: 'your-app-name'
}
);
}
function verifyToken(token) {
return jwt.verify(token, JWT_SECRET, {
algorithms: ['HS256'], // whitelist only the expected algorithm
issuer: 'your-app-name',
audience: 'your-app-name'
});
}
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = verifyToken(token);
req.user = { id: decoded.sub };
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
ٹوکن منسوخی پر نوٹس: JWT بے وطن ہے۔ اس کا مطلب ہے کہ ہمارے سرورز پر کوئی ریکارڈ نہیں رکھا جاتا ہے۔ اس کا مطلب یہ ہے کہ لاگ آؤٹ ہونے یا اکاؤنٹ سے سمجھوتہ کرنے پر اضافی انفراسٹرکچر کے بغیر ٹوکنز کو فوری طور پر باطل نہیں کیا جا سکتا۔
عام حلوں میں ایک قلیل المدت رسائی ٹوکن (15 منٹ) شامل ہوتا ہے جو کہ ریفریش ٹوکن اسٹور شدہ سرور سائیڈ کے ساتھ جوڑا جاتا ہے، یا Redis جیسے تیز اسٹور میں ٹوکن انکار کی فہرست۔ وہ طریقہ منتخب کریں جو آپ کی درخواست کی ضروریات کے مطابق ہو۔
JWT سیکیورٹی چیک لسٹ:
-
ہمیشہ گزرنا
algorithms: ['HS256']کوverify(). -
اگلا، کم از کم 32 بے ترتیب بائٹس کا ایک راز استعمال کریں جس کے ذریعہ تیار کیا گیا ہے:
crypto.randomBytes. -
ایک مختصر میعاد ختم ہونے کا وقت مقرر کریں۔
-
پے لوڈ میں صرف غیر حساس شناخت کنندگان (صارف IDs) کو اسٹور کریں، ای میلز، پاس ورڈز یا کرداروں کو نہیں۔
-
معیاری دعووں کا استعمال:
sub،iss،aud،exp. -
پیداوار میں جانے سے پہلے اپنی منسوخی کی حکمت عملی کا منصوبہ بنائیں۔
خلاصہ
ذیل میں اس ٹیوٹوریل میں شامل چھ خطرات کے زمروں کے لیے ایک فوری حوالہ ہے۔
| کمزوری | بنیادی وجہ | بنیادی درستگی |
|---|---|---|
| خشک | اجازت کی تصدیق غائب ہے۔ | تصدیق شدہ سیشنز میں ملکیت کی تصدیق کریں۔ |
| بلک مختص | تمام درخواست باڈی فیلڈز ڈیٹا بیس پر لاگو ہوتے ہیں۔ | وائٹ لسٹ میں واضح طور پر اجازت شدہ فیلڈز شامل کریں۔ |
| پروٹوٹائپ آلودگی | ناقابل بھروسہ ان پٹ کا بار بار انضمام | اسٹیٹ کو سرور سائڈ پر اسٹور کریں۔ استعمال کریں Object.create(null) انضمام کے اہداف کے لیے۔ |
| دوڑ کی حالت | جوہری کے بغیر تصدیق کے بعد چلائیں۔ | ایٹم UPDATE ... WHERE condition یا سودا |
| کاروباری منطق کی خرابی۔ | کلائنٹ کے ذریعہ فراہم کردہ عددی قدر پر بھروسہ کریں۔ | باقاعدہ اظہار کی توثیق ان پٹ۔ پیسے کے لیے عددی ریاضی کا استعمال کریں۔ |
| JWT کنفیگریشن کی خرابی۔ | کوئی الگورتھم وائٹ لسٹ نہیں، کمزور راز | واضح الگورتھمک انتظام، مضبوط بے ترتیب راز، مختصر میعاد |
ان میں سے کسی بھی کمزوری کا فائدہ اٹھانے کے لیے ہوشیار حملہ آور کی ضرورت نہیں ہے۔ آپ کو صرف کوڈ کی ضرورت ہے جو غلط وقت پر غلط چیزوں پر بھروسہ کرے۔
اندرونی بنانے کے قابل ایک نمونہ: ہمیشہ سیشن سے توثیق حاصل کریں، درخواست سے نہیں۔ رینج کا استعمال کرتے ہوئے تمام عددی ان پٹ کی توثیق کرتا ہے۔ مالیاتی تحریر کو جوہری بنائیں۔ واضح طور پر JWT کنفیگریشن بنائیں۔
ان خطرات سے نمٹنے کے طریقے کو سمجھنا تصویر کا ایک رخ ہے۔ یہ سمجھنا کہ حقیقی دنیا کی سیکیورٹی کے جائزے کے دوران ان کی شناخت اور استحصال کیسے کیا جاتا ہے۔
اگر آپ چیزوں کے جارحانہ پہلو کے بارے میں مزید جاننا چاہتے ہیں (دخول ٹیسٹ کرنے والے ویب ایپلیکیشن کے اہداف تک کیسے رسائی حاصل کرتے ہیں، وہ کیا تلاش کرتے ہیں، اور متعدد خامیوں کو کیسے جوڑتے ہیں)، تو ویب ایپلیکیشن کے حملے کی تکنیکوں کا یہ گائیڈ اس تناظر کو تفصیل سے احاطہ کرتا ہے۔