संक्षिप्त उत्तर नहीं है , पीडीओ की तैयारी आपको हर संभव SQL-Injection हमलों से बचाव नहीं करेगी। कुछ अस्पष्ट किनारे के मामलों के लिए।
मैं पीडीओ के बारे में बात करने के लिए इस उत्तर को स्वीकार कर रहा हूं ...
लंबा जवाब इतना आसान नहीं है। यह यहां प्रदर्शित एक हमले पर आधारित है ।
हमला
तो, आइए हम हमले को दिखाते हुए शुरू करें ...
$pdo->query('SET NAMES gbk');
$var = "\xbf\x27 OR 1=1 /*";
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));
कुछ परिस्थितियों में, यह 1 से अधिक पंक्ति में लौटेगा। चलो यहाँ क्या चल रहा है:
कैरेक्टर सेट का चयन करना
$pdo->query('SET NAMES gbk');
काम करने के लिए इस हमले के लिए, हम एन्कोडिंग की जरूरत है कि सर्वर के दोनों एनकोड करने के लिए कनेक्शन पर उम्मीद '
ASCII यानी के रूप में 0x27
और कुछ चरित्र जिसका अंतिम बाइट एक ASCII है के लिए \
यानी 0x5c
। यह पता चला है के रूप में, 5 ऐसे डिफ़ॉल्ट रूप से MySQL 5.6 में समर्थित एनकोडिंग हैं: big5
, cp932
, gb2312
, gbk
और sjis
। हम gbk
यहाँ का चयन करेंगे ।
अब, SET NAMES
यहां के उपयोग पर ध्यान देना बहुत महत्वपूर्ण है। यह सर्वर पर सेट चरित्र सेट करता है । इसे करने का एक और तरीका है, लेकिन हम जल्द ही वहां पहुंचेंगे।
पेलोड
इस इंजेक्शन के लिए हम जिस पेलोड का उपयोग करने जा रहे हैं, वह बाइट अनुक्रम से शुरू होता है 0xbf27
। में gbk
, यह एक अमान्य मल्टीबाइट चरित्र है; में latin1
, यह स्ट्रिंग है ¿'
। ध्यान दें कि में latin1
और gbk
, 0x27
पर अपने आप ही एक शाब्दिक है '
चरित्र।
हमने इस पेलोड को चुना है, क्योंकि अगर हमने इस addslashes()
पर कॉल किया , तो हम चरित्र से पहले एक ASCII \
यानी सम्मिलित करेंगे । तो हम एक साथ एक दो चरित्र अनुक्रम है , जिसके साथ हवा होगी : द्वारा पीछा किया । या दूसरे शब्दों में, एक वैध चरित्र जिसके बाद एक गैर-पंजीकृत व्यक्ति आता है । लेकिन हम उपयोग नहीं कर रहे हैं । तो अगले कदम पर ...0x5c
'
0xbf5c27
gbk
0xbf5c
0x27
'
addslashes()
$ Stmt-> निष्पादित ()
यहां महसूस करने के लिए महत्वपूर्ण बात यह है कि डिफ़ॉल्ट रूप से पीडीओ सही तैयार बयान नहीं करता है। यह उनका (MySQL के लिए) अनुकरण करता है। इसलिए, PDO आंतरिक रूप से क्वेरी स्ट्रिंग, mysql_real_escape_string()
प्रत्येक बाध्य स्ट्रिंग मान पर (MySQL C API फ़ंक्शन) कॉल करता है।
सी एपीआई कॉल mysql_real_escape_string()
इससे अलग addslashes()
है कि यह कनेक्शन वर्ण सेट को जानता है। तो यह सर्वर सेट की उम्मीद कर रहे चरित्र सेट के लिए भागने का ठीक से प्रदर्शन कर सकता है। हालाँकि, इस बिंदु तक, क्लाइंट को लगता है कि हम अभी भी latin1
कनेक्शन के लिए उपयोग कर रहे हैं , क्योंकि हमने इसे अन्यथा कभी नहीं बताया। हमने उस सर्वर को बताया जिसका हम उपयोग कर रहे हैं gbk
, लेकिन ग्राहक अभी भी सोचता है latin1
।
इसलिए कॉल mysql_real_escape_string()
बैकस्लैश सम्मिलित करता है, और '
हमारी "बच गई" सामग्री में एक मुफ़्त लटका हुआ चरित्र है! वास्तव में, अगर हमें चरित्र सेट $var
में देखना था gbk
, तो हम देखेंगे:
= 'या 1 = 1 / *
जो वास्तव में हमले की आवश्यकता है।
पूछताछ
यह हिस्सा सिर्फ एक औपचारिकता है, लेकिन यहां प्रस्तुत किया गया प्रश्न है:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
बधाई हो, आपने पीडीओ तैयार तैयारियों का उपयोग करते हुए एक कार्यक्रम पर सफलतापूर्वक हमला किया ...
द सिंपल फिक्स
अब, यह ध्यान देने योग्य है कि आप तैयार किए गए कथनों को अक्षम करके इसे रोक सकते हैं:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
यह आमतौर पर एक सच्चे तैयार कथन (यानी डेटा को क्वेरी से एक अलग पैकेट में भेजा जा रहा है) के परिणामस्वरूप होगा । हालाँकि, इस बात से अवगत रहें कि पीडीओ उन बयानों का अनुकरण करने के लिए चुपचाप वापस आ जाएगा, जो MySQL मूल रूप से तैयार नहीं कर सकते हैं: जिन्हें यह मैनुअल में सूचीबद्ध किया गया है, लेकिन उचित सर्वर संस्करण का चयन करने के लिए सावधान रहें)।
सही सुधार
यहाँ समस्या यह है कि हमने mysql_set_charset()
इसके बजाय C API को कॉल नहीं किया SET NAMES
। यदि हमने किया, तो हम ठीक हो जाएंगे, क्योंकि हम 2006 से एक MySQL रिलीज का उपयोग कर रहे हैं।
आप पहले के एक MySQL रिलीज, तो एक प्रयोग कर रहे हैं बग में mysql_real_escape_string()
मतलब है कि इस तरह के हमारे पेलोड में उन लोगों के रूप में अवैध multibyte वर्ण प्रयोजनों से बचने के लिए एक बाइट के रूप में इलाज किया गया है, भले ही ग्राहक को सही ढंग से कनेक्शन एन्कोडिंग के बारे में सूचित कर दिया गया था और इसलिए इस हमले होगा अभी भी सफल हैं। बग MySQL 4.1.20 , 5.0.22 और 5.1.11 में तय किया गया था ।
लेकिन सबसे बुरी बात यह है कि 5.3.6 तक PDO
सी एपीआई को उजागर नहीं किया गया mysql_set_charset()
, इसलिए पूर्व संस्करणों में यह हर संभव आदेश के लिए इस हमले को रोक नहीं सकता है! यह अब DSN पैरामीटर के रूप में सामने आया है , जिसका उपयोग इसके बजाय किया जाना चाहिए SET NAMES
...
द सेविंग ग्रेस
जैसा कि हमने शुरू में कहा, इस हमले के लिए डेटाबेस कनेक्शन को काम करने के लिए एक कमजोर चरित्र सेट का उपयोग करके एन्कोड किया जाना चाहिए। utf8mb4
है न कमजोर अभी तक समर्थन कर सकते हैं और हर ताकि आप MySQL 5.5.3 के बाद से है कि उपलब्ध बजाय-लेकिन यह केवल किया गया है का उपयोग करने का चयन कर सकते हैं: यूनिकोड वर्ण। एक विकल्प है utf8
, जो भी असुरक्षित नहीं है और पूरे यूनिकोड बेसिक बहुभाषी विमान का समर्थन कर सकता है ।
वैकल्पिक रूप से, आप NO_BACKSLASH_ESCAPES
SQL मोड को सक्षम कर सकते हैं , जो (अन्य चीजों के बीच) के संचालन को बदल देता है mysql_real_escape_string()
। सक्षम किए गए इस मोड के साथ, 0x27
इसके 0x2727
बजाय प्रतिस्थापित किया जाएगा 0x5c27
और इस प्रकार भागने की प्रक्रिया किसी भी असुरक्षित एन्कोडिंग में मान्य वर्ण नहीं बना सकती है जहां वे पहले मौजूद नहीं थे (यानी 0xbf27
अभी भी 0xbf27
आदि।) - इसलिए सर्वर अभी भी स्ट्रिंग को अस्वीकार कर देगा। । हालाँकि, एक अलग भेद्यता के लिए @ eggyal का उत्तर देखें जो इस SQL मोड का उपयोग करने से उत्पन्न हो सकता है (यद्यपि पीडीओ के साथ नहीं)।
सुरक्षित उदाहरण
निम्नलिखित उदाहरण सुरक्षित हैं:
mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
क्योंकि सर्वर की उम्मीद है utf8
...
mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
क्योंकि हमने कैरेक्टर सेट को ठीक से सेट किया है इसलिए क्लाइंट और सर्वर मैच करते हैं।
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
क्योंकि हम तैयार बयानों का अनुकरण कर चुके हैं।
$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
क्योंकि हमने कैरेक्टर सेट ठीक से सेट किया है।
$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
क्योंकि MySQLi हर समय सच तैयार स्टेटमेंट्स करता है।
समेट रहा हु
अगर तुम:
- MySQL के आधुनिक संस्करणों (5.1, सभी 5.5, 5.6, आदि) और PDO के DSN चारसेट पैरामीटर (PHP SQL 5.3.6 में) का उपयोग करें
या
- कनेक्शन एन्कोडिंग के लिए एक कमजोर वर्ण सेट का उपयोग न करें (आप केवल
utf8
/ latin1
/ ascii
/ etc का उपयोग करें )
या
NO_BACKSLASH_ESCAPES
SQL मोड सक्षम करें
आप 100% सुरक्षित हैं।
अन्यथा, आप पीडीओ तैयार स्टेटमेंट का उपयोग करते हुए भी असुरक्षित हैं ...
परिशिष्ट
मैं धीरे-धीरे एक पैच पर काम कर रहा हूं ताकि PHP के भविष्य के संस्करण के लिए तैयारी का अनुकरण न करने के लिए डिफ़ॉल्ट को बदल सके। समस्या यह है कि मैं भाग रहा हूं कि जब मैं ऐसा करता हूं तो बहुत सारे परीक्षण टूट जाते हैं। एक समस्या यह है कि उत्सर्जित तैयारी केवल निष्पादन पर वाक्यविन्यास त्रुटियों को फेंक देगी, लेकिन सच्ची तैयारी त्रुटियों को तैयारी पर फेंक देगी। ताकि समस्याएँ पैदा हो सकें (और कारण यह है कि परीक्षण बोरिंग हैं)।