htmlspecialchars และ mysql_real_escape_string ทำให้โค้ด PHP ของฉันปลอดภัยจากการแทรกหรือไม่


116

คำถามในวันนี้ก่อนหน้านี้ถูกถามเกี่ยวกับกลยุทธ์การเข้าตรวจสอบในเว็บแอป

คำตอบด้านบนในขณะที่เขียนแสดงให้เห็นในPHPเพียงแค่ใช้และhtmlspecialcharsmysql_real_escape_string

คำถามของฉันคือเพียงพอหรือไม่? มีอะไรมากกว่านี้ที่เราควรรู้? ฟังก์ชันเหล่านี้พังตรงไหน?

คำตอบ:


241

เมื่อพูดถึงการสืบค้นฐานข้อมูลให้พยายามใช้แบบสอบถามที่กำหนดพารามิเตอร์ไว้เสมอ mysqliและPDOห้องสมุดสนับสนุนเรื่องนี้ ปลอดภัยกว่าการใช้ฟังก์ชัน Escape อย่างไม่มีที่สิ้นสุดเช่นmysql_real_escape_string.

ใช่mysql_real_escape_stringเป็นเพียงฟังก์ชันหนีสตริงเท่านั้น มันไม่ใช่กระสุนวิเศษ สิ่งที่ต้องทำคือหลีกเลี่ยงอักขระอันตรายเพื่อให้สามารถใช้ในสตริงการสืบค้นเดียวได้อย่างปลอดภัย อย่างไรก็ตามหากคุณไม่ทำความสะอาดอินพุตของคุณล่วงหน้าคุณจะเสี่ยงต่อเวกเตอร์การโจมตีบางอย่าง

ลองนึกภาพ SQL ต่อไปนี้:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

คุณควรจะเห็นว่าสิ่งนี้เสี่ยงต่อการถูกใช้ประโยชน์
ลองนึกภาพidพารามิเตอร์ที่มีเวกเตอร์การโจมตีทั่วไป:

1 OR 1=1

ไม่มีตัวอักษรที่มีความเสี่ยงในการเข้ารหัสดังนั้นมันจะผ่านตัวกรองการหลบหนีโดยตรง ออกจากเรา:

SELECT fields FROM table WHERE id= 1 OR 1=1

ซึ่งเป็นเวกเตอร์การฉีด SQL ที่น่ารักและอนุญาตให้ผู้โจมตีส่งคืนแถวทั้งหมด หรือ

1 or is_admin=1 order by id limit 1

ซึ่งผลิต

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

ซึ่งช่วยให้ผู้โจมตีสามารถส่งคืนรายละเอียดของผู้ดูแลระบบรายแรกในตัวอย่างสมมตินี้ได้อย่างสมบูรณ์

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

สำหรับhtmlspecialchars(). นั่นเป็นที่วางทุ่นระเบิดของตัวเอง

มีปัญหาที่แท้จริงใน PHP เนื่องจากมีฟังก์ชันการหลบหนีที่เกี่ยวข้องกับ html ที่แตกต่างกันและไม่มีคำแนะนำที่ชัดเจนว่าฟังก์ชันใดทำหน้าที่อะไร

ประการแรกหากคุณอยู่ในแท็ก HTML แสดงว่าคุณประสบปัญหาอย่างแท้จริง ดูที่

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

เราอยู่ในแท็ก HTML แล้วดังนั้นเราจึงไม่จำเป็นต้อง <หรือ> ทำอะไรที่เป็นอันตราย เวกเตอร์การโจมตีของเราอาจเป็นได้javascript:alert(document.cookie)

ตอนนี้ HTML ผลลัพธ์ดูเหมือน

<img src= "javascript:alert(document.cookie)" />

การโจมตีผ่านเข้าไปโดยตรง

มันแย่ลง ทำไม? เพราะhtmlspecialchars(เมื่อเรียกแบบนี้) เข้ารหัสเฉพาะอัญประกาศคู่และไม่เข้ารหัสเดี่ยว ดังนั้นถ้าเรามี

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

ผู้โจมตีที่ชั่วร้ายของเราสามารถฉีดพารามิเตอร์ใหม่ทั้งหมดได้แล้ว

