PDO มีการจัดทำงบที่เพียงพอเพื่อป้องกันการฉีด SQL หรือไม่?


661

สมมติว่าฉันมีรหัสเช่นนี้:

$dbh = new PDO("blahblah");

$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

เอกสาร PDO พูดว่า:

ไม่จำเป็นต้องอ้างพารามิเตอร์ของคำสั่งที่เตรียมไว้ คนขับจัดการกับมันให้คุณ

นั่นคือทั้งหมดที่ฉันต้องทำเพื่อหลีกเลี่ยงการฉีด SQL? มันง่ายจริงๆเหรอ?

คุณสามารถสมมติว่า MySQL ถ้ามันสร้างความแตกต่าง นอกจากนี้ฉันแค่อยากรู้เกี่ยวกับการใช้งบเตรียมกับการฉีด SQL ในบริบทนี้ฉันไม่สนใจ XSS หรือช่องโหว่อื่น ๆ


5
วิธีที่ดีกว่าอันดับ 7 คำตอบstackoverflow.com/questions/134099/…
NullPoiиteя

คำตอบ:


807

คำตอบสั้น ๆ คือไม่ 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 แถว มาวิเคราะห์กันว่าเกิดอะไรขึ้นที่นี่:

  1. การเลือกชุดตัวละคร

    $pdo->query('SET NAMES gbk');

    สำหรับการโจมตีการทำงานเราต้องเข้ารหัสที่เซิร์ฟเวอร์ของคาดหวังว่าในการเชื่อมต่อทั้งในการเข้ารหัส'ในขณะที่เช่น ASCII 0x27 และจะมีตัวละครบางคนที่มีไบต์สุดท้ายเป็น ASCII คือ\ 0x5cมันจะเปิดออกมี 5 การเข้ารหัสดังกล่าวได้รับการสนับสนุนใน MySQL 5.6 โดยค่าเริ่มต้น: big5, cp932, gb2312, และgbk sjisเราจะเลือกgbkที่นี่

    ตอนนี้มันสำคัญมากที่จะต้องสังเกตการใช้SET NAMESที่นี่ ชุดนี้ชุดอักขระบนเซิร์ฟเวอร์ มีวิธีทำอีกวิธีหนึ่ง แต่เราจะไปถึงที่นั่นเร็วพอ

  2. ส่วนของข้อมูล

    0xbf27น้ำหนักบรรทุกที่เรากำลังจะใช้สำหรับการฉีดนี้เริ่มต้นด้วยลำดับไบต์ ในgbkนั้นเป็นอักขระหลายไบต์ที่ไม่ถูกต้อง ในก็สตริงlatin1 ¿'โปรดสังเกตว่าในlatin1 และ gbk , 0x27ในตัวเองเป็นตัวอักษร'ตัวอักษร

    เราได้เลือกเพย์โหลดนี้เพราะถ้าเราเรียกaddslashes()มันเราจะใส่ ASCII \เช่น0x5cก่อน'ตัวละคร ดังนั้นเราจึงต้องการปิดท้ายด้วย0xbf5c27ซึ่งgbkเป็นลำดับสองตัวอักษร: ตามมาด้วย0xbf5c 0x27หรือคำอื่น ๆที่ถูกต้อง'ของตัวละครตามมาด้วยการไม่ใช้ Escape addslashes()แต่เราไม่ได้ใช้ ดังนั้นไปยังขั้นตอนต่อไป ...

  3. $ 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 / *

    ซึ่งเป็นสิ่งที่การโจมตีต้องการ

  4. คำค้นหา

    ส่วนนี้เป็นเพียงพิธีการ แต่นี่คือแบบสอบถามที่แสดงผล:

    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 เวอร์ชันในอนาคต ปัญหาที่ฉันพบคือการทดสอบจำนวนมากแตกสลายเมื่อฉันทำเช่นนั้น ปัญหาหนึ่งคือการเตรียมการจำลองที่จะโยนข้อผิดพลาดทางไวยากรณ์ในการดำเนินการ แต่การเตรียมการจริงจะโยนข้อผิดพลาดในการเตรียมการ เพื่อที่จะทำให้เกิดปัญหา (และเป็นส่วนหนึ่งของการทดสอบเหตุผลที่บอร์ก)


47
นี่คือคำตอบที่ดีที่สุดที่ฉันพบ .. คุณให้ลิงค์สำหรับการอ้างอิงเพิ่มเติมหรือไม่
StaticVariable

