मुझे लगता था कि अपना खुद का authentication layer लिखना सम्मान की बात है। अगर आप JWTs समझते हैं और पासवर्ड को hash कर सकते हैं, तो यह कितना कठिन हो सकता है? मैंने एक Node backend तैयार किया, टोकन जारी किए, और काम पूरा समझ लिया। कोड काम कर रहा था। टेस्ट पास हो गए। फिर मैंने पढ़ना शुरू किया कि असल में हमले कैसे होते हैं, और मेरी सारी समझ बिखर गई। injection, enumeration, और side-channel leaks पर हर अध्याय ने मुझे भारी मन के साथ अपने एडिटर के पास वापस भेज दिया। मेरे auth system में सिर्फ कमियां ही नहीं थीं; उसमें चार ऐसे खुले दरवाजे थे जिन्हें मैंने खुद लगाया था। यहाँ वह सब है जो मैंने पाया, और मैंने वास्तव में क्या बदला।
String Concatenation के माध्यम से SQL Injection
पहला बग सबसे पुराने तरीकों में से एक था। मैं यूजर इनपुट ले रहा था और उसे सीधे SQL स्ट्रिंग्स में डाल रहा था। अपने login route में, मैंने request body से ईमेल लिया और उसे इस तरह की क्वेरी में जोड़ दिया: SELECT * FROM users WHERE email = '${email}'। मुझे लगा कि यह हानिरहित है क्योंकि frontend मेरे नियंत्रण में था। यह एक खतरनाक धारणा है। हमलावर को आपके frontend की ज़रूरत नहीं है। एक अकेला तैयार किया गया POST body उस login check को डेटा ब्रीच या डेटाबेस को मिटाने वाले हमले में बदल सकता है। यदि आप ' OR '1'='1 जैसा payload भेजते हैं या उससे भी बुरा, एक stacked query जो tables को drop कर दे, और यदि स्ट्रिंग को raw रूप में निष्पादित (execute) किया जाता है, तो आपका डेटा चला जाएगा। मैंने डेटाबेस को मेरे कोड और हमलावर के डेटा के बीच अंतर करने का कोई तरीका नहीं दिया था।
इसका समाधान अधिक input validation या मैन्युअल रूप से स्ट्रिंग्स को escape करना नहीं था। असली समाधान parameterized queries थे। मैं node-postgres पर स्विच हुआ और $1 जैसे placeholders का उपयोग करना शुरू किया। अब क्वेरी एक टेम्पलेट बन जाती है: SELECT * FROM users WHERE email = $1। ड्राइवर SQL और वैल्यूज़ को अलग-अलग चैनलों के माध्यम से भेजता है। डेटाबेस इनपुट को सख्ती से केवल डेटा के रूप में मानता है, चाहे उसमें कोई भी कैरेक्टर हो। यह एक बदलाव injection attacks की पूरी श्रेणी को बंद कर देता है। यह पढ़ने में सरल है, बनाए रखने में आसान है, और यह हर बार WHERE clause लिखते समय regex wizard बनने के बोझ को हटा देता है।
Error Messages के माध्यम से Email Enumeration
मेरी दूसरी गलती अच्छी UX जैसी लग रही थी। जब कोई यूजर गलत ईमेल टाइप करता, तो मैं User not found वापस करता था। जब वे ईमेल सही डालते लेकिन पासवर्ड गलत होता, तो मैं Incorrect password वापस करता था। यह मददगार लगता था। लेकिन यह हमलावरों के लिए एक reconnaissance tool भी था। Enumeration scripts हजारों ईमेल एड्रेस के साथ आपके login endpoint पर हमला कर सकती हैं। यदि रिस्पॉन्स बॉडी या स्टेटस कोड इस आधार पर बदलता है कि अकाउंट मौजूद है या नहीं, तो स्क्रिप्ट आपके यूजर्स की एक सत्यापित सूची बना सकती है। वह सूची credential stuffing, लक्षित phishing, और आगे के brute-force प्रयासों का आधार बन जाती है।
मुझे यह स्वीकार करना पड़ा कि कभी-कभी यूजर-फ्रेंडलीनेस को सुरक्षा के सामने झुकना पड़ता है। मैंने हर विफल लॉगिन पाथ को बिल्कुल एक ही स्ट्रिंग वापस करने के लिए बदल दिया: Invalid credentials। कोई संकेत नहीं। एरर रिस्पॉन्स में कोई branching logic नहीं। चाहे ईमेल गायब हो, पासवर्ड गलत हो, या अकाउंट लॉक हो, टेक्स्ट बिल्कुल एक जैसा रहता है। यह रजिस्ट्रेशन और पासवर्ड-रीसेट फ्लो पर भी लागू होता है; यह खुलासा न करें कि कोई पता पहले से आपके सिस्टम में है या नहीं। एक सामान्य संदेश उस जानकारी के लीक होने को रोकता है जिस पर हमलावर निर्भर करते हैं।
Password Comparison में Timing Attacks
तीसरा बग अदृश्य था। मैं