pic.png' onclick='location.href=xxx' onmouseover='...

ให้เรา

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

ในกรณีเหล่านี้ไม่มีสัญลักษณ์แสดงหัวข้อย่อยเวทย์มนตร์คุณเพียงแค่ต้องปรับแต่งอินพุตด้วยตัวคุณเอง หากคุณพยายามกรองตัวละครที่ไม่ดีออกไปคุณจะต้องล้มเหลวอย่างแน่นอน ใช้วิธีการอนุญาตพิเศษและปล่อยผ่านตัวอักษรที่ดีเท่านั้น ดูตารางสรุปXSSสำหรับตัวอย่างว่าเวกเตอร์มีความหลากหลายได้อย่างไร

แม้ว่าคุณจะใช้htmlspecialchars($string)นอกแท็ก HTML คุณก็ยังเสี่ยงต่อเวกเตอร์การโจมตีแบบชาร์ตแบบหลายไบต์

ประสิทธิภาพสูงสุดที่คุณสามารถทำได้คือการใช้การรวมกันของ mb_convert_encoding และ htmlentities ดังนี้

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

แม้สิ่งนี้จะทำให้ IE6 มีช่องโหว่เนื่องจากวิธีจัดการกับ UTF อย่างไรก็ตามคุณสามารถเปลี่ยนกลับไปใช้การเข้ารหัสที่ จำกัด มากขึ้นเช่น ISO-8859-1 ได้จนกว่าการใช้งาน IE6 จะลดลง

สำหรับการศึกษาเชิงลึกเพิ่มเติมเกี่ยวกับปัญหาหลายไบต์โปรดดูที่https://stackoverflow.com/a/12118602/1820


24
สิ่งเดียวที่พลาดไปที่นี่คือตัวอย่างแรกสำหรับแบบสอบถาม DB ... intval ง่าย ๆ () จะแก้ปัญหาการฉีด ใช้ intval () แทน mysqlescape ... () เสมอเมื่อต้องการตัวเลขไม่ใช่สตริง
Robert K

11
และโปรดจำไว้ว่าการใช้การสืบค้นแบบกำหนดพารามิเตอร์จะช่วยให้คุณได้รับข้อมูลที่ถือว่าเป็นข้อมูลไม่ใช่รหัส ใช้ไลบรารีเช่น PDO และใช้เคียวรีที่กำหนดพารามิเตอร์เมื่อทำได้
Cheekysoft

9
ข้อสังเกตสองประการ: 1. ในตัวอย่างแรกคุณจะปลอดภัยถ้าคุณใส่เครื่องหมายคำพูดไว้รอบ ๆ พารามิเตอร์เช่น$result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2. ในกรณีที่สอง (แอตทริบิวต์ที่มี URL) ไม่มีประโยชน์ใด ๆhtmlspecialcharsเลย ในกรณีเหล่านี้คุณควรเข้ารหัสอินพุตโดยใช้รูปแบบการเข้ารหัส URL เช่นการใช้rawurlencode. ด้วยวิธีนี้ผู้ใช้ไม่สามารถแทรกjavascript:และคณะได้
Marcel Korpel

7
“htmlspecialchars เพียงถอดรหัสคำพูดสองและไม่ได้คนเดียว” นั่นไม่เป็นความจริงก็ขึ้นอยู่กับธงเป็นชุดที่ดูของพารามิเตอร์
Marcel Korpel

2
สิ่งนี้ควรเป็นตัวหนา: Take a whitelist approach and only let through the chars which are good.บัญชีดำมักจะพลาดบางสิ่งบางอย่างไป +1
Jo Smo

10