1
@nicogawenda: นั่นเป็นข้อผิดพลาดที่แตกต่างกัน ก่อนหน้า 5.0.22 mysql_real_escape_stringจะไม่จัดการกรณีที่การเชื่อมต่อถูกตั้งค่าเป็น BIG5 / GBK อย่างเหมาะสม ดังนั้นจริง ๆ แล้วแม้แต่เรียกmysql_set_charset()ใช้ mysql <5.0.22 จะเสี่ยงต่อข้อผิดพลาดนี้! ดังนั้นไม่โพสต์นี้ยังคงใช้งานได้กับ 5.0.22 (เนื่องจาก mysql_real_escape_string เป็นเพียงชุดอักขระสำหรับการโทรออกmysql_set_charset()เท่านั้นซึ่งเป็นสิ่งที่โพสต์นี้กำลังพูดถึงเกี่ยวกับการข้าม) ...
ircmaxell

1
@progfa ไม่ว่าคุณจะทำหรือไม่ควรตรวจสอบอินพุตของคุณบนเซิร์ฟเวอร์ก่อนทำอะไรกับข้อมูลผู้ใช้
Tek

2
โปรดทราบว่าNO_BACKSLASH_ESCAPESสามารถแนะนำช่องโหว่ใหม่ ๆ ได้เช่น: stackoverflow.com/a/23277864/1014813
lepix

2
@slevin "OR 1 = 1" เป็นตัวยึดตำแหน่งสำหรับทุกสิ่งที่คุณต้องการ ใช่มันกำลังค้นหาค่าในชื่อ แต่คิดว่าส่วน "OR 1 = 1" คือ "UNION SELECT * จากผู้ใช้" ตอนนี้คุณสามารถควบคุมการสืบค้นและเช่นนี้สามารถใช้งานในทางที่ผิดได้ ...
ircmaxell

515

คำสั่งที่เตรียมไว้ / การสืบค้นแบบแปรผันตามปกตินั้นเพียงพอสำหรับการป้องกันคำสั่งที่ 1 ในคำสั่งนั้น * หากคุณใช้ไดนามิก sql ที่ไม่ผ่านการตรวจสอบที่อื่นในแอปพลิเคชันของคุณคุณยังคงเสี่ยงต่อการฉีดลำดับที่สอง

การสั่งซื้อลำดับที่ 2 หมายถึงข้อมูลได้ถูกหมุนเวียนผ่านฐานข้อมูลหนึ่งครั้งก่อนที่จะถูกรวมไว้ในแบบสอบถามและเป็นการยากที่จะดึงออกมา AFAIK คุณแทบจะไม่เคยเห็นการโจมตีลำดับที่ 2 ที่มีการออกแบบจริง ๆ เพราะมันมักจะง่ายกว่าสำหรับผู้โจมตีที่จะเข้าสู่สังคมวิศวกร แต่ในบางครั้งคุณมีข้อผิดพลาดอันดับที่สองเกิดขึ้นเนื่องจาก'อักขระที่ไม่สุภาพหรือคล้ายกัน

คุณสามารถทำการโจมตีลำดับที่ 2 ให้สำเร็จเมื่อคุณสามารถทำให้ค่าถูกเก็บไว้ในฐานข้อมูลที่ใช้เป็นตัวอักษรในแบบสอบถามในภายหลัง ตัวอย่างเช่นสมมติว่าคุณป้อนข้อมูลต่อไปนี้เป็นชื่อผู้ใช้ใหม่ของคุณเมื่อสร้างบัญชีบนเว็บไซต์ (สมมติว่าฐานข้อมูล MySQL สำหรับคำถามนี้):

' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + '

หากไม่มีข้อ จำกัด อื่น ๆ เกี่ยวกับชื่อผู้ใช้คำสั่งที่เตรียมไว้จะยังคงตรวจสอบให้แน่ใจว่าแบบสอบถามแบบฝังตัวด้านบนไม่ทำงานในเวลาที่ใส่และเก็บค่าอย่างถูกต้องในฐานข้อมูล อย่างไรก็ตามลองนึกภาพว่าภายหลังแอปพลิเคชันจะดึงชื่อผู้ใช้ของคุณจากฐานข้อมูลและใช้การต่อสตริงเพื่อรวมค่านั้นของแบบสอบถามใหม่ คุณอาจเห็นรหัสผ่านของคนอื่น ตั้งแต่ชื่อแรก ๆ ในตารางผู้ใช้มีแนวโน้มที่จะเป็นผู้ดูแลระบบคุณอาจเพิ่งจะมอบฟาร์มให้ (โปรดทราบ: นี่เป็นอีกเหตุผลหนึ่งที่ไม่ควรเก็บรหัสผ่านเป็นข้อความธรรมดา!)

