คำตอบสั้น ๆ คือไม่ PDO ที่เตรียมไว้จะไม่ปกป้องคุณจากการโจมตี SQL-Injection ที่เป็นไปได้ทั้งหมด สำหรับขอบบางกรณีที่คลุมเครือ
ฉันกำลังปรับคำตอบนี้เพื่อพูดคุยเกี่ยวกับ PDO ...
คำตอบที่ยาวนั้นไม่ใช่เรื่องง่าย ก็ขึ้นอยู่การโจมตีแสดงให้เห็นที่นี่
การโจมตี
ดังนั้นเรามาเริ่มต้นด้วยการแสดงการโจมตี ...
$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หรือคำอื่น ๆที่ถูกต้อง- 'ของตัวละครตามมาด้วยการไม่ใช้ Escape- addslashes()แต่เราไม่ได้ใช้ ดังนั้นไปยังขั้นตอนต่อไป ...
 
- $ stmt-> รัน () - สิ่งสำคัญที่ต้องตระหนักที่นี่คือ PDO โดยค่าเริ่มต้นไม่ได้ทำงบเตรียมไว้จริง มันเลียนแบบพวกเขา (สำหรับ MySQL) ดังนั้น PDO จึงสร้างสตริงข้อความค้นหาเรียก- mysql_real_escape_string()(ฟังก์ชัน MySQL C API) ภายในแต่ละค่าสตริงที่ถูกผูกไว้
 - การเรียก 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 Prepared Statement ...
การแก้ไขง่าย ๆ
ตอนนี้ก็เป็นที่น่าสังเกตว่าคุณสามารถป้องกันสิ่งนี้ได้โดยการปิดการใช้งานคำสั่งจำลองที่เตรียมไว้
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
นี้จะมักจะส่งผลให้งบเตรียมความจริง (เช่นข้อมูลที่ถูกส่งผ่านในแพ็คเก็ตที่แยกต่างหากจากแบบสอบถาม) อย่างไรก็ตามโปรดทราบว่า PDO จะย้อนกลับไปเลียนแบบข้อความที่ MySQL ไม่สามารถเตรียมความพร้อมได้: ข้อความที่สามารถแสดงรายการในคู่มือได้ แต่ควรระวังในการเลือกรุ่นเซิร์ฟเวอร์ที่เหมาะสม)
การแก้ไขที่ถูกต้อง
นี่คือปัญหาที่เราไม่ได้เรียก C API เป็นแทนmysql_set_charset() SET NAMESถ้าเราทำเช่นนั้นเราก็จะถูกปรับถ้าเราใช้ 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ซึ่งควรใช้แทน SET NAMES ...
พระคุณประหยัด
ดังที่เราได้กล่าวไว้ในตอนต้นสำหรับการโจมตีครั้งนี้เพื่อการทำงานการเชื่อมต่อฐานข้อมูลจะต้องเข้ารหัสโดยใช้ชุดอักขระที่มีช่องโหว่  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 นี้ (แม้ว่าจะไม่ใช่ PDO)
ตัวอย่างที่ปลอดภัย
ตัวอย่างต่อไปนี้ปลอดภัย:
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, ฯลฯ ) และพารามิเตอร์ DSN charset ของ PDO (ใน PHP ≥ 5.3.6)
หรือ
- อย่าใช้ชุดอักขระที่มีช่องโหว่สำหรับการเข้ารหัสการเชื่อมต่อ (คุณใช้เพียงutf8/latin1/ascii/ ฯลฯ )
หรือ
- เปิดใช้งานNO_BACKSLASH_ESCAPESโหมด SQL
คุณปลอดภัย 100%
มิฉะนั้นคุณจะมีความเสี่ยงแม้ว่าคุณจะใช้งบเตรียม PDO ...
ภาคผนวก
ฉันทำงานอย่างช้าๆเพื่อแก้ไขค่าเริ่มต้นเพื่อไม่เลียนแบบการเตรียมการสำหรับ PHP เวอร์ชันในอนาคต ปัญหาที่ฉันพบคือการทดสอบจำนวนมากแตกสลายเมื่อฉันทำเช่นนั้น ปัญหาหนึ่งคือการเตรียมการจำลองที่จะโยนข้อผิดพลาดทางไวยากรณ์ในการดำเนินการ แต่การเตรียมการจริงจะโยนข้อผิดพลาดในการเตรียมการ เพื่อที่จะทำให้เกิดปัญหา (และเป็นส่วนหนึ่งของการทดสอบเหตุผลที่บอร์ก)