คำตอบสั้น ๆ คือใช่มีวิธีที่จะmysql_real_escape_string()แก้ไข
สำหรับกรณี EDBSURE มากขอบ !!!
คำตอบที่ยาวนั้นไม่ใช่เรื่องง่าย ก็ขึ้นอยู่การโจมตีแสดงให้เห็นที่นี่
การโจมตี
ดังนั้นเรามาเริ่มต้นด้วยการแสดงการโจมตี ...
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');
สำหรับการโจมตีการทำงานเราต้องเข้ารหัสที่เซิร์ฟเวอร์ของคาดหวังว่าในการเชื่อมต่อทั้งในการเข้ารหัส'ในขณะที่เช่น ASCII 0x27 และจะมีตัวละครบางคนที่มีไบต์สุดท้ายเป็น ASCII คือ\ 0x5cมันจะเปิดออกมี 5 การเข้ารหัสดังกล่าวได้รับการสนับสนุนใน MySQL 5.6 โดยค่าเริ่มต้น: big5, cp932, gb2312, และgbk sjisเราจะเลือกgbkที่นี่
ตอนนี้มันสำคัญมากที่จะต้องสังเกตการใช้SET NAMESที่นี่ ชุดนี้ชุดอักขระบนเซิร์ฟเวอร์ ถ้าเราใช้การเรียกฟังก์ชัน C API mysql_set_charset()เราก็ไม่เป็นไร (ในรุ่น MySQL ตั้งแต่ปี 2549) แต่เพิ่มเติมเกี่ยวกับสาเหตุในไม่กี่นาที ...
ส่วนของข้อมูล
0xbf27น้ำหนักบรรทุกที่เรากำลังจะใช้สำหรับการฉีดนี้เริ่มต้นด้วยลำดับไบต์ ในgbkนั้นเป็นอักขระหลายไบต์ที่ไม่ถูกต้อง ในก็สตริงlatin1 ¿'โปรดสังเกตว่าในlatin1 และ gbk , 0x27ในตัวเองเป็นตัวอักษร'ตัวอักษร
เราได้เลือกเพย์โหลดนี้เพราะถ้าเราเรียกaddslashes()มันเราจะใส่ ASCII \เช่น0x5cก่อน'ตัวละคร ดังนั้นเราจึงต้องการปิดท้ายด้วย0xbf5c27ซึ่งgbkเป็นลำดับสองตัวอักษร: ตามมาด้วย0xbf5c 0x27หรือคำอื่น ๆที่ถูกต้อง'ของตัวละครตามมาด้วยการไม่ใช้ Escape addslashes()แต่เราไม่ได้ใช้ ดังนั้นไปยังขั้นตอนต่อไป ...
mysql_real_escape_string ()
การเรียก 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
ขอแสดงความยินดีคุณเพิ่งประสบความสำเร็จในการโจมตีโปรแกรมโดยใช้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 Basic Multilingual Planeทั้งหมด
หรือคุณสามารถเปิดใช้งานNO_BACKSLASH_ESCAPESโหมด SQL ซึ่ง (ในหมู่สิ่งอื่น ๆ ) alters 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, ฯลฯ ) และ
mysql_set_charset() / $mysqli->set_charset()/ PDO ของพารามิเตอร์ DSN charset (ใน PHP ≥ 5.3.6)
หรือ
- อย่าใช้ชุดอักขระที่มีช่องโหว่สำหรับการเข้ารหัสการเชื่อมต่อ (คุณใช้เพียง
utf8/ latin1/ ascii/ ฯลฯ )
คุณปลอดภัย 100%
ไม่อย่างนั้นคุณก็มีช่องโหว่ถึงแม้ว่าคุณจะใช้mysql_real_escape_string() ...