นอกจากคำตอบที่ยอดเยี่ยมของ Cheekysoft แล้ว:

  • ใช่มันจะทำให้คุณปลอดภัย แต่ถ้าใช้อย่างถูกต้องเท่านั้น ใช้อย่างไม่ถูกต้องและคุณจะยังคงเสี่ยงและอาจมีปัญหาอื่น ๆ (เช่นข้อมูลเสียหาย)
  • โปรดใช้การสืบค้นที่กำหนดพารามิเตอร์แทน (ตามที่ระบุไว้ข้างต้น) คุณสามารถใช้ผ่านเช่น PDO หรือผ่านเครื่องห่อเช่น PEAR DB
  • ตรวจสอบให้แน่ใจว่า magic_quotes_gpc และ magic_quotes_runtime ปิดอยู่ตลอดเวลาและอย่าเปิดโดยไม่ได้ตั้งใจแม้แต่ในช่วงสั้น ๆ นี่เป็นความพยายามในช่วงต้นและเข้าใจผิดอย่างลึกซึ้งโดยนักพัฒนาของ PHP เพื่อป้องกันปัญหาด้านความปลอดภัย (ซึ่งทำลายข้อมูล)

ไม่มีสัญลักษณ์แสดงหัวข้อย่อยสีเงินสำหรับการป้องกันการแทรก HTML (เช่นการเขียนสคริปต์ข้ามไซต์) แต่คุณอาจทำได้ง่ายขึ้นหากคุณใช้ไลบรารีหรือระบบเทมเพลตเพื่อส่งออก HTML อ่านเอกสารสำหรับวิธีการหลบหนีอย่างเหมาะสม

ใน HTML สิ่งที่ต้องหลีกเลี่ยงแตกต่างกันขึ้นอยู่กับบริบท โดยเฉพาะอย่างยิ่งกับสตริงที่วางไว้ใน Javascript


3

ฉันเห็นด้วยกับโพสต์ข้างต้นอย่างแน่นอน แต่ฉันมีสิ่งเล็กน้อยที่จะตอบกลับคำตอบของ Cheekysoft โดยเฉพาะ:

เมื่อพูดถึงการสืบค้นฐานข้อมูลให้พยายามใช้แบบสอบถามที่กำหนดพารามิเตอร์ไว้เสมอ ไลบรารี mysqli และ PDO รองรับสิ่งนี้ สิ่งนี้ปลอดภัยกว่าการใช้ฟังก์ชัน Escape เช่น mysql_real_escape_string อย่างไม่มีที่สิ้นสุด

ใช่ mysql_real_escape_string เป็นเพียงฟังก์ชันหนีสตริงเท่านั้น มันไม่ใช่กระสุนวิเศษ สิ่งที่ต้องทำคือหลีกเลี่ยงอักขระอันตรายเพื่อให้สามารถใช้ในสตริงการสืบค้นเดียวได้อย่างปลอดภัย อย่างไรก็ตามหากคุณไม่ทำความสะอาดอินพุตของคุณล่วงหน้าคุณจะเสี่ยงต่อเวกเตอร์การโจมตีบางอย่าง

ลองนึกภาพ SQL ต่อไปนี้:

$ result = "เลือกช่องจากตาราง WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);

คุณควรจะเห็นว่าสิ่งนี้เสี่ยงต่อการถูกใช้ประโยชน์ ลองนึกภาพพารามิเตอร์ id มีเวกเตอร์การโจมตีทั่วไป:

1 หรือ 1 = 1

ไม่มีตัวอักษรที่มีความเสี่ยงในการเข้ารหัสดังนั้นมันจะผ่านตัวกรองการหลบหนีโดยตรง ออกจากเรา:

เลือกช่องจากตาราง WHERE id = 1 หรือ 1 = 1

ฉันเขียนโค้ดฟังก์ชั่นเล็ก ๆ น้อย ๆ ที่ฉันใส่ไว้ในคลาสฐานข้อมูลของฉันซึ่งจะตัดสิ่งที่ไม่ใช่ตัวเลขออกไป มันใช้ preg_replace ดังนั้นจึงมีฟังก์ชั่นที่ได้รับการปรับให้เหมาะสมมากขึ้นเล็กน้อย แต่มันใช้งานได้ในพริบตา ...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

ดังนั้นแทนที่จะใช้

$ result = "เลือกช่องจากตาราง WHERE id =" .mysqlrealescapestring ("1 หรือ 1 = 1");

ฉันจะใช้

$ result = "เลือกช่องจากตาราง WHERE id =" .Numbers ("1 หรือ 1 = 1");

และจะเรียกใช้แบบสอบถามได้อย่างปลอดภัย

เลือกช่องจากตาราง WHERE id = 111

