मला वाटायचे की स्वतःचे ऑथेंटिकेशन लेयर (authentication layer) लिहिणे ही सन्मानाची गोष्ट आहे. जर तुम्हाला JWTs समजले आणि तुम्ही पासवर्ड हॅश (hash) करू शकत असाल, तर यात काय कठीण असू शकते? मी एक Node बॅकएंड तयार केले, टोकन्स जारी केले आणि काम पूर्ण झाले असे मानले. कोड व्यवस्थित चालत होता. टेस्ट्स पास झाल्या. मग मी प्रत्यक्ष हल्ले कसे होतात याबद्दल वाचायला सुरुवात केली आणि माझा पायाच उखडला. इंजेक्शन (injection), एन्युमरेशन (enumeration) आणि साइड-चॅनेल लीक्स (side-channel leaks) वरील प्रत्येक प्रकरण वाचल्यानंतर मला खूप अस्वस्थ वाटू लागले. माझ्या ऑथ सिस्टममध्ये केवळ त्रुटी नव्हत्या; तर मी स्वतःच तिथे चार उघडी दारे ठेवली होती. मला काय आढळले आणि मी नेमके काय बदलले, ते खाली दिले आहे.
स्ट्रिंग कॉनकॅटनेशनद्वारे (String Concatenation) SQL इंजेक्शन
पहिली चूक ही सर्वात जुनी पद्धत होती. मी युजर इनपुट घेत होतो आणि ते थेट SQL स्ट्रिंग्समध्ये टाकत होतो. माझ्या लॉगिन रूटमध्ये, मी रिक्वेस्ट बॉडीमधून ईमेल घेतला आणि तो SELECT * FROM users WHERE email = '${email}' सारख्या क्वेरीमध्ये जोडत (concatenate) होतो. मला वाटले की हे सुरक्षित आहे कारण फ्रंटएंड माझ्या नियंत्रणात होते. हे एक धोकादायक गृहितक आहे. अटॅकरला तुमच्या फ्रंटएंडची गरज नसते. एक तयार केलेली (crafted) POST बॉडी तुमच्या लॉगिन चेकला डेटा ब्रीचमध्ये किंवा डेटाबेस पुसून टाकण्यात बदलू शकते. ' OR '1'='1 सारखा पेलोड किंवा त्याहून वाईट, टेबल्स ड्रॉप करणारी स्टॅक्ड क्वेरी (stacked query) पाठवली, आणि जर ती स्ट्रिंग कच्च्या स्वरूपात (raw) एक्झिक्युट झाली, तर तुमचा डेटा कायमचा जाईल. मी डेटाबेसमध्ये माझ्या कोड आणि अटॅकरच्या डेटा यातील फरक ओळखण्यासाठी कोणताही मार्ग ठेवला नव्हता.
यावर उपाय म्हणजे अधिक इनपुट व्हॅलिडेशन किंवा हाताने स्ट्रिंग्स एस्केप करणे हा नव्हता. खरा उपाय म्हणजे पॅरामीटराइज्ड क्वेरीज (parameterized queries) वापरणे हा होता. मी node-postgres कडे वळलो आणि $1 सारखे प्लेसहोल्डर्स वापरण्यास सुरुवात केली. आता क्वेरी एक टेम्पलेट बनते: SELECT * FROM users WHERE email = $1. ड्रायव्हर SQL आणि व्हॅल्यूज वेगवेगळ्या चॅनेलद्वारे पाठवतो. डेटाबेस इनपुटला केवळ डेटा म्हणून मानतो, त्यात कोणतेही कॅरेक्टर्स असले तरीही. या एका बदलामुळे इंजेक्शन अटॅक्सचा संपूर्ण प्रकार बंद होतो. हे वाचायला सोपे आहे, मेंटेन करायला सोपे आहे आणि प्रत्येक वेळी WHERE क्लॉज लिहिताना regex तज्ज्ञ बनण्याची गरज उरत नाही.
एरर मेसेजद्वारे ईमेल एन्युमरेशन (Email Enumeration)
माझी दुसरी चूक 'चांगल्या UX' सारखी वाटत होती. जेव्हा युजरने चुकीचा ईमेल टाइप केला, तेव्हा मी User not found असे उत्तर द्यायचो. जेव्हा त्यांनी ईमेल बरोबर पण पासवर्ड चुकीचा टाकला, तेव्हा मी Incorrect password असे उत्तर द्यायचो. हे उपयुक्त वाटत होते. पण हे अटॅकर्ससाठी माहिती गोळा करण्याचे (reconnaissance) साधन देखील होते. एन्युमरेशन स्क्रिप्ट्स हजारो ईमेल पत्त्यांसह तुमच्या लॉगिन एंडपॉइंटवर सतत हल्ले करू शकतात. जर रिस्पॉन्स बॉडी किंवा स्टेटस कोड खाते अस्तित्वात आहे की नाही यावर बदलत असेल, तर ती स्क्रिप्ट तुमच्या युजर्सची पडताळलेली यादी तयार करू शकते. ती यादी credential stuffing, targeted phishing आणि पुढील brute-force प्रयत्नांचा पाया बनते.
मला हे मान्य करावे लागले की कधीकधी सुरक्षिततेसाठी युजर-फ्रेंडलीनेसचा त्याग करावा लागतो. मी प्रत्येक अयशस्वी लॉगिन पाथ बदलून एकच स्ट्रिंग रिटर्न केली: Invalid credentials. कोणतेही संकेत नाही. एरर रिस्पॉन्समध्ये कोणतेही ब्रांचिंग लॉजिक नाही. ईमेल नसेल, पासवर्ड चुकीचा असेल किंवा खाते लॉक असेल, मजकूर तसाच राहतो. हे रजिस्ट्रेशन आणि पासवर्ड-रिसेट फ्लोला देखील लागू होते; एखादा पत्ता तुमच्या सिस्टममध्ये आधीपासून आहे की नाही हे उघड करू नका. एक सामान्य (generic) मेसेज माहितीची अशी गळती थांबवतो ज्यावर अटॅकर्स अवलंबून असतात.
पासवर्ड कंपेरिजनमधील टाइमिंग अटॅक्स (Timing Attacks)
तिसरा बग अदृश्य होता. मी
