میں پہلے سوچتا تھا کہ اپنا آتھنٹیکیشن لیئر (authentication layer) لکھنا ایک اعزاز کی بات ہے۔ اگر آپ JWTs کو سمجھتے ہیں اور پاس ورڈ کو ہیش (hash) کر سکتے ہیں، تو یہ کتنا مشکل ہو سکتا ہے؟ میں نے ایک Node بیک اینڈ تیار کیا، ٹوکنز جاری کیے، اور کام مکمل سمجھا۔ کوڈ کام کر رہا تھا۔ ٹیسٹ پاس ہو گئے۔ پھر میں نے یہ پڑھنا شروع کیا کہ اصل حملے حقیقت میں کیسے ہوتے ہیں، اور میرے پیروں تلے سے زمین نکل گئی۔ انجیکشن (injection)، اینومریشن (enumeration)، اور سائیڈ چینل لیکس (side-channel leaks) پر ہر باب مجھے ایک مایوسی کے احساس کے ساتھ اپنے ایڈیٹر کے پاس واپس لے گیا۔ میرے آتھنٹیکیشن سسٹم میں صرف خامیاں ہی نہیں تھیں؛ بلکہ اس میں چار ایسے کھلے دروازے تھے جو میں نے خود لگائے تھے۔ یہاں وہ ہے جو میں نے دریافت کیا، اور میں نے بالکل کیا تبدیلیاں کیں۔

اسٹرنگ کنکیٹینیشن (String Concatenation) کے ذریعے SQL انجیکشن

پہلی غلطی کتاب کی سب سے پرانی چال تھی۔ میں صارف کی ان پٹ لے رہا تھا اور اسے براہ راست SQL اسٹرنگز میں ڈال رہا تھا۔ اپنے لاگ ان روٹ میں، میں نے ریکویسٹ باڈی سے ای میل لی اور اسے اس طرح کی کوئری میں شامل کر دیا: SELECT * FROM users WHERE email = '${email}'۔ یہ بے ضرر محسوس ہوتا تھا کیونکہ فرنٹ اینڈ میرے کنٹرول میں تھا۔ یہ ایک خطرناک مفروضہ ہے۔ حملہ آور کو آپ کے فرنٹ اینڈ کی ضرورت نہیں ہے۔ ایک ہی تیار کردہ POST باڈی اس لاگ ان چیک کو ڈیٹا کی چوری یا ڈیٹا بیس کے مکمل خاتمے میں بدل سکتی ہے۔ اگر کوئی ' OR '1'='1 جیسا پی لوڈ یا اس سے بھی بدتر، ٹیبلز کو ڈراپ کرنے والی اسٹیکڈ کوئری (stacked query) بھیجتا ہے، اور اگر اسٹرنگ کو خام طور پر (raw) چلایا جاتا ہے، تو آپ کا ڈیٹا ختم ہو جائے گا۔ میں نے ڈیٹا بیس کو میرے کوڈ اور حملہ آور کے ڈیٹا کے درمیان فرق کرنے کا کوئی طریقہ نہیں دیا تھا۔

اس کا حل مزید ان پٹ ویلیڈیشن یا دستی طور پر اسٹرنگز کو اسکیپ (escaping) کرنا نہیں تھا۔ اصل حل پیرامیٹرائزڈ کوئریز (parameterized queries) تھا۔ میں نے node-postgres پر سوئچ کیا اور $1 جیسے پلیس ہولڈرز کا استعمال شروع کیا۔ کوئری اب ایک ٹیمپلیٹ بن گئی: SELECT * FROM users WHERE email = $1۔ ڈرائیور SQL اور ویلیوز کو الگ الگ چینلز کے ذریعے بھیجتا ہے۔ ڈیٹا بیس ان پٹ کو سختی سے صرف ڈیٹا کے طور پر لیتا ہے، چاہے اس میں کوئی بھی کریکٹر موجود ہو۔ یہ ایک تبدیلی انجیکشن حملوں کی پوری کلاس کو بند کر دیتی ہے۔ یہ پڑھنے میں سادہ، برقرار رکھنے میں آسان ہے، اور یہ ہر بار WHERE کلاز لکھتے وقت ریگیکس (regex) جادوگر بننے کے بوجھ کو ختم کر دیتی ہے۔

ایرر میسجز کے ذریعے ای میل اینومریشن (Email Enumeration)

میری دوسری غلطی ایک اچھے UX کی طرح لگتی تھی۔ جب صارف نے غلط ای میل ٹائپ کی، تو میں نے User not found واپس کیا۔ جب انہوں نے ای میل صحیح لیکن پاس ورڈ غلط لکھا، تو میں نے Incorrect password واپس کیا۔ یہ مددگار محسوس ہوتا تھا۔ لیکن یہ حملہ آوروں کے لیے جاسوسی کا ایک ذریعہ بھی تھا۔ اینومریشن اسکرپٹس ہزاروں ای میل ایڈریسز کے ساتھ آپ کے لاگ ان اینڈ پوائنٹ پر حملے کر سکتے ہیں۔ اگر رسپانس باڈی یا اسٹیٹس کوڈ اس بنیاد پر بدلتا ہے کہ اکاؤنٹ موجود ہے یا نہیں، تو اسکرپٹ آپ کے صارفین کی ایک تصدیق شدہ فہرست بنا سکتا ہے۔ وہ فہرست کریڈنشل اسٹفنگ (credential stuffing)، ٹارگٹڈ فشنگ (targeted phishing)، اور مزید بروٹ فورس (brute-force) کوششوں کی بنیاد بن جاتی ہے۔

مجھے یہ تسلیم کرنا پڑا کہ کبھی کبھی صارف کی سہولت کو سیکیورٹی کے لیے قربان کرنا پڑتا ہے۔ میں نے ہر ناکام لاگ ان کے لیے بالکل ایک ہی اسٹرنگ واپس کرنا شروع کر دی: Invalid credentials۔ کوئی اشارہ نہیں۔ ایرر رسپانس میں کوئی الگ منطق نہیں۔ چاہے ای میل موجود نہ ہو، پاس ورڈ غلط ہو، یا اکاؤنٹ لاک ہو، متن بالکل ایک جیسا رہتا ہے۔ یہ رجسٹریشن اور پاس ورڈ ری سیٹ کے عمل پر بھی لاگو ہوتا ہے؛ یہ ظاہر نہ کریں کہ کوئی ایڈریس پہلے سے آپ کے سسٹم میں موجود ہے یا نہیں۔ ایک عام پیغام اس معلومات کے اخراج (information leak) کو ختم کر دیتا ہے جس پر حملہ آور انحصار کرتے ہیں۔

پاس ورڈ موازنہ میں ٹائمنگ اٹیکس (Timing Attacks)

تیسری غلطی نظر نہیں آتی تھی۔ میں...