แน่นอนว่ามันหยุดไม่ให้แสดงแถวที่ถูกต้อง แต่ฉันไม่คิดว่านั่นเป็นปัญหาใหญ่สำหรับใครก็ตามที่พยายามฉีด sql ลงในไซต์ของคุณ)


1
ที่สมบูรณ์แบบ! นี่เป็นวิธีการฆ่าเชื้อที่คุณต้องการ รหัสเริ่มต้นล้มเหลวเนื่องจากไม่ได้ตรวจสอบว่าตัวเลขเป็นตัวเลข รหัสของคุณทำสิ่งนี้ คุณควรเรียก Numbers () ใน vars ที่ใช้จำนวนเต็มทั้งหมดที่มีค่ามาจากภายนอก codebase
Cheekysoft

1
เป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่า intval () จะทำงานได้ดีอย่างสมบูรณ์เนื่องจาก PHP จะบังคับให้จำนวนเต็มเป็นสตริงให้คุณโดยอัตโนมัติ
Adam Ernst

11
ฉันชอบ intval มากกว่า เปลี่ยน 1abc2 เป็น 1 ไม่ใช่ 12
jmucchiello

1
intval ดีกว่าโดยเฉพาะใน ID โดยส่วนใหญ่แล้วหากเกิดความเสียหายก็จะเหมือนกับที่กล่าวไว้ข้างต้น 1 หรือ 1 = 1 คุณไม่ควรรั่วไหล ID ของคนอื่นจริงๆ ดังนั้น intval จะส่งคืน ID ที่ถูกต้อง หลังจากนั้นคุณควรตรวจสอบว่าค่าเดิมและค่าที่ล้างแล้วเหมือนกันหรือไม่ มันเป็นวิธีที่ยอดเยี่ยมในการหยุดการโจมตีไม่เพียง แต่ค้นหาผู้โจมตี
ไตรกีฬา

2
แถวที่ไม่ถูกต้องจะหายนะหากคุณแสดงข้อมูลส่วนบุคคลคุณจะเห็นข้อมูลของผู้ใช้รายอื่น! แทนที่จะตรวจสอบจะดีกว่าreturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Frank Forte

2

ส่วนสำคัญของปริศนานี้คือบริบท มีคนส่ง "1 หรือ 1 = 1" เนื่องจาก ID ไม่ใช่ปัญหาหากคุณอ้างทุกอาร์กิวเมนต์ในคำถามของคุณ:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

ซึ่งส่งผลให้:

SELECT fields FROM table WHERE id='1 OR 1=1'

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


15
จากนั้นฉันจะเริ่มเวกเตอร์การโจมตีของฉันด้วย multi-byte char 0xbf27 ซึ่งในฐานข้อมูล latin1 ของคุณจะถูกแปลงโดย fuction ตัวกรองเป็น 0xbf5c27 ซึ่งเป็นอักขระหลายไบต์เดี่ยวตามด้วยเครื่องหมายคำพูดเดี่ยว
Cheekysoft

8
พยายามอย่าป้องกันโจมตีเวกเตอร์เดียวที่รู้จัก คุณจะจบลงด้วยการไล่ตามหางของคุณจนกว่าจะสิ้นสุดเวลาที่ใช้โปรแกรมแก้ไขหลังจากแก้ไขรหัสของคุณ การยืนมองย้อนกลับไปที่กรณีทั่วไปจะทำให้เกิดรหัสที่ปลอดภัยขึ้นและความคิดที่เน้นความปลอดภัยที่ดีขึ้น
Cheekysoft

ฉันเห็นด้วย; ตามหลักการแล้ว OP จะใช้งบที่เตรียมไว้
Lucas Oman

1
แม้ว่าการอ้างถึงข้อโต้แย้งที่แนะนำโดยโพสต์นี้จะไม่สามารถเข้าใจผิดได้ แต่จะช่วยลดการโจมตีประเภท 1 หรือ 1 = 1 ทั่วไปจำนวนมากดังนั้นจึงควรค่าแก่การกล่าวถึง
Night Owl

2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

ทำงานได้ดีและดียิ่งขึ้นบนระบบ 64 บิต ระวังข้อ จำกัด ของระบบของคุณในการกำหนดหมายเลขจำนวนมาก แต่สำหรับรหัสฐานข้อมูลจะใช้งานได้ดีถึง 99% ของเวลา