เราเห็นแล้วว่าข้อความที่เตรียมไว้นั้นเพียงพอสำหรับการสืบค้นเดียว แต่ด้วยตัวเองพวกเขาไม่เพียงพอที่จะป้องกันการโจมตีแบบ sql injection ตลอดทั้งแอพพลิเคชั่นเพราะพวกเขาไม่มีกลไกในการบังคับใช้การเข้าถึงฐานข้อมูลทั้งหมดภายในแอปพลิเคชัน รหัส. อย่างไรก็ตามใช้เป็นส่วนหนึ่งของการออกแบบแอปพลิเคชันที่ดีซึ่งอาจรวมถึงการปฏิบัติเช่นการตรวจสอบรหัสหรือการวิเคราะห์แบบคงที่หรือการใช้ ORM ชั้นข้อมูลหรือชั้นบริการที่ จำกัดงบ SQL แบบไดนามิกเป็นเครื่องมือหลักสำหรับการแก้ SQL Injection ปัญหา.หากคุณปฏิบัติตามหลักการออกแบบแอปพลิเคชันที่ดีเช่นการเข้าถึงข้อมูลของคุณจะถูกแยกออกจากส่วนที่เหลือของโปรแกรมของคุณมันจะกลายเป็นเรื่องง่ายที่จะบังคับใช้หรือตรวจสอบว่าแบบสอบถามทั้งหมดใช้การกำหนดพารามิเตอร์อย่างถูกต้อง ในกรณีนี้การฉีด sql (ลำดับที่หนึ่งและที่สอง) ได้รับการป้องกันอย่างสมบูรณ์


*ปรากฎว่า MySql / PHP เป็นเพียงแค่ตกลงเกี่ยวกับการจัดการพารามิเตอร์เมื่อตัวอักษรกว้างมีส่วนเกี่ยวข้องและยังมีกรณีที่หายากที่ระบุไว้ในคำตอบโหวตสูงอื่น ๆ ที่นี่ที่สามารถอนุญาตให้ฉีดผ่านพารามิเตอร์ สอบถาม


6
นั่นดูน่าสนใจ. ฉันไม่ทราบลำดับที่ 1 กับลำดับที่ 2 คุณช่วยอธิบายเพิ่มเติมอีกเล็กน้อยเกี่ยวกับการทำงานของคำสั่งที่สองได้หรือไม่?
Mark Biek

193
หากทุกข้อสงสัยของคุณถูกกำหนดไว้คุณจะได้รับการป้องกันจากการฉีดใบสั่งลำดับที่ 2 การสั่งซื้อครั้งแรกลืมว่าข้อมูลผู้ใช้ไม่น่าเชื่อถือ การสั่งซื้อลำดับที่ 2 นั้นลืมว่าข้อมูลในฐานข้อมูลนั้นไม่น่าเชื่อถือ (เพราะมาจากผู้ใช้เดิม)
cjm

6
ขอบคุณ cjm ฉันพบว่าบทความนี้มีประโยชน์ในการอธิบายการฉีดลำดับที่ 2: codeproject.com/KB/database/SqlInjectionAttacks.aspx
Mark Biek

49
อ่าใช่ แต่สิ่งที่เกี่ยวกับการฉีดเพื่อที่สาม ต้องระวังสิ่งเหล่านั้น
troelskn

81
@troelskn ว่าจะต้องเป็นนักพัฒนาที่เป็นแหล่งที่มาของข้อมูลที่ไม่น่าไว้วางใจ
MikeMurko

45

ไม่พวกเขาไม่ได้เสมอ

ขึ้นอยู่กับว่าคุณอนุญาตให้ผู้ใช้ป้อนข้อมูลภายในแบบสอบถามหรือไม่ ตัวอย่างเช่น:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

จะมีความเสี่ยงต่อการแทรกซึมของ SQL และการใช้คำสั่งที่เตรียมไว้ในตัวอย่างนี้จะไม่ทำงานเนื่องจากอินพุตของผู้ใช้ถูกใช้เป็นตัวระบุไม่ใช่เป็นข้อมูล คำตอบที่ถูกต้องที่นี่คือการใช้การกรอง / การตรวจสอบเช่น:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];
$allowedTables = array('users','admins','moderators');
if (!in_array($tableToUse,$allowedTables))    
 $tableToUse = 'users';

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

หมายเหตุ: คุณไม่สามารถใช้ PDO เพื่อผูกข้อมูลที่อยู่นอก DDL (Data Definition Language) นั่นคือไม่ได้ผล:

$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');

