การฉีด SQL ที่อยู่รอบ ๆ mysql_real_escape_string ()
มีความเป็นไปได้ในการฉีด SQL แม้ว่าจะใช้mysql_real_escape_string()
ฟังก์ชันหรือไม่?
พิจารณาสถานการณ์ตัวอย่างนี้ SQL ถูกสร้างขึ้นใน PHP ดังนี้:
$login = mysql_real_escape_string(GetFromPost('login')); $password = mysql_real_escape_string(GetFromPost('password'));
$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";
ฉันเคยได้ยินหลายคนพูดกับฉันว่ารหัสแบบนั้นยังคงอันตรายและสามารถแฮ็คได้แม้จะmysql_real_escape_string()
ใช้ฟังก์ชัน แต่ฉันไม่สามารถคิดหาประโยชน์ใด ๆ ที่เป็นไปได้?
การฉีดแบบคลาสสิกเช่นนี้:
aaa' OR 1=1 --
ไม่ทำงาน.
คุณรู้หรือไม่ว่ามีการฉีดเข้าไปในโค้ด PHP ด้านบนหรือไม่?
คำตอบ
พิจารณาแบบสอบถามต่อไปนี้:
$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";
mysql_real_escape_string()
จะไม่ปกป้องคุณจากสิ่งนี้ การที่คุณใช้เครื่องหมายคำพูดเดี่ยว ( ' '
) รอบ ๆ ตัวแปรภายในข้อความค้นหาของคุณคือสิ่งที่ช่วยป้องกันคุณจากสิ่งนี้ ต่อไปนี้เป็นตัวเลือก:
$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
คำตอบสั้น ๆใช่ใช่มีวิธีการที่จะได้รับรอบ mysql_real_escape_string()
# สำหรับเคส EDGE OBSCURE มาก !!!
คำตอบยาว ๆ ไม่ใช่เรื่องง่าย ก็ขึ้นอยู่การโจมตีแสดงให้เห็นที่นี่
การโจมตี
เริ่มจากการแสดงการโจมตี ...
mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*"); mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
ในบางสถานการณ์ระบบจะส่งคืนมากกว่า 1 แถว มาดูว่าเกิดอะไรขึ้นที่นี่:
การเลือกชุดอักขระ
mysql_query('SET NAMES gbk');
สำหรับการโจมตีการทำงานเราต้องเข้ารหัสที่เซิร์ฟเวอร์ของคาดหวังว่าในการเชื่อมต่อทั้งในการเข้ารหัส
'
ในขณะที่เช่น ASCII0x27
และจะมีตัวละครบางคนที่มีไบต์สุดท้ายเป็น ASCII คือ\
0x5c
มันจะเปิดออกมี 5 การเข้ารหัสดังกล่าวได้รับการสนับสนุนใน MySQL 5.6 โดยค่าเริ่มต้น:big5
,cp932
,gb2312
, และgbk
sjis
เราจะเลือกgbk
ที่นี่ตอนนี้สิ่งสำคัญมากที่จะต้องสังเกตการใช้
SET NAMES
ที่นี่ ชุดนี้ชุดอักขระบนเซิร์ฟเวอร์ หากเราใช้การเรียกใช้ฟังก์ชัน C APImysql_set_charset()
เราก็สบายดี (ในรุ่น MySQL ตั้งแต่ปี 2549) แต่เพิ่มเติมว่าทำไมในอีกไม่กี่นาที ...Payload
0xbf27
น้ำหนักบรรทุกที่เรากำลังจะใช้สำหรับการฉีดนี้เริ่มต้นด้วยลำดับไบต์ ในgbk
ที่ตัวอักษรสัญลักษณ์ไม่ถูกต้อง ในก็สตริงlatin1
¿'
โปรดสังเกตว่าในlatin1
และgbk
,0x27
ในตัวเองเป็นตัวอักษร'
ตัวอักษรเราได้เลือกน้ำหนักบรรทุกนี้เพราะถ้าเราเรียก
addslashes()
มันว่าเราต้องการแทรก ASCII\
คือ0x5c
ก่อนที่'
ตัวละคร ดังนั้นเราจึงต้องการปิดท้ายด้วย0xbf5c27
ซึ่งgbk
เป็นลำดับสองตัวอักษร: ตามมาด้วย0xbf5c
0x27
หรือคำอื่น ๆที่ถูกต้อง'
ของตัวละครตามมาด้วยการไม่ใช้ Escapeaddslashes()
แต่เราไม่ได้ใช้ ไปยังขั้นตอนต่อไป ...mysql_real_escape_string ()
การเรียก C API
mysql_real_escape_string()
แตกต่างจากตรงaddslashes()
ที่ทราบชุดอักขระการเชื่อมต่อ ดังนั้นจึงสามารถดำเนินการ Escape ได้อย่างเหมาะสมสำหรับชุดอักขระที่เซิร์ฟเวอร์คาดหวัง อย่างไรก็ตามจนถึงจุดนี้ลูกค้าคิดว่าเรายังคงใช้latin1
การเชื่อมต่ออยู่เพราะเราไม่เคยบอกเป็นอย่างอื่น เราไม่ได้บอกเซิร์ฟเวอร์ที่เรากำลังใช้gbk
แต่ลูกค้าlatin1
ยังคงคิดว่ามันเป็นดังนั้นการเรียกให้
mysql_real_escape_string()
แทรกแบ็กสแลชและเรามี'
อักขระที่แขวนอยู่ฟรีในเนื้อหา "Escape" ของเรา! ในความเป็นจริงถ้าเราดู$var
ในgbk
ชุดอักขระเราจะเห็น:縗 'หรือ 1 = 1 / *
ซึ่งเป็นสิ่งที่การโจมตีต้องการ
แบบสอบถาม
ส่วนนี้เป็นเพียงพิธีการ แต่นี่คือแบบสอบถามที่แสดงผล:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
ขอแสดงความยินดีคุณเพิ่งโจมตีโปรแกรมสำเร็จโดยใช้mysql_real_escape_string()
...
เลว
มันแย่ลง PDO
ค่าเริ่มต้นเป็นการเลียนแบบคำสั่งที่เตรียมไว้ด้วย MySQL นั่นหมายความว่าในฝั่งไคลเอ็นต์โดยทั่วไปจะทำการ sprintf ผ่านmysql_real_escape_string()
(ในไลบรารี C) ซึ่งหมายความว่าสิ่งต่อไปนี้จะส่งผลให้การฉีดสำเร็จ:
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
ตอนนี้เป็นที่น่าสังเกตว่าคุณสามารถป้องกันสิ่งนี้ได้โดยปิดใช้งานคำสั่งที่เลียนแบบเตรียมไว้:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
นี้จะมักจะส่งผลให้งบเตรียมความจริง (เช่นข้อมูลที่ถูกส่งผ่านในแพ็คเก็ตที่แยกต่างหากจากแบบสอบถาม) อย่างไรก็ตามทราบว่า PDO จะเงียบทางเลือกที่จะลอกเลียนแบบงบที่ MySQL ไม่สามารถเตรียมความพร้อมโดยกำเนิด: ผู้ที่จะสามารถมีการระบุไว้ในคู่มือ แต่ระวังเพื่อเลือกรุ่นของเซิร์ฟเวอร์ที่เหมาะสม)
น่าเกลียด
ผมบอกว่าที่จุดเริ่มต้นมากที่เราอาจมีการป้องกันทั้งหมดนี้ถ้าเราได้นำมาใช้แทนmysql_set_charset('gbk')
SET NAMES gbk
และนั่นเป็นความจริงหากคุณใช้ MySQL รุ่นตั้งแต่ปี 2549
หากคุณกำลังใช้รุ่น MySQL ก่อนหน้านี้แล้วข้อผิดพลาดในmysql_real_escape_string()
ความหมายว่าตัวอักษรสัญลักษณ์ที่ไม่ถูกต้องเช่นผู้ที่อยู่ในส่วนของข้อมูลของเราได้รับการรักษาเป็นไบต์เดียวสำหรับวัตถุประสงค์ในการหลบหนีแม้กระทั่งในกรณีที่ลูกค้าได้รับแจ้งอย่างถูกต้องของการเข้ารหัสการเชื่อมต่อและการโจมตีครั้งนี้จะ ยังคงประสบความสำเร็จ ข้อผิดพลาดได้รับการแก้ไขใน MySQL 4.1.20 , 5.0.22และ5.1.11
แต่ส่วนที่แย่ที่สุดคือPDO
ไม่เปิดเผย C API mysql_set_charset()
จนถึง 5.3.6 ดังนั้นในเวอร์ชันก่อนหน้าจึงไม่สามารถป้องกันการโจมตีนี้ได้สำหรับทุกคำสั่งที่เป็นไปได้! มันสัมผัสในขณะนี้เป็นพารามิเตอร์ DSN
พระคุณแห่งการออม
ดังที่เราได้กล่าวไว้ในตอนต้นเพื่อให้การโจมตีนี้ทำงานได้การเชื่อมต่อฐานข้อมูลจะต้องเข้ารหัสโดยใช้ชุดอักขระที่มีช่องโหว่ utf8mb4คือไม่เสี่ยงและยังสามารถรองรับทุกอักขระ Unicode: เพื่อให้คุณสามารถเลือกที่จะใช้ที่แทน แต่จะได้รับเพียงมีตั้งแต่ MySQL 5.5.3 ทางเลือกคือutf8ซึ่งยังไม่เสี่ยงและสามารถรองรับทั้งของ Unicode สื่อสารได้หลายภาษาเครื่องบินขั้นพื้นฐาน
หรือคุณสามารถเปิดใช้งานNO_BACKSLASH_ESCAPESโหมด SQL ซึ่ง (เหนือสิ่งอื่นใด) จะเปลี่ยนแปลงการทำงานของmysql_real_escape_string()
. เมื่อเปิดใช้งานโหมดนี้0x27
จะถูกแทนที่ด้วย0x2727
มากกว่า0x5c27
ดังนั้นกระบวนการ Escape จึงไม่สามารถสร้างอักขระที่ถูกต้องในการเข้ารหัสที่มีช่องโหว่ซึ่งไม่มีอยู่ก่อนหน้านี้ (เช่น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 ทั้งหมด ฯลฯ ) และ
mysql_set_charset()
/$mysqli->set_charset()
/ / / / / / / PDO ของพารามิเตอร์ชุดอักขระ DSN (ใน PHP ≥ 5.3.6)
หรือ
- อย่าใช้ชุดอักขระที่มีช่องโหว่ในการเข้ารหัสการเชื่อมต่อ (คุณใช้เฉพาะ
utf8
/latin1
/ascii
/ etc)
คุณปลอดภัย 100%
มิฉะนั้นคุณจะเสี่ยงแม้ว่าคุณจะใช้mysql_real_escape_string()
...
TL; ดร
mysql_real_escape_string()
จะไม่ให้การป้องกันใด ๆ (และยังสามารถโกงข้อมูลของคุณได้อีก) หาก:
NO_BACKSLASH_ESCAPESเปิดใช้งานโหมด SQL ของ MySQL (ซึ่งอาจเป็นไปได้เว้นแต่คุณจะเลือกโหมด SQL อื่นอย่างชัดเจนทุกครั้งที่เชื่อมต่อ ) และ
ลิเทอรัลสตริง SQL ของคุณถูกยกมาโดยใช้
"
อักขระอัญประกาศสิ่งนี้ถูกยื่นเป็นข้อบกพร่อง # 72458และได้รับการแก้ไขแล้วใน MySQL v5.7.6 (ดูหัวข้อ " The Saving Grace " ด้านล่าง)
นี่คืออีกอัน (อาจจะน้อยกว่านี้?) EDGE CASE ที่คลุมเครือ !!!
เพื่อเป็นการแสดงความเคารพต่อคำตอบที่ยอดเยี่ยมของ @ ircmaxell (จริงๆแล้วนี่ควรจะเป็นการเยินยอและไม่ใช่การลอกเลียนแบบ!) ฉันจะใช้รูปแบบของเขา:
การโจมตี
เริ่มต้นด้วยการสาธิต ...
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- '); mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
สิ่งนี้จะส่งคืนระเบียนทั้งหมดจากtest
ตาราง การผ่า:
การเลือกโหมด SQL
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
ตามที่ระบุไว้ในString Literals :
มีหลายวิธีในการรวมอักขระเครื่องหมายคำพูดไว้ในสตริง:
“
'
” ในสตริงที่ยกมาด้วย“'
” อาจเขียนว่า“''
”“
"
” ในสตริงที่ยกมาด้วย“"
” อาจเขียนว่า“""
”นำหน้าอักขระเครื่องหมายคำพูดด้วยอักขระหลีก (“
\
”)"
'
" ภายในสตริงที่มีเครื่องหมาย ""
" ไม่จำเป็นต้องได้รับการดูแลเป็นพิเศษและไม่จำเป็นต้องเพิ่มเป็นสองเท่าหรือหลีกหนี ในทำนองเดียวกัน ""
" ในสตริงที่มีเครื่องหมาย "'
" ไม่จำเป็นต้องได้รับการดูแลเป็นพิเศษ
หากโหมด SQL ของเซิร์ฟเวอร์มีNO_BACKSLASH_ESCAPESตัวเลือกที่สามซึ่งเป็นแนวทางปกติที่นำมาใช้
mysql_real_escape_string()
- จะไม่สามารถใช้งานได้: ต้องใช้หนึ่งในสองตัวเลือกแรกแทน โปรดทราบว่าผลกระทบของสัญลักษณ์แสดงหัวข้อย่อยที่สี่คือเราจำเป็นต้องทราบอักขระที่จะใช้ในการอ้างอิงตามตัวอักษรเพื่อหลีกเลี่ยงการบดบังข้อมูลPayload
" OR 1=1 --
น้ำหนักบรรทุกเริ่มต้นการฉีดนี้ด้วย
"
อักขระอย่างแท้จริง ไม่มีการเข้ารหัสโดยเฉพาะ ไม่มีอักขระพิเศษ ไม่มีไบต์แปลก ๆmysql_real_escape_string ()
$var = mysql_real_escape_string('" OR 1=1 -- ');
โชคดีที่
mysql_real_escape_string()
ตรวจสอบโหมด SQL และปรับพฤติกรรมให้เหมาะสม ดูlibmysql.c:ulong STDCALL mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, ulong length) { if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) return escape_quotes_for_mysql(mysql->charset, to, 0, from, length); return escape_string_for_mysql(mysql->charset, to, 0, from, length); }
ดังนั้นฟังก์ชันพื้นฐานที่แตกต่างกัน
escape_quotes_for_mysql()
จึงถูกเรียกใช้หากใช้NO_BACKSLASH_ESCAPES
โหมด SQL ดังที่ได้กล่าวไว้ข้างต้นฟังก์ชันดังกล่าวจำเป็นต้องทราบว่าจะใช้อักขระใดในการอ้างอิงตัวอักษรเพื่อที่จะทำซ้ำโดยไม่ทำให้อักขระอัญประกาศอื่นซ้ำตามตัวอักษรอย่างไรก็ตามฟังก์ชันนี้จะอนุมานโดยพลการว่าสตริงจะถูกยกมาโดยใช้
'
อักขระอัญประกาศเดี่ยว ดูcharset.c:/* Escape apostrophes by doubling them up // [ deletia 839-845 ] DESCRIPTION This escapes the contents of a string by doubling up any apostrophes that it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in effect on the server. // [ deletia 852-858 ] */ size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info, char *to, size_t to_length, const char *from, size_t length) { // [ deletia 865-892 ] if (*from == '\'') { if (to + 2 > to_end) { overflow= TRUE; break; } *to++= '\''; *to++= '\''; }
ดังนั้นจึงปล่อยให้
"
อักขระอัญประกาศคู่โดยไม่ถูกแตะต้อง (และเพิ่มเป็นสองเท่าของ'
อักขระอัญประกาศเดี่ยวทั้งหมด) โดยไม่คำนึงถึงอักขระจริงที่ใช้ในการอ้างอิงตัวอักษร ! ในกรณีของเรา$var
ยังคงอยู่ตรงเช่นเดียวกับการโต้แย้งที่ถูกจัดให้ไปmysql_real_escape_string()
-IT ราวกับว่าไม่มีหนีได้ที่สถานที่ทั้งหมดแบบสอบถาม
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
สิ่งที่เป็นทางการแบบสอบถามที่แสดงผลคือ:
SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1
ตามที่เพื่อนเรียนของฉันพูดไว้: ขอแสดงความยินดีคุณเพิ่งประสบความสำเร็จในการโจมตีโปรแกรมโดยใช้mysql_real_escape_string()
...
เลว
mysql_set_charset()ไม่สามารถช่วยได้เนื่องจากไม่มีส่วนเกี่ยวข้องกับชุดอักขระ และไม่สามารถทำได้mysqli::real_escape_string()เนื่องจากเป็นเพียงกระดาษห่อหุ้มที่แตกต่างกันในฟังก์ชันเดียวกันนี้
ปัญหาหากยังไม่ชัดเจนก็คือการโทรไปmysql_real_escape_string()
ยังไม่สามารถทราบได้ว่าอักขระใดจะถูกยกมาตามตัวอักษรเนื่องจากจะปล่อยให้นักพัฒนาตัดสินใจในภายหลัง ดังนั้นในNO_BACKSLASH_ESCAPES
โหมดจึงไม่มีวิธีใดที่ฟังก์ชันนี้จะสามารถหลบหนีทุกอินพุตได้อย่างปลอดภัยเพื่อใช้กับการอ้างอิงโดยพลการ (อย่างน้อยก็ไม่ต้องเพิ่มอักขระสองเท่าที่ไม่ต้องการการเพิ่มเป็นสองเท่าและทำให้ข้อมูลของคุณเต็มไปด้วย)
น่าเกลียด
มันแย่ลง NO_BACKSLASH_ESCAPES
อาจไม่ใช่เรื่องแปลกทั้งหมดเนื่องจากความจำเป็นในการใช้งานเพื่อความเข้ากันได้กับ SQL มาตรฐาน (เช่นดูส่วน 5.3 ของข้อกำหนดSQL-92ได้แก่ การสร้าง<quote symbol> ::= <quote><quote>
ไวยากรณ์และการขาดความหมายพิเศษใด ๆ ที่กำหนดให้กับแบ็กสแลช) นอกจากนี้แนะนำให้ใช้อย่างชัดเจนเพื่อเป็นวิธีแก้ปัญหาสำหรับข้อบกพร่อง (แก้ไขนานแล้ว) ที่โพสต์ของ ircmaxell อธิบายไว้ ใครจะรู้บ้างว่า DBA บางตัวอาจกำหนดค่าให้เปิดโดยค่าเริ่มต้นเนื่องจากเป็นการกีดกันการใช้วิธีการหลีกเลี่ยงที่ไม่ถูกต้องเช่นaddslashes().
นอกจากนี้โหมด SQL ของการเชื่อมต่อใหม่ยังถูกกำหนดโดยเซิร์ฟเวอร์ตามการกำหนดค่า (ซึ่งSUPER
ผู้ใช้สามารถเปลี่ยนแปลงได้ตลอดเวลา) จึงจะต้องมีบางอย่างของพฤติกรรมของเซิร์ฟเวอร์คุณต้องเสมอระบุอย่างชัดเจนโหมดที่ต้องการของคุณหลังจากที่เชื่อมต่อ
พระคุณแห่งการออม
ตราบเท่าที่คุณตั้งค่าโหมด SQL อย่างชัดเจนเสมอไม่ให้รวมNO_BACKSLASH_ESCAPES
หรืออ้างตัวอักษรสตริง MySQL โดยใช้อักขระอัญประกาศเดี่ยวข้อผิดพลาดนี้ไม่สามารถอยู่ด้านหลังส่วนหัวที่น่าเกลียดได้: escape_quotes_for_mysql()
จะไม่ใช้ตามลำดับหรือสมมติฐานเกี่ยวกับอักขระเครื่องหมายคำพูดที่ต้องการการทำซ้ำ ถูกต้อง.
ด้วยเหตุนี้ฉันขอแนะนำให้ทุกคนที่ใช้โหมดNO_BACKSLASH_ESCAPES
เปิดANSI_QUOTESใช้งานด้วยเนื่องจากจะบังคับให้ใช้ตัวอักษรสตริงที่มีเครื่องหมายอัญประกาศเดี่ยวเป็นนิสัย โปรดทราบว่าการดำเนินการนี้ไม่ได้ป้องกันการแทรก SQL ในกรณีที่มีการใช้ตัวอักษรที่มีเครื่องหมายคำพูดซ้ำซ้อนซึ่งเป็นเพียงการลดโอกาสที่จะเกิดขึ้นเท่านั้น (เนื่องจากการสืบค้นปกติที่ไม่เป็นอันตรายจะล้มเหลว)
ใน PDO ทั้งฟังก์ชันที่เทียบเท่ากันPDO::quote()และโปรแกรมจำลองคำสั่งที่เตรียมไว้จะเรียกใช้mysql_handle_quoter()- ซึ่งทำเช่นนี้ทุกประการ: ช่วยให้มั่นใจได้ว่าลิเทอรัลที่หลบหนีถูกยกมาในเครื่องหมายอัญประกาศเดี่ยวดังนั้นคุณจึงมั่นใจได้ว่า PDO จะได้รับการป้องกันจากข้อบกพร่องนี้เสมอ
สำหรับ MySQL v5.7.6 ข้อบกพร่องนี้ได้รับการแก้ไขแล้ว ดูบันทึกการเปลี่ยนแปลง :
เพิ่มหรือเปลี่ยนแปลงฟังก์ชันการทำงาน
การเปลี่ยนแปลงที่เข้ากันไม่ได้:มีการนำฟังก์ชัน C API ใหม่mysql_real_escape_string_quote()มาใช้แทนmysql_real_escape_string()เนื่องจากฟังก์ชันหลังอาจไม่สามารถเข้ารหัสอักขระได้อย่างถูกต้องเมื่อNO_BACKSLASH_ESCAPESเปิดใช้งานโหมด SQL ในกรณีนี้mysql_real_escape_string()ไม่สามารถหลีกเลี่ยงอักขระเครื่องหมายคำพูดได้ยกเว้นโดยการเพิ่มเป็นสองเท่าและเพื่อให้ทำได้อย่างถูกต้องต้องทราบข้อมูลเพิ่มเติมเกี่ยวกับบริบทการอ้างอิงมากกว่าที่มีอยู่ mysql_real_escape_string_quote()ใช้อาร์กิวเมนต์พิเศษสำหรับระบุบริบทการอ้างอิง สำหรับรายละเอียดการใช้งานดู mysql_real_escape_string_quote ()
บันทึก
ควรแก้ไขแอปพลิเคชันเพื่อใช้mysql_real_escape_string_quote()แทนmysql_real_escape_string()ซึ่งตอนนี้ล้มเหลวและเกิดCR_INSECURE_API_ERRข้อผิดพลาดหากNO_BACKSLASH_ESCAPESเปิดใช้งาน
อ้างอิง: ดูข้อผิดพลาด # 19211994 ด้วย
ตัวอย่างที่ปลอดภัย
เมื่อนำมารวมกับข้อบกพร่องที่อธิบายโดย ircmaxell ตัวอย่างต่อไปนี้ปลอดภัยทั้งหมด (สมมติว่ามีการใช้ MySQL ที่ช้ากว่า 4.1.20, 5.0.22, 5.1.11 หรือไม่ได้ใช้การเข้ารหัสการเชื่อมต่อ GBK / Big5) :
mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*'); mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
... เพราะเราได้เลือกโหมด SQL NO_BACKSLASH_ESCAPES
อย่างชัดเจนที่ไม่รวม
mysql_set_charset($charset); $var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
... เพราะเรากำลังอ้างถึงลิเทอรัลสตริงของเราด้วยเครื่องหมายอัญประกาศเดี่ยว
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $stmt->execute(["' OR 1=1 /*"]);
... เนื่องจากคำสั่ง PDO ที่เตรียมไว้นั้นไม่ได้รับผลกระทบจากช่องโหว่นี้ (และของ ircmaxell ก็เช่นกันหากคุณใช้PHP≥5.3.6และชุดอักขระได้รับการตั้งค่าอย่างถูกต้องใน DSN หรือการจำลองคำสั่งที่เตรียมไว้ถูกปิดใช้งาน) .
$var = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");
... เนื่องจากquote()
ฟังก์ชั่นของ PDO ไม่เพียง แต่หลีกหนีลิเทอรัลเท่านั้น แต่ยังใส่เครื่องหมายคำพูด (ใน'
อักขระเครื่องหมายคำพูดเดี่ยว) โปรดทราบว่าเพื่อหลีกเลี่ยงข้อผิดพลาดของ ircmaxell ในกรณีนี้คุณต้องใช้PHP≥5.3.6 และตั้งค่าอักขระใน DSN ให้ถูกต้อง
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
... เพราะ MySQLi งบที่เตรียมไว้ปลอดภัย
ห่อ
ดังนั้นหากคุณ:
- ใช้งบที่เตรียมไว้
หรือ
- ใช้ MySQL v5.7.6 หรือใหม่กว่า
หรือ
ในนอกจากนี้เพื่อการหนึ่งของการแก้ปัญหาในการสรุป ircmaxell ของการใช้งานที่หนึ่งอย่างน้อย:
- PDO;
- ลิเทอรัลสตริงที่ยกมาเดี่ยว หรือ
- โหมด SQL ที่ตั้งค่าอย่างชัดเจนซึ่งไม่รวม
NO_BACKSLASH_ESCAPES
... คุณควรปลอดภัยอย่างสมบูรณ์ (ช่องโหว่ที่อยู่นอกขอบเขตของสตริงที่หลุดออกไปด้านข้าง)
ไม่มีอะไรที่จะผ่านสิ่งนั้นไปได้นอกจาก%
สัญลักษณ์แทน อาจเป็นอันตรายหากคุณใช้LIKE
คำสั่งเนื่องจากผู้โจมตีสามารถใส่ได้%
เหมือนกับการเข้าสู่ระบบหากคุณไม่กรองสิ่งนั้นออกและจะต้องปิดกั้นรหัสผ่านของผู้ใช้ของคุณ ผู้คนมักแนะนำให้ใช้ข้อความที่เตรียมไว้เพื่อให้ปลอดภัย 100% เนื่องจากข้อมูลไม่สามารถรบกวนการสืบค้นได้ด้วยวิธีนั้น แต่สำหรับคำถามง่ายๆเช่นนี้อาจมีประสิทธิภาพมากกว่าที่จะทำบางสิ่งเช่น$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);