คุณควรใช้ฟังก์ชัน / วิธีการเดียวในการทำความสะอาดค่าของคุณด้วย แม้ว่าฟังก์ชันนี้จะเป็นเพียง Wrapper สำหรับ mysql_real_escape_string () ทำไม? เนื่องจากวันหนึ่งเมื่อพบการใช้ประโยชน์จากวิธีการทำความสะอาดข้อมูลที่คุณต้องการคุณจะต้องอัปเดตที่เดียวแทนที่จะค้นหาและแทนที่ทั้งระบบ


-3

ทำไมโอ้ทำไมคุณไม่ใส่เครื่องหมายคำพูดรอบอินพุตของผู้ใช้ในคำสั่ง sql ของคุณ? ดูเหมือนจะโง่มากที่จะไม่! รวมถึงเครื่องหมายคำพูดในคำสั่ง sql ของคุณจะทำให้ "1 หรือ 1 = 1" เป็นความพยายามที่ไร้ผลใช่หรือไม่?

ตอนนี้คุณจะพูดว่า "จะเกิดอะไรขึ้นถ้าผู้ใช้ใส่เครื่องหมายคำพูด (หรือเครื่องหมายคำพูดคู่) ในอินพุต"

ดีและแก้ไขได้ง่ายเพียงแค่ลบคำพูดที่ป้อนโดยผู้ใช้ เช่น: input =~ s/'//g;. ตอนนี้ดูเหมือนว่าสำหรับฉันแล้วการป้อนข้อมูลของผู้ใช้นั้นจะปลอดภัย ...


"ทำไมโอ้ทำไมคุณไม่ใส่เครื่องหมายคำพูดรอบ ๆ อินพุตของผู้ใช้ในคำสั่ง sql ของคุณ" - คำถามบอกว่าไม่มีอะไรเกี่ยวกับการไม่อ้างข้อมูลผู้ใช้
Quentin

1
"ดีแก้ไขได้ง่าย" - การแก้ไขที่แย่มากสำหรับสิ่งนั้น ที่พ่นข้อมูลออกไป วิธีแก้ปัญหาที่กล่าวถึงในคำถามนั้นเป็นแนวทางที่ดีกว่า
Quentin

ในขณะที่ฉันยอมรับว่าคำถามไม่ได้กล่าวถึงการป้อนข้อมูลของผู้ใช้ แต่ก็ดูเหมือนว่าจะไม่อ้างถึงข้อมูลที่ป้อน และฉันอยากจะโยนข้อมูลแทนที่จะป้อนข้อมูลที่ไม่ถูกต้อง โดยทั่วไปในการโจมตีด้วยการฉีดยาคุณไม่ต้องการข้อมูลนั้นอยู่แล้ว .... ใช่ไหม?
Jarett L

"แม้ว่าฉันยอมรับว่าคำถามไม่ได้ระบุถึงการป้อนข้อมูลของผู้ใช้ แต่ก็ดูเหมือนว่าจะไม่อ้างถึงข้อมูลที่ป้อน" - ไม่มันไม่ คำถามไม่ได้แสดงให้เห็นทางใดทางหนึ่ง
Quentin

1
@JarettL ทั้งสองได้รับการใช้ในการใช้งบเตรียมหรือรับใช้บ๊อบบี้ตารางการทำลายข้อมูลของคุณทุกวันอังคาร Parameterized SQL เป็นวิธีเดียวที่ดีที่สุดในการป้องกันตัวเองจากการแทรก SQL คุณไม่จำเป็นต้องทำการ "ตรวจสอบการฉีด SQL" หากคุณกำลังใช้คำสั่งที่เตรียมไว้ พวกเขาใช้งานง่ายมาก (และในความคิดของฉันทำให้โค้ดอ่านง่ายขึ้นมาก) ป้องกันจากความแปลกประหลาดต่างๆของการต่อสายอักขระและการฉีด sql และที่ดีที่สุดคือคุณไม่จำเป็นต้องสร้างวงล้อใหม่เพื่อใช้งาน .
Siyual
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.