เหตุผลดังกล่าวข้างต้นไม่ทำงานเป็นเพราะDESCและASCไม่ได้ข้อมูล PDO สามารถหลบหนีได้สำหรับข้อมูลเท่านั้น ประการที่สองคุณไม่สามารถใส่'เครื่องหมายคำพูดล้อมรอบมันได้ วิธีเดียวที่จะช่วยให้ผู้ได้รับการแต่งตั้งเรียงลำดับคือการกรองด้วยตนเองและตรวจสอบว่ามันเป็นอย่างใดอย่างหนึ่งหรือDESCASC


11
ฉันขาดอะไรบางอย่างที่นี่ แต่ไม่ใช่จุดรวมของคำสั่งที่เตรียมไว้เพื่อหลีกเลี่ยงการปฏิบัติต่อ sql เหมือนสตริงหรือไม่? จะไม่เหมือนกับ $ dbh-> เตรียมพร้อม ('เลือก * จาก: tableToUse โดยที่ชื่อผู้ใช้ =: ชื่อผู้ใช้'); รับปัญหาของคุณ?
Rob Forrest

4
@ RobForrest ใช่คุณหายไป :) ข้อมูลที่คุณผูกไว้ใช้งานได้กับ DDL (Data Definition Language) เท่านั้น คุณต้องการคำพูดเหล่านั้นและการหลบหนีที่เหมาะสม การวางเครื่องหมายคำพูดสำหรับส่วนอื่น ๆ ของแบบสอบถามจะแยกความน่าจะเป็นสูง ตัวอย่างเช่นSELECT * FROM 'table'อาจผิดได้อย่างที่ควรจะเป็นSELECT * FROM `table`หรือไม่มีแผ่นรองหลัง จากนั้นบางสิ่งเช่นORDER BY DESCที่DESCมาจากผู้ใช้จะไม่สามารถหลบหนีได้อย่างง่ายดาย ดังนั้นสถานการณ์จริงจึงไม่ จำกัด
หอคอย

8
ฉันสงสัยว่าคน 6 คนสามารถเอาชนะความคิดเห็นที่เสนอการใช้คำสั่งที่เตรียมไว้อย่างไม่ถูกต้องได้อย่างไร หากพวกเขาลองอีกครั้งพวกเขาจะค้นพบได้ทันทีว่าการใช้พารามิเตอร์ที่มีชื่อแทนชื่อตารางจะไม่ทำงาน
Félix Gagnon-Grenier

นี่คือบทช่วยสอนที่ยอดเยี่ยมเกี่ยวกับ PDO ถ้าคุณต้องการเรียนรู้ a2znotes.blogspot.in/2014/09/introduction-to-pdo.html
RN Kushwaha

11
คุณไม่ควรใช้สตริงข้อความค้นหา / เนื้อความ POST เพื่อเลือกตารางที่จะใช้ หากคุณไม่มีโมเดลอย่างน้อยใช้ a switchเพื่อดูชื่อตาราง
ZiggyTheHamster

29

ใช่มันเพียงพอแล้ว วิธีการทำงานของการฉีดแบบฉีดเป็นการรับล่าม (ฐานข้อมูล) เพื่อประเมินบางสิ่งซึ่งควรเป็นข้อมูลราวกับว่ามันเป็นรหัส สิ่งนี้เป็นไปได้เฉพาะเมื่อคุณผสมรหัสและข้อมูลในสื่อเดียวกัน (เช่นเมื่อคุณสร้างแบบสอบถามเป็นสตริง)

แบบสอบถามแบบ Parameterised ทำงานโดยการส่งรหัสและข้อมูลแยกกันดังนั้นจึงไม่สามารถหารูในนั้นได้

คุณยังคงเสี่ยงต่อการถูกโจมตีแบบฉีดอื่น ๆ ตัวอย่างเช่นหากคุณใช้ข้อมูลในหน้า HTML คุณอาจถูกโจมตีจากประเภท XSS


10
"ไม่เคย" เป็นวิธีที่เกินความจริงไปจนถึงจุดที่ทำให้เข้าใจผิด หากคุณใช้งบที่เตรียมไว้อย่างไม่ถูกต้องมันก็ไม่ได้ดีไปกว่าการไม่ใช้งบเลย (แน่นอนว่า "คำสั่งที่เตรียมไว้" ซึ่งมีการป้อนข้อมูลของผู้ใช้ที่แทรกเข้ามามันทำลายวัตถุประสงค์ ... แต่ฉันได้เห็นจริงแล้วและคำสั่งที่เตรียมไว้ไม่สามารถจัดการตัวระบุ (ชื่อตาราง ฯลฯ ) เป็นพารามิเตอร์ได้) เพิ่ม ดังนั้นไดร์เวอร์ PDO บางตัวจึงเลียนแบบคำสั่งที่เตรียมไว้และมีที่ว่างให้พวกเขาทำอย่างไม่ถูกต้อง (ตัวอย่างเช่นโดยการแยกวิเคราะห์ SQL แบบครึ่งภาษา) เวอร์ชั่นสั้น: อย่าคิดเลยว่ามันง่าย
cHao

