คำตอบสั้น ๆ คือใช่มีวิธีที่จะ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()
...