हर कुछ वर्षों में, ओपन वेब एप्लिकेशन सिक्योरिटी प्रोजेक्ट (ओडब्ल्यूएएसपी) सबसे महत्वपूर्ण वेब एप्लिकेशन सुरक्षा जोखिमों को रैंक करता है। पहली रिपोर्ट के बाद से, इंजेक्शन जोखिम हमेशा शीर्ष पर रहा है। सभी इंजेक्शन प्रकारों में, SQL इंजेक्शन सबसे आम हमले वैक्टर में से एक है, और यकीनन सबसे खतरनाक है। चूंकि पायथन दुनिया की सबसे लोकप्रिय प्रोग्रामिंग भाषाओं में से एक है, इसलिए यह जानना महत्वपूर्ण है कि पायथन एसक्यूएल इंजेक्शन से कैसे बचाव किया जाए।
इस ट्यूटोरियल में, आप सीखेंगे:
- क्या पायथन SQL इंजेक्शन है और इसे कैसे रोका जाए
- कैसे क्वेरी लिखें पैरामीटर के रूप में अक्षर और पहचानकर्ता दोनों के साथ
- कैसे प्रश्नों को सुरक्षित रूप से निष्पादित करें डेटाबेस में
यह ट्यूटोरियल सभी डेटाबेस इंजन के उपयोगकर्ताओं . के लिए उपयुक्त है . यहां उदाहरण PostgreSQL का उपयोग करते हैं, लेकिन परिणाम अन्य डेटाबेस प्रबंधन प्रणालियों (जैसे SQLite, MySQL, Microsoft SQL Server, Oracle, और इसी तरह) में पुन:प्रस्तुत किए जा सकते हैं।
मुफ़्त बोनस: पायथन मास्टरी पर 5 विचार, पायथन डेवलपर्स के लिए एक नि:शुल्क पाठ्यक्रम जो आपको रोडमैप और मानसिकता दिखाता है कि आपको अपने पायथन कौशल को अगले स्तर तक ले जाने की आवश्यकता होगी।
पायथन SQL इंजेक्शन को समझना
SQL इंजेक्शन हमले एक ऐसी सामान्य सुरक्षा भेद्यता है कि दिग्गज xkcd वेबकॉमिक ने इसे एक कॉमिक समर्पित किया:
SQL क्वेरी बनाना और निष्पादित करना एक सामान्य कार्य है। हालाँकि, दुनिया भर की कंपनियाँ अक्सर SQL कथनों की रचना करते समय भयानक गलतियाँ करती हैं। जबकि ओआरएम परत आमतौर पर एसक्यूएल प्रश्नों की रचना करती है, कभी-कभी आपको अपना खुद का लिखना पड़ता है।
जब आप इन प्रश्नों को सीधे डेटाबेस में निष्पादित करने के लिए पायथन का उपयोग करते हैं, तो एक मौका है कि आप ऐसी गलतियाँ कर सकते हैं जो आपके सिस्टम से समझौता कर सकती हैं। इस ट्यूटोरियल में, आप सीखेंगे कि डायनामिक SQL क्वेरी बनाने वाले फ़ंक्शन को सफलतापूर्वक कैसे कार्यान्वित किया जाए बिना अपने सिस्टम को Python SQL इंजेक्शन के लिए जोखिम में डालना।
डेटाबेस सेट करना
आरंभ करने के लिए, आप एक नया PostgreSQL डेटाबेस सेट करने जा रहे हैं और इसे डेटा के साथ पॉप्युलेट करेंगे। पूरे ट्यूटोरियल में, आप इस डेटाबेस का उपयोग प्रत्यक्ष रूप से यह देखने के लिए करेंगे कि Python SQL इंजेक्शन कैसे काम करता है।
डेटाबेस बनाना
सबसे पहले, अपना शेल खोलें और उपयोगकर्ता के स्वामित्व वाला एक नया PostgreSQL डेटाबेस बनाएं postgres
:
$ createdb -O postgres psycopgtest
यहां आपने कमांड लाइन विकल्प -O
. का उपयोग किया है डेटाबेस के स्वामी को उपयोगकर्ता के लिए सेट करने के लिए postgres
. आपने डेटाबेस का नाम भी निर्दिष्ट किया है, जो psycopgtest
. है ।
नोट: postgres
एक विशेष उपयोगकर्ता . है , जिसे आप आम तौर पर प्रशासनिक कार्यों के लिए आरक्षित रखते हैं, लेकिन इस ट्यूटोरियल के लिए, postgres
का उपयोग करना ठीक है . हालांकि, एक वास्तविक प्रणाली में, आपको डेटाबेस का स्वामी बनने के लिए एक अलग उपयोगकर्ता बनाना चाहिए।
आपका नया डेटाबेस जाने के लिए तैयार है! आप इसे psql
. का उपयोग करके कनेक्ट कर सकते हैं :
$ psql -U postgres -d psycopgtest
psql (11.2, server 10.5)
Type "help" for help.
अब आप डेटाबेस से जुड़े हैं psycopgtest
उपयोगकर्ता के रूप में postgres
. यह उपयोगकर्ता डेटाबेस का स्वामी भी है, इसलिए आपके पास डेटाबेस में प्रत्येक तालिका पर पढ़ने की अनुमति होगी।
डेटा के साथ तालिका बनाना
इसके बाद, आपको कुछ उपयोगकर्ता जानकारी के साथ एक तालिका बनानी होगी और उसमें डेटा जोड़ना होगा:
psycopgtest=# CREATE TABLE users (
username varchar(30),
admin boolean
);
CREATE TABLE
psycopgtest=# INSERT INTO users
(username, admin)
VALUES
('ran', true),
('haki', false);
INSERT 0 2
psycopgtest=# SELECT * FROM users;
username | admin
----------+-------
ran | t
haki | f
(2 rows)
तालिका में दो स्तंभ हैं:username
और admin
. admin
कॉलम इंगित करता है कि उपयोगकर्ता के पास प्रशासनिक विशेषाधिकार हैं या नहीं। आपका लक्ष्य admin
. को लक्षित करना है फ़ील्ड और उसका दुरुपयोग करने का प्रयास करें।
पायथन वर्चुअल एनवायरनमेंट सेट करना
अब जब आपके पास एक डेटाबेस है, तो अपने पायथन वातावरण को स्थापित करने का समय आ गया है। इसे कैसे करें, इस बारे में चरण-दर-चरण निर्देशों के लिए, Python Virtual Environments:A Primer देखें।
एक नई निर्देशिका में अपना वर्चुअल वातावरण बनाएं:
(~/src) $ mkdir psycopgtest
(~/src) $ cd psycopgtest
(~/src/psycopgtest) $ python3 -m venv venv
आपके द्वारा इस आदेश को चलाने के बाद, venv
. नामक एक नई निर्देशिका बनाया जाएगा। यह निर्देशिका आपके द्वारा संस्थापित सभी संकुलों को आभासी वातावरण में संग्रहित करेगी।
डेटाबेस से कनेक्ट करना
Python में किसी डेटाबेस से कनेक्ट करने के लिए, आपको एक डेटाबेस एडेप्टर . की आवश्यकता होती है . अधिकांश डेटाबेस एडेप्टर पायथन डेटाबेस एपीआई विशिष्टता पीईपी 249 के संस्करण 2.0 का पालन करते हैं। प्रत्येक प्रमुख डेटाबेस इंजन में एक अग्रणी एडेप्टर होता है:
डेटाबेस | एडेप्टर |
---|---|
PostgreSQL | साइकोप |
SQLite | sqlite3 |
ओरेकल | cx_oracle |
MySql | MySQLdb |
PostgreSQL डेटाबेस से कनेक्ट करने के लिए, आपको Psycopg इंस्टॉल करना होगा, जो कि Python में PostgreSQL के लिए सबसे लोकप्रिय एडेप्टर है। Django ORM डिफ़ॉल्ट रूप से इसका उपयोग करता है, और यह SQLAlchemy द्वारा भी समर्थित है।
अपने टर्मिनल में, आभासी वातावरण को सक्रिय करें और pip
. का उपयोग करें psycopg
स्थापित करने के लिए :
(~/src/psycopgtest) $ source venv/bin/activate
(~/src/psycopgtest) $ python -m pip install psycopg2>=2.8.0
Collecting psycopg2
Using cached https://....
psycopg2-2.8.2.tar.gz
Installing collected packages: psycopg2
Running setup.py install for psycopg2 ... done
Successfully installed psycopg2-2.8.2
अब आप अपने डेटाबेस से कनेक्शन बनाने के लिए तैयार हैं। यहां आपकी पायथन लिपि की शुरुआत है:
import psycopg2
connection = psycopg2.connect(
host="localhost",
database="psycopgtest",
user="postgres",
password=None,
)
connection.set_session(autocommit=True)
आपने psycopg2.connect()
. का इस्तेमाल किया कनेक्शन बनाने के लिए। यह फ़ंक्शन निम्नलिखित तर्कों को स्वीकार करता है:
-
host
सर्वर का IP पता या DNS है जहां आपका डेटाबेस स्थित है। इस मामले में, होस्ट आपकी स्थानीय मशीन है, याlocalhost
। -
database
कनेक्ट करने के लिए डेटाबेस का नाम है। आप पहले बनाए गए डेटाबेस से कनेक्ट करना चाहते हैं,psycopgtest
। -
user
डेटाबेस के लिए अनुमतियों वाला उपयोगकर्ता है। इस मामले में, आप मालिक के रूप में डेटाबेस से जुड़ना चाहते हैं, इसलिए आप उपयोगकर्ता को पास करते हैंpostgres
। -
password
आपके द्वाराuser
. में निर्दिष्ट किसी के लिए भी पासवर्ड है . अधिकांश विकास परिवेशों में, उपयोगकर्ता बिना पासवर्ड के स्थानीय डेटाबेस से जुड़ सकते हैं।
कनेक्शन स्थापित करने के बाद, आपने सत्र को autocommit=True
. के साथ कॉन्फ़िगर किया है . autocommit
को सक्रिय किया जा रहा है इसका मतलब है कि आपको commit
. जारी करके लेनदेन को मैन्युअल रूप से प्रबंधित करने की आवश्यकता नहीं होगी या rollback
. अधिकांश ओआरएम में यह डिफ़ॉल्ट व्यवहार है। आप इस व्यवहार का उपयोग यहां भी करते हैं ताकि आप लेन-देन के प्रबंधन के बजाय SQL क्वेरी लिखने पर ध्यान केंद्रित कर सकें।
नोट: Django उपयोगकर्ता ORM द्वारा उपयोग किए गए कनेक्शन का उदाहरण django.db.connection
से प्राप्त कर सकते हैं :
from django.db import connection
प्रश्न निष्पादित करना
अब जब आपके पास डेटाबेस से कनेक्शन है, तो आप एक क्वेरी निष्पादित करने के लिए तैयार हैं:
>>>>>> with connection.cursor() as cursor:
... cursor.execute('SELECT COUNT(*) FROM users')
... result = cursor.fetchone()
... print(result)
(2,)
आपने connection
. का उपयोग किया है cursor
बनाने के लिए आपत्ति . पायथन में एक फ़ाइल की तरह, cursor
एक संदर्भ प्रबंधक के रूप में लागू किया गया है। जब आप प्रसंग बनाते हैं, तो एक cursor
डेटाबेस को कमांड भेजने के लिए उपयोग करने के लिए आपके लिए खोला गया है। जब संदर्भ बाहर निकलता है, cursor
बंद हो जाता है और अब आप इसका उपयोग नहीं कर सकते।
नोट: संदर्भ प्रबंधकों के बारे में अधिक जानने के लिए, Python Context Managers और "with" स्टेटमेंट देखें।
संदर्भ के अंदर, आपने cursor
. का उपयोग किया था एक क्वेरी निष्पादित करने और परिणाम लाने के लिए। इस मामले में, आपने users
. में पंक्तियों को गिनने के लिए एक क्वेरी जारी की है टेबल। क्वेरी से परिणाम प्राप्त करने के लिए, आपने cursor.fetchone()
executed निष्पादित किया और एक टपल प्राप्त किया। चूंकि क्वेरी केवल एक परिणाम लौटा सकती है, आपने fetchone()
. का उपयोग किया है . यदि क्वेरी एक से अधिक परिणाम लौटाने वाली थी, तो आपको cursor
पर या तो पुनरावृति करने की आवश्यकता होगी या दूसरे में से किसी एक का उपयोग करें fetch*
तरीके।
SQL में क्वेरी पैरामीटर का उपयोग करना
पिछले खंड में, आपने एक डेटाबेस बनाया, उससे एक कनेक्शन स्थापित किया, और एक क्वेरी निष्पादित की। आपके द्वारा उपयोग की गई क्वेरी स्थिर . थी . दूसरे शब्दों में, इसमें कोई पैरामीटर नहीं . था . अब आप अपने प्रश्नों में पैरामीटर का उपयोग करना शुरू कर देंगे।
सबसे पहले, आप एक फ़ंक्शन को लागू करने जा रहे हैं जो यह जांचता है कि उपयोगकर्ता एक व्यवस्थापक है या नहीं। is_admin()
एक उपयोगकर्ता नाम स्वीकार करता है और उस उपयोगकर्ता की व्यवस्थापक स्थिति लौटाता है:
# BAD EXAMPLE. DON'T DO THIS!
def is_admin(username: str) -> bool:
with connection.cursor() as cursor:
cursor.execute("""
SELECT
admin
FROM
users
WHERE
username = '%s'
""" % username)
result = cursor.fetchone()
admin, = result
return admin
यह फ़ंक्शन admin
. का मान प्राप्त करने के लिए एक क्वेरी निष्पादित करता है किसी दिए गए उपयोगकर्ता नाम के लिए कॉलम। आपने fetchone()
. का इस्तेमाल किया एक परिणाम के साथ एक टपल वापस करने के लिए। फिर, आपने इस टपल को वेरिएबल admin
. में खोल दिया . अपने फ़ंक्शन का परीक्षण करने के लिए, कुछ उपयोगकर्ता नाम जांचें:
>>> is_admin('haki')
False
>>> is_admin('ran')
True
अब तक सब ठीक है। फ़ंक्शन ने दोनों उपयोगकर्ताओं के लिए अपेक्षित परिणाम लौटाया। लेकिन गैर-मौजूदा उपयोगकर्ता के बारे में क्या? इस पायथन ट्रेसबैक पर एक नज़र डालें:
>>>>>> is_admin('foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 12, in is_admin
TypeError: cannot unpack non-iterable NoneType object
जब उपयोगकर्ता मौजूद नहीं होता है, तो एक TypeError
उठाया है। ऐसा इसलिए है क्योंकि .fetchone()
रिटर्न None
जब कोई परिणाम न मिले, और None
को अनपैक करना एक TypeError
उठाता है . टपल को अनपैक करने का एकमात्र स्थान वह है जहां आप admin
को भरते हैं result
. से ।
गैर-मौजूदा उपयोगकर्ताओं को संभालने के लिए, result
. के लिए एक विशेष मामला बनाएं None
है :
# BAD EXAMPLE. DON'T DO THIS!
def is_admin(username: str) -> bool:
with connection.cursor() as cursor:
cursor.execute("""
SELECT
admin
FROM
users
WHERE
username = '%s'
""" % username)
result = cursor.fetchone()
if result is None:
# User does not exist
return False
admin, = result
return admin
यहां, आपने None
. को संभालने के लिए एक विशेष मामला जोड़ा है . अगर username
मौजूद नहीं है, तो फ़ंक्शन को False
वापस करना चाहिए . एक बार फिर, कुछ उपयोगकर्ताओं पर फ़ंक्शन का परीक्षण करें:
>>> is_admin('haki')
False
>>> is_admin('ran')
True
>>> is_admin('foo')
False
महान! फ़ंक्शन अब गैर-मौजूदा उपयोगकर्ता नामों को भी संभाल सकता है।
पायथन एसक्यूएल इंजेक्शन के साथ क्वेरी पैरामीटर का शोषण करना
पिछले उदाहरण में, आपने क्वेरी उत्पन्न करने के लिए स्ट्रिंग इंटरपोलेशन का उपयोग किया था। फिर, आपने क्वेरी निष्पादित की और परिणामी स्ट्रिंग को सीधे डेटाबेस में भेज दिया। हालाँकि, इस प्रक्रिया के दौरान आपने कुछ अनदेखा कर दिया होगा।
username
पर वापस विचार करें तर्क जो आपने is_admin()
को दिया है . यह चर वास्तव में क्या दर्शाता है? आप मान सकते हैं कि username
केवल एक स्ट्रिंग है जो वास्तविक उपयोगकर्ता के नाम का प्रतिनिधित्व करती है। जैसा कि आप देखने वाले हैं, हालांकि, एक घुसपैठिया आसानी से इस तरह की निगरानी का फायदा उठा सकता है और पायथन एसक्यूएल इंजेक्शन करके बड़ा नुकसान पहुंचा सकता है।
यह जांचने का प्रयास करें कि निम्न उपयोगकर्ता एक व्यवस्थापक है या नहीं:
>>>>>> is_admin("'; select true; --")
True
रुको... अभी क्या हुआ?
आइए कार्यान्वयन पर एक और नज़र डालें। डेटाबेस में निष्पादित की जा रही वास्तविक क्वेरी का प्रिंट आउट लें:
>>>>>>> print("select admin from users where username = '%s'" % "'; select true; --")
select admin from users where username = ''; select true; --'
परिणामी पाठ में तीन कथन हैं। यह समझने के लिए कि पायथन एसक्यूएल इंजेक्शन कैसे काम करता है, आपको प्रत्येक भाग का अलग-अलग निरीक्षण करना होगा। पहला कथन इस प्रकार है:
select admin from users where username = '';
यह आपकी इच्छित क्वेरी है। अर्धविराम (;
) क्वेरी को समाप्त करता है, इसलिए इस क्वेरी का परिणाम कोई मायने नहीं रखता। अगला दूसरा कथन है:
select true;
इस बयान का निर्माण घुसपैठिए ने किया था। इसे हमेशा True
return लौटाने के लिए डिज़ाइन किया गया है ।
अंत में, आपको यह छोटा सा कोड दिखाई देता है:
--'
यह स्निपेट इसके बाद आने वाली किसी भी चीज़ को निष्क्रिय कर देता है। घुसपैठिए ने टिप्पणी चिह्न जोड़ा (--
) आखिरी प्लेसहोल्डर के बाद आपके द्वारा रखी गई हर चीज को एक टिप्पणी में बदलने के लिए।
जब आप इस तर्क के साथ फ़ंक्शन निष्पादित करते हैं, तो यह हमेशा True
लौटाएगा . यदि, उदाहरण के लिए, आप अपने लॉगिन पृष्ठ में इस फ़ंक्शन का उपयोग करते हैं, तो एक घुसपैठिया उपयोगकर्ता नाम '; select true; --
, और उन्हें पहुंच प्रदान की जाएगी।
अगर आपको लगता है कि यह बुरा है, तो यह और भी खराब हो सकता है! आपकी तालिका संरचना के ज्ञान वाले घुसपैठिए स्थायी क्षति के लिए पायथन एसक्यूएल इंजेक्शन का उपयोग कर सकते हैं। उदाहरण के लिए, घुसपैठिए डेटाबेस में जानकारी को बदलने के लिए अपडेट स्टेटमेंट को इंजेक्ट कर सकता है:
>>>>>> is_admin('haki')
False
>>> is_admin("'; update users set admin = 'true' where username = 'haki'; select true; --")
True
>>> is_admin('haki')
True
आइए इसे फिर से तोड़ें:
';
यह स्निपेट पिछले इंजेक्शन की तरह ही क्वेरी को समाप्त कर देता है। अगला कथन इस प्रकार है:
update users set admin = 'true' where username = 'haki';
यह अनुभाग admin
को अपडेट करता है करने के लिए True
उपयोगकर्ता के लिए haki
।
अंत में, यह कोड स्निपेट है:
select true; --
पिछले उदाहरण की तरह, यह टुकड़ा True
लौटाता है और उसके बाद आने वाली हर चीज़ पर टिप्पणी करता है।
यह बदतर क्यों है? ठीक है, अगर घुसपैठिया इस इनपुट के साथ फ़ंक्शन को निष्पादित करने का प्रबंधन करता है, तो उपयोगकर्ता haki
एक व्यवस्थापक बन जाएगा:
psycopgtest=# select * from users;
username | admin
----------+-------
ran | t
haki | t
(2 rows)
घुसपैठिए को अब हैक का उपयोग नहीं करना पड़ेगा। वे केवल यूज़रनेम haki
. के साथ लॉग इन कर सकते हैं . (यदि घुसपैठिया वास्तव में नुकसान पहुंचाना चाहते थे, तो वे DROP DATABASE
. भी जारी कर सकते थे आदेश।)
भूलने से पहले, haki
restore को पुनर्स्थापित करें वापस अपनी मूल स्थिति में:
psycopgtest=# update users set admin = false where username = 'haki';
UPDATE 1
तो, ऐसा क्यों हो रहा है? खैर, आप username
के बारे में क्या जानते हैं? बहस? आप जानते हैं कि यह उपयोगकर्ता नाम का प्रतिनिधित्व करने वाला एक स्ट्रिंग होना चाहिए, लेकिन आप वास्तव में इस दावे की जांच या लागू नहीं करते हैं। यह खतरनाक हो सकता है! जब हमलावर आपके सिस्टम को हैक करने का प्रयास करते हैं, तो ठीक यही होता है।
सुरक्षित क्वेरी पैरामीटर तैयार करना
पिछले अनुभाग में, आपने देखा कि कैसे एक घुसपैठिया आपके सिस्टम का फायदा उठा सकता है और सावधानीपूर्वक तैयार की गई स्ट्रिंग का उपयोग करके व्यवस्थापक अनुमतियां प्राप्त कर सकता है। मुद्दा यह था कि आपने क्लाइंट से पास किए गए मान को सीधे डेटाबेस में निष्पादित करने की अनुमति दी, बिना किसी प्रकार की जांच या सत्यापन किए। SQL इंजेक्शन इस प्रकार की भेद्यता पर निर्भर करते हैं।
किसी भी समय डेटाबेस क्वेरी में उपयोगकर्ता इनपुट का उपयोग किया जाता है, SQL इंजेक्शन के लिए संभावित भेद्यता होती है। पायथन एसक्यूएल इंजेक्शन को रोकने की कुंजी यह सुनिश्चित करना है कि मूल्य का उपयोग डेवलपर के रूप में किया जा रहा है। पिछले उदाहरण में, आपने username
. के लिए इरादा किया था एक स्ट्रिंग के रूप में उपयोग करने के लिए। वास्तव में, इसका उपयोग कच्चे SQL कथन के रूप में किया गया था।
यह सुनिश्चित करने के लिए कि मानों का उपयोग उनके इच्छित उद्देश्य के अनुसार किया जाता है, आपको बचने . की आवश्यकता है मूल्य। उदाहरण के लिए, घुसपैठियों को स्ट्रिंग तर्क के स्थान पर कच्चे SQL को इंजेक्ट करने से रोकने के लिए, आप उद्धरण चिह्नों से बच सकते हैं:
>>>>>> # BAD EXAMPLE. DON'T DO THIS!
>>> username = username.replace("'", "''")
यह तो केवल एक उदाहरण है। पायथन एसक्यूएल इंजेक्शन को रोकने की कोशिश करते समय सोचने के लिए बहुत सारे विशेष पात्र और परिदृश्य हैं। आपके लिए भाग्यशाली, आधुनिक डेटाबेस एडेप्टर, क्वेरी पैरामीटर का उपयोग करके Python SQL इंजेक्शन को रोकने के लिए अंतर्निहित टूल के साथ आते हैं . पैरामीटर के साथ एक क्वेरी लिखने के लिए इन्हें सादे स्ट्रिंग इंटरपोलेशन के बजाय उपयोग किया जाता है।
नोट: विभिन्न एडेप्टर, डेटाबेस और प्रोग्रामिंग भाषाएं अलग-अलग नामों से क्वेरी पैरामीटर को संदर्भित करती हैं। सामान्य नामों में बाइंड वैरिएबल . शामिल हैं , प्रतिस्थापन चर , और प्रतिस्थापन चर ।
अब जब आपको भेद्यता की बेहतर समझ है, तो आप स्ट्रिंग इंटरपोलेशन के बजाय क्वेरी पैरामीटर का उपयोग करके फ़ंक्शन को फिर से लिखने के लिए तैयार हैं:
1def is_admin(username: str) -> bool:
2 with connection.cursor() as cursor:
3 cursor.execute("""
4 SELECT
5 admin
6 FROM
7 users
8 WHERE
9 username = %(username)s
10 """, {
11 'username': username
12 })
13 result = cursor.fetchone()
14
15 if result is None:
16 # User does not exist
17 return False
18
19 admin, = result
20 return admin
यहाँ इस उदाहरण में क्या अलग है:
-
पंक्ति 9 में, आपने एक नामित पैरामीटर का उपयोग किया
username
यह इंगित करने के लिए कि उपयोगकर्ता नाम कहाँ जाना चाहिए। ध्यान दें कि कैसे पैरामीटरusername
अब एकल उद्धरण चिह्नों से घिरा नहीं है। -
पंक्ति 11 में, आपने
username
. का मान पारित कर दिया है दूसरे तर्क के रूप मेंcursor.execute()
. कनेक्शनusername
. के प्रकार और मान का उपयोग करेगा डेटाबेस में क्वेरी निष्पादित करते समय।
इस फ़ंक्शन का परीक्षण करने के लिए, पहले से खतरनाक स्ट्रिंग सहित कुछ मान्य और अमान्य मान आज़माएं:
>>>>>> is_admin('haki')
False
>>> is_admin('ran')
True
>>> is_admin('foo')
False
>>> is_admin("'; select true; --")
False
अद्भुत! फ़ंक्शन ने सभी मानों के लिए अपेक्षित परिणाम लौटाए। क्या अधिक है, खतरनाक स्ट्रिंग अब काम नहीं करती है। यह समझने के लिए कि क्यों, आप execute()
. द्वारा उत्पन्न क्वेरी का निरीक्षण कर सकते हैं :
>>> with connection.cursor() as cursor:
... cursor.execute("""
... SELECT
... admin
... FROM
... users
... WHERE
... username = %(username)s
... """, {
... 'username': "'; select true; --"
... })
... print(cursor.query.decode('utf-8'))
SELECT
admin
FROM
users
WHERE
username = '''; select true; --'
कनेक्शन ने username
. के मान का इलाज किया एक स्ट्रिंग के रूप में और किसी भी वर्ण से बच निकला जो स्ट्रिंग को समाप्त कर सकता है और पायथन एसक्यूएल इंजेक्शन पेश कर सकता है।
सुरक्षित क्वेरी पैरामीटर पास करना
डेटाबेस एडेप्टर आमतौर पर क्वेरी पैरामीटर पास करने के कई तरीके प्रदान करते हैं। नामित प्लेसहोल्डर आमतौर पर पठनीयता के लिए सर्वोत्तम होते हैं, लेकिन कुछ कार्यान्वयन अन्य विकल्पों का उपयोग करने से लाभान्वित हो सकते हैं।
आइए क्वेरी पैरामीटर का उपयोग करने के कुछ सही और गलत तरीकों पर एक नज़र डालें। निम्नलिखित कोड ब्लॉक उन प्रश्नों के प्रकार दिखाता है जिनसे आप बचना चाहते हैं:
# BAD EXAMPLES. DON'T DO THIS!
cursor.execute("SELECT admin FROM users WHERE username = '" + username + '");
cursor.execute("SELECT admin FROM users WHERE username = '%s' % username);
cursor.execute("SELECT admin FROM users WHERE username = '{}'".format(username));
cursor.execute(f"SELECT admin FROM users WHERE username = '{username}'");
इनमें से प्रत्येक कथन username
. से गुजरता है क्लाइंट से सीधे डेटाबेस में, बिना किसी प्रकार की जांच या सत्यापन किए। इस प्रकार का कोड Python SQL इंजेक्शन को आमंत्रित करने के लिए तैयार है।
इसके विपरीत, आपके लिए निष्पादित करने के लिए इस प्रकार की क्वेरी सुरक्षित होनी चाहिए:
# SAFE EXAMPLES. DO THIS!
cursor.execute("SELECT admin FROM users WHERE username = %s'", (username, ));
cursor.execute("SELECT admin FROM users WHERE username = %(username)s", {'username': username});
इन कथनों में, username
नामित पैरामीटर के रूप में पारित किया गया है। अब, डेटाबेस username
. के निर्दिष्ट प्रकार और मान का उपयोग करेगा क्वेरी निष्पादित करते समय, Python SQL इंजेक्शन से सुरक्षा प्रदान करते हुए।
एसक्यूएल संरचना का उपयोग करना
अब तक आपने अक्षर के लिए पैरामीटर का उपयोग किया है। शाब्दिक संख्याएँ, तार और दिनांक जैसे मान हैं। लेकिन क्या होगा यदि आपके पास एक उपयोग केस है जिसके लिए एक अलग क्वेरी लिखने की आवश्यकता है-एक जहां पैरामीटर कुछ और है, जैसे तालिका या कॉलम नाम?
पिछले उदाहरण से प्रेरित होकर, आइए एक फ़ंक्शन को लागू करें जो किसी तालिका के नाम को स्वीकार करता है और उस तालिका में पंक्तियों की संख्या लौटाता है:
# BAD EXAMPLE. DON'T DO THIS!
def count_rows(table_name: str) -> int:
with connection.cursor() as cursor:
cursor.execute("""
SELECT
count(*)
FROM
%(table_name)s
""", {
'table_name': table_name,
})
result = cursor.fetchone()
rowcount, = result
return rowcount
अपने उपयोगकर्ता तालिका पर फ़ंक्शन निष्पादित करने का प्रयास करें:
>>>Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in count_rows
psycopg2.errors.SyntaxError: syntax error at or near "'users'"
LINE 5: 'users'
^
आदेश SQL उत्पन्न करने में विफल रहा। जैसा कि आप पहले ही देख चुके हैं, डेटाबेस एडेप्टर चर को एक स्ट्रिंग या शाब्दिक के रूप में मानता है। एक टेबल नाम, हालांकि, एक सादा स्ट्रिंग नहीं है। यहीं पर SQL कंपोजिशन आता है।
आप पहले से ही जानते हैं कि SQL बनाने के लिए स्ट्रिंग इंटरपोलेशन का उपयोग करना सुरक्षित नहीं है। सौभाग्य से, Psycopg psycopg.sql
. नामक एक मॉड्यूल प्रदान करता है SQL क्वेरी को सुरक्षित रूप से लिखने में आपकी मदद करने के लिए। आइए psycopg.sql.SQL()
. का उपयोग करके फ़ंक्शन को फिर से लिखें :
from psycopg2 import sql
def count_rows(table_name: str) -> int:
with connection.cursor() as cursor:
stmt = sql.SQL("""
SELECT
count(*)
FROM
{table_name}
""").format(
table_name = sql.Identifier(table_name),
)
cursor.execute(stmt)
result = cursor.fetchone()
rowcount, = result
return rowcount
इस कार्यान्वयन में दो अंतर हैं। सबसे पहले, आपने sql.SQL()
. का इस्तेमाल किया क्वेरी बनाने के लिए। फिर, आपने sql.Identifier()
. का इस्तेमाल किया तर्क मान को एनोटेट करने के लिए table_name
. (एक पहचानकर्ता एक कॉलम या टेबल का नाम है।)
नोट: लोकप्रिय पैकेज के उपयोगकर्ता django-debug-toolbar
psycopg.sql.SQL()
. से बनी क्वेरी के लिए SQL पैनल में त्रुटि हो सकती है . संस्करण 2.0 में रिलीज के लिए एक सुधार की उम्मीद है।
अब, users
. पर फ़ंक्शन निष्पादित करने का प्रयास करें तालिका:
>>> count_rows('users')
2
महान! इसके बाद, देखते हैं कि क्या होता है जब तालिका मौजूद नहीं होती है:
>>>>>> count_rows('foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in count_rows
psycopg2.errors.UndefinedTable: relation "foo" does not exist
LINE 5: "foo"
^
फ़ंक्शन UndefinedTable
को फेंकता है अपवाद। निम्नलिखित चरणों में, आप इस अपवाद का उपयोग एक संकेत के रूप में करेंगे कि आपका फ़ंक्शन Python SQL इंजेक्शन हमले से सुरक्षित है।
नोट: अपवाद UndefinedTable
psycopg2 संस्करण 2.8 में जोड़ा गया था। यदि आप Psycopg के पुराने संस्करण के साथ काम कर रहे हैं, तो आपको एक अलग अपवाद मिलेगा।
यह सब एक साथ रखने के लिए, तालिका में पंक्तियों को एक निश्चित सीमा तक गिनने का विकल्प जोड़ें। यह सुविधा बहुत बड़ी तालिकाओं के लिए उपयोगी हो सकती है। इसे लागू करने के लिए, एक LIMIT
जोड़ें सीमा के मान के लिए क्वेरी पैरामीटर के साथ क्वेरी का खंड:
from psycopg2 import sql
def count_rows(table_name: str, limit: int) -> int:
with connection.cursor() as cursor:
stmt = sql.SQL("""
SELECT
COUNT(*)
FROM (
SELECT
1
FROM
{table_name}
LIMIT
{limit}
) AS limit_query
""").format(
table_name = sql.Identifier(table_name),
limit = sql.Literal(limit),
)
cursor.execute(stmt)
result = cursor.fetchone()
rowcount, = result
return rowcount
इस कोड ब्लॉक में, आपने limit
. को एनोटेट किया है sql.Literal()
. का उपयोग करना . पिछले उदाहरण की तरह, psycopg
सरल दृष्टिकोण का उपयोग करते समय सभी क्वेरी पैरामीटर को अक्षर के रूप में बाध्य करेगा। हालांकि, sql.SQL()
का उपयोग करते समय , आपको sql.Identifier()
. का उपयोग करके प्रत्येक पैरामीटर को स्पष्ट रूप से एनोटेट करना होगा या sql.Literal()
।
नोट: दुर्भाग्य से, पायथन एपीआई विनिर्देश पहचानकर्ताओं के बंधन को संबोधित नहीं करता है, केवल शाब्दिक। Psycopg एकमात्र लोकप्रिय एडेप्टर है जिसने शाब्दिक और पहचानकर्ता दोनों के साथ SQL को सुरक्षित रूप से लिखने की क्षमता को जोड़ा है। यह तथ्य पहचानकर्ताओं को बाध्य करते समय बारीकी से ध्यान देना और भी महत्वपूर्ण बनाता है।
यह सुनिश्चित करने के लिए फ़ंक्शन निष्पादित करें कि यह काम करता है:
>>>>>> count_rows('users', 1)
1
>>> count_rows('users', 10)
2
अब जब आप देख रहे हैं कि फ़ंक्शन काम कर रहा है, तो सुनिश्चित करें कि यह सुरक्षित भी है:
>>>>>> count_rows("(select 1) as foo; update users set admin = true where name = 'haki'; --", 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 18, in count_rows
psycopg2.errors.UndefinedTable: relation "(select 1) as foo; update users set admin = true where name = '" does not exist
LINE 8: "(select 1) as foo; update users set adm...
^
यह ट्रेसबैक दर्शाता है कि psycopg
मूल्य से बच निकला, और डेटाबेस ने इसे तालिका नाम के रूप में माना। चूंकि इस नाम वाली कोई तालिका मौजूद नहीं है, एक UndefinedTable
अपवाद उठाया गया था और आपको हैक नहीं किया गया था!
निष्कर्ष
आपने एक ऐसा फ़ंक्शन सफलतापूर्वक कार्यान्वित किया है जो गतिशील SQL को बिना बनाता है अपने सिस्टम को Python SQL इंजेक्शन के लिए जोखिम में डालना! आपने सुरक्षा से समझौता किए बिना अपनी क्वेरी में अक्षर और पहचानकर्ता दोनों का उपयोग किया है।
आपने सीखा:
- क्या पायथन SQL इंजेक्शन है और इसका उपयोग कैसे किया जा सकता है
- कैसे पायथन SQL इंजेक्शन को रोकें क्वेरी पैरामीटर का उपयोग करना
- कैसे सुरक्षित रूप से SQL कथन लिखें जो पैरामीटर के रूप में शाब्दिक और पहचानकर्ताओं का उपयोग करते हैं
अब आप ऐसे प्रोग्राम बनाने में सक्षम हैं जो बाहर के हमलों का सामना कर सकते हैं। आगे बढ़ो और हैकर्स को विफल करो!