29

ไม่นี้ไม่เพียงพอ (ในบางกรณี)! ตามค่าเริ่มต้น PDO จะใช้คำสั่งที่จำลองไว้แล้วเมื่อใช้ MySQL เป็นไดรเวอร์ฐานข้อมูล คุณควรปิดใช้งานคำสั่งที่จำลองไว้เสมอเมื่อใช้ MySQL และ PDO:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

อีกสิ่งที่ควรทำเสมอคือตั้งค่าการเข้ารหัสที่ถูกต้องของฐานข้อมูล:

$dbh = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

ดูคำถามที่เกี่ยวข้องเช่นกัน: ฉันจะป้องกันการฉีด SQL ใน PHP ได้อย่างไร

นอกจากนี้โปรดทราบว่าเฉพาะเกี่ยวกับด้านฐานข้อมูลของสิ่งที่คุณจะต้องดูด้วยตัวคุณเองเมื่อแสดงข้อมูล เช่นโดยใช้htmlspecialchars()อีกครั้งพร้อมการเข้ารหัสและสไตล์การอ้างอิงที่ถูกต้อง


14

โดยส่วนตัวแล้วฉันมักจะเรียกใช้การสุขาภิบาลบางรูปแบบกับข้อมูลก่อนเพราะคุณไม่สามารถเชื่อถือการป้อนข้อมูลของผู้ใช้ได้อย่างไรก็ตามเมื่อใช้ตัวยึด / พารามิเตอร์การเชื่อมโยงข้อมูลที่ป้อนเข้าจะถูกส่งไปยังเซิร์ฟเวอร์แยกต่างหากไปยังคำสั่ง sql กุญแจสำคัญในที่นี้คือสิ่งนี้จะผูกข้อมูลที่ให้ไว้กับประเภทที่เฉพาะเจาะจงและการใช้งานเฉพาะและกำจัดโอกาสใด ๆ ในการเปลี่ยนตรรกะของคำสั่ง SQL


1

Eaven ถ้าคุณจะป้องกัน sql injection front-end, โดยใช้ html หรือ js checks, คุณต้องพิจารณาว่า front-end checks นั้นเป็น "bypassable"

คุณสามารถปิดการใช้งาน js หรือแก้ไขรูปแบบด้วยเครื่องมือพัฒนา front-end (สร้างขึ้นด้วย firefox หรือ chrome ทุกวันนี้)

ดังนั้นเพื่อป้องกันการฉีด SQL จะมีสิทธิ์ในการฆ่าเชื้อแบ็กเอนด์วันที่อินพุตภายในตัวควบคุมของคุณ

ฉันอยากจะแนะนำให้คุณใช้ filter_input () ฟังก์ชั่น PHP พื้นเมืองเพื่อล้างค่า GET และ INPUT

หากคุณต้องการความปลอดภัยสำหรับการสืบค้นฐานข้อมูลที่เหมาะสมฉันขอแนะนำให้คุณใช้นิพจน์ทั่วไปเพื่อตรวจสอบรูปแบบข้อมูล preg_match () จะช่วยคุณในกรณีนี้! แต่ระวัง! เครื่องยนต์ Regex ไม่เบา ใช้เมื่อจำเป็นเท่านั้นมิฉะนั้นประสิทธิภาพของแอปพลิเคชันของคุณจะลดลง

ความปลอดภัยมีค่าใช้จ่าย แต่อย่าเสียประสิทธิภาพของคุณ!

ตัวอย่างง่าย ๆ :

ถ้าคุณต้องการตรวจสอบอีกครั้งว่าค่าที่ได้รับจาก GET เป็นตัวเลขน้อยกว่า 99 ถ้า (! preg_match ('/ [0-9] {1,2} /')) {... } นั้นหนักกว่า

if (isset($value) && intval($value)) <99) {...}

ดังนั้นคำตอบสุดท้ายคือ: "ไม่! งบเตรียม PDO ไม่ได้ป้องกันการฉีด sql ทุกชนิด"; มันไม่ได้ป้องกันค่าที่ไม่ได้คาดไว้


5
คุณกำลังสับสนกับการฉีด SQL กับสิ่งอื่นซึ่งทำให้คำตอบของคุณไม่เกี่ยวข้องอย่างสมบูรณ์
Your Common Sense
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.