flawopen.com/SQL इंजेक्शन/Python
एक ऐसे फ़ॉर्म की कल्पना कीजिए जो सिर्फ़ 482 जैसा टिकट नंबर मांगता है। SQL इंजेक्शन तब होता है जब कोई उस बॉक्स में नंबर की जगह कुछ चालाकी भरा टेक्स्ट डाल देता है — एक ऐसी तरकीब वाली इबारत जिससे सिस्टम "सिर्फ़ टिकट 482" दिखाने के बजाय "सारे टिकट दिखाओ" कह बैठता है, क्योंकि सिस्टम ने कभी यह जांचा ही नहीं कि उसे जो मिला वह वाकई सिर्फ़ एक नंबर था या नहीं।
SQL इंजेक्शन तब होता है जब यूज़र-कंट्रोल्ड इनपुट को एक अलग मान के रूप में भेजने के बजाय सीधे डेटाबेस क्वेरी के टेक्स्ट में डाल दिया जाता है। अगर कोड स्ट्रिंग्स को जोड़कर क्वेरी बनाता है, तो हमलावर ऐसा इनपुट दे सकता है जो क्वेरी की असली संरचना ही बदल दे — एक "सिर्फ़ एक रो लाओ" वाली क्वेरी को ऐसी क्वेरी में बदल दे जो सारी रो लौटाए, या पूरी टेबल मिटा दे।
Python में यह लगभग हमेशा एक ही तरह दिखता है: डेटाबेस ड्राइवर के अंदर बने पैरामीटर प्लेसहोल्डर की बजाय, f-स्ट्रिंग, % फ़ॉर्मेटिंग, या सीधे + जोड़कर बनाई गई डेटाबेस कॉल।
2015 में, ब्रिटिश टेलीकॉम कंपनी TalkTalk में एक ऐसी पुरानी वेब पेज में SQL इंजेक्शन की कमज़ोरी का फ़ायदा उठाकर हमलावरों ने 1,50,000 से ज़्यादा ग्राहकों को प्रभावित करने वाली डेटा सेंध लगाई — यह पेज कंपनी को एक अधिग्रहण के ज़रिए विरासत में मिली थी। यूके के डेटा प्रोटेक्शन रेगुलेटर ने TalkTalk पर £4,00,000 का जुर्माना लगाया और इस चूक को "रोकी जा सकने वाली और बुनियादी" बताया।
स्रोत: यूके Information Commissioner's Office की एनफ़ोर्समेंट नोटिस, 2016 — नीचे "संदर्भ" देखें।# user_id सीधे रिक्वेस्ट से आता है
def get_user(cursor, user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
return cursor.fetchone()
# मान अलग से भेजा जाता है, कभी टेक्स्ट में नहीं जोड़ा जाता
def get_user(cursor, user_id):
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
return cursor.fetchone()
ठीक किए गए वर्ज़न में क्वेरी का टेक्स्ट और मान execute() को दो अलग-अलग आर्ग्युमेंट के रूप में भेजे जाते हैं। डेटाबेस ड्राइवर भी इन्हें डेटाबेस तक अलग-अलग भेजता है — मान जुड़ने से पहले ही क्वेरी की संरचना तय हो चुकी होती है, इसलिए मान में चाहे जो भी कैरेक्टर हों, उसे कभी SQL सिंटैक्स के हिस्से के रूप में नहीं समझा जा सकता। f-स्ट्रिंग ऐसा नहीं कर सकती, क्योंकि जब तक execute() क्वेरी देखता है, तब तक मान पहले ही टेक्स्ट में ऐसे शामिल हो चुका होता है जैसे वह हमेशा से कमांड का हिस्सा रहा हो।
cursor.execute("... WHERE id = %s" % user_id) उतना ही असुरक्षित है जितनी f-स्ट्रिंग। प्लेसहोल्डर तभी सुरक्षित बनता है जब मान execute() के अपने दूसरे आर्ग्युमेंट के रूप में भेजा जाए — cursor.execute("...WHERE id = %s", (user_id,)) — ताकि यह प्रतिस्थापन Python की स्ट्रिंग फ़ॉर्मेटिंग नहीं, बल्कि ड्राइवर करे।
Django का ORM और SQLAlchemy का क्वेरी बिल्डर सामान्य क्वेरी में अपने-आप पैरामीटराइज़ कर देते हैं। जैसे ही आप Model.objects.raw() या SQLAlchemy का text() इस्तेमाल करके वह रॉ SQL f-स्ट्रिंग से बनाते हैं, ख़तरा फिर लौट आता है।
psycopg2 (PostgreSQL) कॉलम के टाइप की परवाह किए बिना %s इस्तेमाल करता है; sqlite3 ? इस्तेमाल करता है। एक ड्राइवर के डॉक्यूमेंटेशन से प्लेसहोल्डर स्टाइल कॉपी करके दूसरे में डालने पर चुपचाप गड़बड़ी होती है — मान लेने के बजाय अपने ख़ास ड्राइवर की पैरामीटर स्टाइल जांच लें।
ORM के सामान्य क्वेरी API के लिए यह सही है — पर जैसे ही आप raw() या text() से ख़ुद स्ट्रिंग बनाते हैं, यह बात ग़लत हो जाती है।
ख़तरा रनटाइम पर मान के टाइप में नहीं है — ख़तरा इसमें है कि क्वेरी सिरे से स्ट्रिंग इंटरपोलेशन से बनाई जा रही है। कोड की ज़िंदगी में जिस भी पल यह मान्यता ग़लत साबित होगी, कमज़ोरी वहां पहले से मौजूद होगी।
मैनुअल एस्केपिंग ड्राइवर पर निर्भर होती है और उसमें बारीक़ ग़लती आसानी से हो जाती है। पैरामीटराइज़्ड क्वेरी एस्केपिंग का सख़्त वर्ज़न नहीं है — वे इस समस्या से पूरी तरह बचती हैं, क्योंकि मान कभी क्वेरी टेक्स्ट का हिस्सा बनता ही नहीं।
grep -rn "execute(f\"" --include="*.py" .
grep -rn "execute(.*%\s*(" --include="*.py" .
grep -rn "\.raw(\|text(" --include="*.py" .
उसके सामान्य क्वेरी मेथड के लिए, हां। लेकिन रॉ-क्वेरी वाले एस्केप हैच के लिए नहीं — वे बिल्कुल उतने ही सुरक्षित हैं जितना हाथ से लिखा SQL, उससे ज़्यादा नहीं।
नहीं। कोई भी ऐसी चीज़ जो असल में किसी बाहरी पक्ष के नियंत्रण में हो, इसमें शामिल है — HTTP हेडर, अपलोड की गई फ़ाइल के नाम, यहां तक कि किसी थर्ड-पार्टी API से आया मान जिस पर आपका ऐप भरोसा करता है।
कर तो सकते हैं, पर यह नाज़ुक और ड्राइवर पर निर्भर तरीक़ा है। असली फ़िक्स पैरामीटराइज़्ड क्वेरी है, एस्केपिंग का सख़्त वर्ज़न नहीं।