การใช้ในทางที่ผิดรวมถึง
1. สาเหตุของช่องโหว่:
รวมเป็นฟังก์ชันที่ใช้กันมากที่สุดในการเขียนเว็บไซต์ PHP และรองรับเส้นทางสัมพัทธ์ มีสคริปต์ PHP จำนวนมากที่ใช้ตัวแปรอินพุตเป็นพารามิเตอร์รวมโดยตรง ทำให้เกิดช่องโหว่ เช่น การอ้างอิงสคริปต์โดยพลการและเส้นทางรั่วไหลโดยสมบูรณ์ ดูรหัสต่อไปนี้:
-
$includepage=$_GET["รวมหน้า"];
รวม($รวมหน้า);
-
แน่นอนว่าเราเพียงส่งตัวแปรรวมหน้าที่แตกต่างกันเพื่อให้ได้หน้าที่ต้องการ หากคุณส่งเพจที่ไม่มีอยู่ คุณสามารถทำให้สคริปต์ PHP เกิดข้อผิดพลาดและทำให้เส้นทางสัมบูรณ์จริงรั่วไหลได้ (วิธีแก้ไขปัญหานี้ได้อธิบายไว้ในบทความต่อไปนี้)
2. การแก้ไขช่องโหว่:
วิธีแก้ปัญหาช่องโหว่นี้ทำได้ง่ายมาก โดยต้องระบุก่อนว่ามีเพจนั้นอยู่หรือไม่ จากนั้นจึงรวมเข้าไป หรือใช้อาร์เรย์เพื่อระบุไฟล์ที่สามารถรวมไว้ได้ ดูรหัสต่อไปนี้:
$pagelist=array("test1.php", "test2.php", "test3.php"); // นี่เป็นการระบุไฟล์ที่สามารถรวมได้
if(isset($_GET["includepage"])) //ตรวจสอบว่ามี $includepage หรือไม่
-
$includepage=$_GET["รวมหน้า"];
foreach($pagelist เป็น $prepage)
-
if($includepage==$prepage) //ตรวจสอบว่าไฟล์อยู่ในรายการที่อนุญาตหรือไม่
-
รวม($เตรียมหน้า);
$checkfind=true;
หยุดพัก;
-
-
if($checkfind==true){ unset($checkfind); }
else{ die("หน้าอ้างอิงไม่ถูกต้อง!");
-
จะช่วยแก้ปัญหาได้เป็นอย่างดี
เคล็ดลับ: ฟังก์ชันที่มีปัญหานี้ได้แก่: need(), need_once(), include_once(), readfile() ฯลฯ คุณควรใส่ใจเมื่อเขียนด้วย
ตัวแปรอินพุตจะไม่ถูกกรอง
1. สาเหตุของช่องโหว่:
ช่องโหว่นี้ปรากฏมานานแล้วใน ASP ทำให้เกิดช่องโหว่ในการแทรกจำนวนนับไม่ถ้วนในขณะนั้น แต่เนื่องจาก PHP มีอิทธิพลเล็กน้อยในขณะนั้น จึงมีคนไม่มากนักที่จะสนใจเรื่องนี้ สำหรับ PHP ผลกระทบของช่องโหว่นี้มีมากกว่า ASP เนื่องจากสคริปต์ PHP จำนวนมากใช้ฐานข้อมูลข้อความ แน่นอนว่ายังมีปัญหาในการแทรกคำสั่ง SQL อีกด้วย เพื่อให้เป็นตัวอย่างที่คลาสสิกมากขึ้น สิ่งแรกคือฐานข้อมูล:
$id=$_GET["id"];
$query="SELECT * FROM my_table โดยที่ id='".$id."'"; // ช่องโหว่การฉีด SQL แบบคลาสสิก
$result=mysql_query($query);
เห็นได้ชัดว่าเราสามารถใช้การฉีดเพื่อรับเนื้อหาอื่น ๆ ของฐานข้อมูลได้ ฉันจะไม่อธิบายรายละเอียดที่นี่ เช่นเดียวกับการฉีด ASP คุณสามารถดูการป้องกันสีดำก่อนหน้านี้ได้ จากนั้นเรามาดูปัญหาของฐานข้อมูลข้อความ:
$text1=$_POST["text1"];
$text2=$_POST["text2"];
$text3=$_POST["text3"];
$fd=fopen("test.php", "a");
fwrite($fd,"rn$text1&line;$text2&line;$text3");
fclose($fd);
ช่องโหว่ของข้อความนั้นมีความรุนแรงมากยิ่งขึ้น หากเราแทรกโค้ด PHP ชิ้นเล็กๆ ลงในตัวแปรที่ส่งมา เราก็สามารถเปลี่ยนฐานข้อมูลข้อความ test.php ให้เป็นแบ็คดอร์ PHP ได้ แม้แต่การใส่โค้ดอัพโหลดก็ทำให้เราสามารถอัพโหลดแบ็คดอร์ PHP ได้อย่างสมบูรณ์ จากนั้นเพิ่มสิทธิ์และเซิร์ฟเวอร์ก็เป็นของคุณ
2. การแก้ไขช่องโหว่:
จริงๆ แล้ววิธีแก้ปัญหาช่องโหว่นี้ง่ายมาก นั่นคือกรองตัวแปรที่ส่งมาทั้งหมดอย่างเคร่งครัด แทนที่อักขระที่ละเอียดอ่อนบางตัว เราสามารถแทนที่เนื้อหาของ HTML ด้วยความช่วยเหลือของฟังก์ชัน htmlspecialchars() ที่จัดทำโดย PHP นี่คือตัวอย่าง:
//สร้างฟังก์ชันตัวกรอง www.knowsky.com
ฟังก์ชั่น flt_tags($ข้อความ)
-
$badwords=array("Fuck you", "fuck"); // รายการตัวกรองคำ
$text=rtrim($ข้อความ);
foreach($badwords as $badword) //กรองคำศัพท์ที่นี่
-
if(stristr($text,$badword)==true){ die("ข้อผิดพลาด: เนื้อหาที่คุณส่งมามีคำที่ละเอียดอ่อน โปรดอย่าส่งเนื้อหาที่ละเอียดอ่อน"); }
-
$text=htmlspecialchars($text); //การแทนที่ HTML
// สองบรรทัดนี้แทนที่การขึ้นบรรทัดใหม่ด้วย
$text=str_replace("r","
",$ข้อความ);
$text=str_replace("n","",$text);
$text=str_replace("&line;","│",$text); //แทนที่ตัวคั่นฐานข้อมูลข้อความ "&line;" ด้วยความกว้างเต็ม "│"
$text=preg_replace("/s{ 2 }/"," ",$text); // การแทนที่ช่องว่าง
$text=preg_replace("/t/"," ",$text); // ยังคงแทนที่ช่องว่าง
if(get_magic_quotes_gpc()){ $text=stripslashes($text); } // หาก magic_quotes เปิดอยู่ ให้แทนที่ '
กลับข้อความ $;
}
$text1=$_POST["ข้อความ1"];
$text2=$_POST["text2"];
$text3=$_POST["text3"];
//กรองอินพุตทั้งหมด
$text1=flt_tags($text1);
$text2=flt_tags($text2);
$text3=flt_tags($text3);
$fd=fopen("test.php", "a");
fwrite($fd,"rn$text1&line;$text2&line;$text3");
fclose($fd);
หลังจากเปลี่ยนและกรองแล้ว คุณสามารถเขียนข้อมูลลงในข้อความหรือฐานข้อมูลได้อย่างปลอดภัย
การตัดสินของผู้ดูแลระบบไม่สมบูรณ์
1. สาเหตุของช่องโหว่:
เราใช้ PHP ในการเขียนสคริปต์ ซึ่งโดยปกติแล้วจะเกี่ยวข้องกับการอนุญาตของผู้ดูแลระบบ สคริปต์บางตัวทำการตัดสิน "ใช่" เกี่ยวกับสิทธิ์ของผู้ดูแลระบบเท่านั้น แต่มักจะเพิกเฉยต่อการตัดสิน "ไม่" เมื่อ register_globals ถูกเปิดใช้งานในไฟล์คอนฟิกูเรชัน PHP (จะถูกปิดโดยค่าเริ่มต้นในเวอร์ชันหลัง 4.2.0 แต่หลายคนเปิดใช้งานเพื่อความสะดวกซึ่งเป็นพฤติกรรมที่อันตรายอย่างยิ่ง) จะมีสถานการณ์ที่ตัวแปรถูกส่งไปเลียนแบบ ผู้ดูแลระบบ ลองมาดูโค้ดตัวอย่าง:
$cookiesign="admincookiesign"; //ตรวจสอบว่าตัวแปรคุกกี้ของผู้ดูแลระบบหรือไม่
$adminsign=$_COOKIE["sign"]; //รับตัวแปรคุกกี้ของผู้ใช้
if($adminsign==$cookiesign)
-
$admin=true;
}
if($admin){ echo "คุณเป็นผู้ดูแลระบบแล้ว"; }
มันดูปลอดภัยมากเลย 555 ตอนนี้เราถือว่า register_globals เปิดอยู่ในไฟล์คอนฟิกูเรชัน PHP เราส่งที่อยู่ดังกล่าว "test.php?admin=true" คุณเห็นผลลัพธ์แล้วหรือยัง? แม้ว่าเราจะไม่มีคุกกี้ที่ถูกต้อง แต่เนื่องจาก register_globals เปิดอยู่ ตัวแปรผู้ดูแลระบบที่เราส่งมาจึงได้รับการลงทะเบียนเป็น True โดยอัตโนมัติ นอกจากนี้ สคริปต์ยังขาดการตัดสินว่า "ไม่" ซึ่งช่วยให้เราได้รับสิทธิ์ผู้ดูแลระบบผ่าน admin=true ได้สำเร็จ ปัญหานี้มีอยู่ในเว็บไซต์และฟอรัมส่วนใหญ่
2. การแก้ไขช่องโหว่:
เพื่อแก้ไขปัญหานี้ เราเพียงแต่ต้องเพิ่มการตัดสินว่า "ไม่" ให้กับผู้ดูแลระบบในสคริปต์เท่านั้น เรายังคงถือว่า register_globals เปิดอยู่ในไฟล์คอนฟิกูเรชัน PHP ดูรหัส:
$cookiesign="admincookiesign"; //ตรวจสอบว่าตัวแปรคุกกี้ของผู้ดูแลระบบหรือไม่
$adminsign=$_COOKIE["sign"]; //รับตัวแปรคุกกี้ของผู้ใช้
if($adminsign==$cookiesign)
-
$admin=true;
-
อื่น
-
$admin=false;
-
if($admin){ echo "คุณเป็นผู้ดูแลระบบแล้ว"; }
ด้วยวิธีนี้ แม้ว่าผู้โจมตีจะส่งตัวแปร admin=true โดยไม่มีคุกกี้ที่ถูกต้อง สคริปต์จะตั้งค่า $admin เป็น False ในการตัดสินในอนาคต วิธีนี้จะช่วยแก้ปัญหาได้บางส่วน อย่างไรก็ตาม เนื่องจาก $admin เป็นตัวแปร หากเกิดช่องโหว่ในการอ้างอิงสคริปต์อื่นในอนาคต และ $admin ถูกมอบหมายใหม่ วิกฤตครั้งใหม่จะเกิดขึ้น ดังนั้นเราจึงควรใช้ค่าคงที่เพื่อจัดเก็บการกำหนดสิทธิ์ของผู้ดูแลระบบ ใช้คำสั่ง Define() เพื่อกำหนดค่าคงที่ของผู้ดูแลระบบเพื่อบันทึกสิทธิ์ของผู้ดูแลระบบ หากมีการกำหนดใหม่หลังจากนี้ จะเกิดข้อผิดพลาดขึ้น เพื่อให้บรรลุวัตถุประสงค์ของการป้องกัน ดูรหัสต่อไปนี้:
$cookiesign="admincookiesign"; //ตรวจสอบว่าตัวแปรคุกกี้ของผู้ดูแลระบบหรือไม่
$adminsign=$_COOKIE["sign"]; //รับตัวแปรคุกกี้ของผู้ใช้
if($adminsign==$cookiesign)
-
กำหนด (ผู้ดูแลระบบจริง);
-
อื่น
-
กำหนด (ผู้ดูแลระบบ, เท็จ);
-
if(admin){ echo "ขณะนี้คุณอยู่ในสถานะผู้ดูแลระบบ";
เป็นที่น่าสังเกตว่าเราใช้คำสั่ง Define ดังนั้นเมื่อเรียกค่าคงที่ Admin อย่าเพิ่มสัญลักษณ์ตัวแปร $ ไว้ข้างหน้าเป็นประจำ แต่ใช้ Admin และ !admin
ฐานข้อมูลข้อความถูกเปิดเผย
1. สาเหตุของช่องโหว่:
ตามที่กล่าวไว้ข้างต้น เนื่องจากฐานข้อมูลข้อความมีความยืดหยุ่นสูง จึงไม่จำเป็นต้องมีการสนับสนุนจากภายนอก นอกจากนี้ PHP ยังมีความสามารถในการประมวลผลไฟล์ที่แข็งแกร่งมาก ดังนั้นจึงมีการใช้ฐานข้อมูลข้อความอย่างกว้างขวางในสคริปต์ PHP มีโปรแกรมฟอรัมที่ดีหลายโปรแกรมที่ใช้ฐานข้อมูลข้อความ แต่มีกำไรและขาดทุน และความปลอดภัยของฐานข้อมูลข้อความยังต่ำกว่าฐานข้อมูลอื่นๆ
2. การแก้ไขช่องโหว่:
ฐานข้อมูลข้อความทำหน้าที่เป็นไฟล์ธรรมดาซึ่งสามารถดาวน์โหลดได้เช่นเดียวกับ MDB ดังนั้นเราจึงจำเป็นต้องปกป้องฐานข้อมูลข้อความในลักษณะเดียวกับ MDB เปลี่ยนชื่อส่วนต่อท้ายของฐานข้อมูลข้อความเป็น .PHP และเข้าร่วมในแถวแรกของฐานข้อมูล วิธีนี้ฐานข้อมูลข้อความจะถือเป็นไฟล์ PHP และการดำเนินการจะออกจากบรรทัดแรก นั่นคือหน้าว่างจะถูกส่งกลับเพื่อให้บรรลุวัตถุประสงค์ในการปกป้องฐานข้อมูลข้อความ
เส้นทางที่ผิดรั่วไหล
1. สาเหตุของช่องโหว่:
เมื่อ PHP พบข้อผิดพลาด PHP จะแจ้งตำแหน่ง หมายเลขบรรทัด และเหตุผลของสคริปต์ข้อผิดพลาด เช่น:
หมายเหตุ: การใช้การทดสอบคงที่ที่ไม่ได้กำหนด - ถือว่า 'ทดสอบ' ใน D:interpubbigflytest.php ออนไลน์ 3
หลายคนบอกว่าไม่ใช่เรื่องใหญ่ แต่ผลที่ตามมาของการรั่วไหลของเส้นทางที่เกิดขึ้นจริงนั้นเป็นเรื่องที่ไม่อาจจินตนาการได้สำหรับผู้บุกรุกบางราย ข้อมูลนี้มีความสำคัญมาก ที่จริงแล้ว เซิร์ฟเวอร์จำนวนมากประสบปัญหานี้
ผู้ดูแลระบบเครือข่ายบางคนเพียงตั้งค่า display_errors ในไฟล์กำหนดค่า PHP เป็นปิดเพื่อแก้ไขปัญหา แต่ฉันคิดว่าวิธีนี้เป็นลบเกินไป บางครั้งเราต้องการ PHP จริงๆ เพื่อส่งคืนข้อมูลข้อผิดพลาดสำหรับการดีบัก และเมื่อมีบางอย่างผิดพลาด คุณอาจต้องให้คำอธิบายแก่ผู้ใช้หรือแม้กระทั่งนำทางไปยังหน้าอื่น
2. การแก้ไขช่องโหว่:
PHP ได้จัดเตรียมฟังก์ชันการจัดการข้อผิดพลาดแบบกำหนดเอง function set_error_handler() ไว้ตั้งแต่ 4.1.0 แต่มีผู้เขียนสคริปต์เพียงไม่กี่คนที่รู้ ในบรรดาฟอรั่ม PHP หลายๆ ฟอรั่ม ฉันเห็นว่ามีเพียงไม่กี่ฟอรั่มเท่านั้นที่จัดการกับสถานการณ์นี้ได้ การใช้งาน set_error_handler มีดังนี้:
สตริง set_error_handler ( โทรกลับ error_handler [, int error_types])
ตอนนี้เราใช้การจัดการข้อผิดพลาดแบบกำหนดเองเพื่อกรองเส้นทางจริงออก
//ผู้ดูแลระบบคือการกำหนดตัวตนของผู้ดูแลระบบ ความจริงคือผู้ดูแลระบบ
//ฟังก์ชันการจัดการข้อผิดพลาดแบบกำหนดเองต้องมีตัวแปรอินพุตสี่ตัวแปรนี้ $errno, $errstr, $errfile, $errline มิฉะนั้นจะไม่ถูกต้อง
ฟังก์ชั่น my_error_handler($errno,$errstr,$errfile,$errline)
-
//หากคุณไม่ใช่ผู้ดูแลระบบ ให้กรองเส้นทางจริง
ถ้า(!ผู้ดูแลระบบ)
-
$errfile=str_replace(getcwd(),"",$errfile);
$errstr=str_replace(getcwd(),"",$errstr);
}
สวิตช์($errno)
-
กรณี E_ERROR:
echo "ข้อผิดพลาด: [ID $errno] $errstr (บรรทัด: $errline จาก $errfile)
n";
echo "โปรแกรมหยุดทำงานแล้ว โปรดติดต่อผู้ดูแลระบบ";
//ออกจากสคริปต์เมื่อพบข้อผิดพลาดระดับข้อผิดพลาด
ออก;
พัง;
กรณี E_WARNING:
echo "คำเตือน: [ID $errno] $errstr (บรรทัด: $errline จาก $errfile)
n";
ทำลาย
;
//อย่าแสดงข้อผิดพลาดระดับการแจ้งเตือน
หยุดพัก;
-
}
//ตั้งค่าการจัดการข้อผิดพลาดเป็นฟังก์ชัน my_error_handler
set_error_handler("my_error_handler");
-
ด้วยวิธีนี้ ความขัดแย้งระหว่างความปลอดภัยและความสะดวกในการตรวจแก้จุดบกพร่องจะสามารถแก้ไขได้อย่างดี และคุณยังสามารถคิดเพื่อทำให้ข้อความแสดงข้อผิดพลาดสวยงามยิ่งขึ้นเพื่อให้เข้ากับสไตล์ของเว็บไซต์ได้อีกด้วย แต่ให้สังเกตสองประเด็น:
(1) E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR และ E_COMPILE_WARNING จะไม่ถูกประมวลผลโดยหมายเลขอ้างอิงนี้ กล่าวคือ จะแสดงในรูปแบบดั้งเดิมที่สุด อย่างไรก็ตาม ข้อผิดพลาดเหล่านี้มีสาเหตุมาจากข้อผิดพลาดในการคอมไพล์หรือเคอร์เนล PHP และจะไม่เกิดขึ้นภายใต้สถานการณ์ปกติ
(2) หลังจากใช้ set_error_handler() แล้ว error_reporting () จะไม่ถูกต้อง นั่นคือข้อผิดพลาดทั้งหมด (ยกเว้นข้อผิดพลาดที่กล่าวข้างต้น) จะถูกส่งไปยังฟังก์ชันที่กำหนดเองเพื่อการประมวลผล
สำหรับข้อมูลอื่นๆ เกี่ยวกับ set_error_handler() คุณสามารถดูคู่มือ PHP อย่างเป็นทางการได้
โพสต์ช่องโหว่
1. สาเหตุของช่องโหว่:
ดังที่ได้กล่าวไว้ก่อนหน้านี้ การใช้ register_globals เพื่อลงทะเบียนตัวแปรถือเป็นนิสัยที่ไม่ดี ในบางโปรแกรมสมุดเยี่ยมชมและฟอรัม จำเป็นต้องตรวจสอบวิธีการรับหน้าและช่วงเวลาระหว่างการส่งอย่างเข้มงวดยิ่งขึ้น เพื่อป้องกันโพสต์สแปมและการส่งจากภายนอก มาดูโค้ดต่อไปนี้สำหรับโปรแกรมสมุดเยี่ยม:
-
$text1=flt_tags($text1);
$text2=flt_tags($text2);
$text3=flt_tags($text3);
$fd=fopen("data.php", "a");
fwrite($fd,"rn$text1&line;$text2&line;$text3");
fclose($fd);
-
แน่นอนว่าหากเราส่ง URL "post.php?text1=testhaha&text2=testhaha&text3=testhaha" ข้อมูลจะถูกเขียนลงไฟล์ตามปกติ โปรแกรมนี้ตรวจไม่พบแหล่งที่มาของตัวแปรและวิธีที่เบราว์เซอร์ได้รับเพจ หากเราส่งการส่งหลายครั้งในหน้านี้ จะทำให้เกิดน้ำท่วม นอกจากนี้ยังมีซอฟต์แวร์บางตัวที่ใช้ประโยชน์จากช่องโหว่นี้ในการโพสต์โฆษณาบนฟอรัมหรือสมุดเยี่ยม ซึ่งเป็นพฤติกรรมที่น่าละอาย (สมุดเยี่ยมของเพื่อนของฉันถูกน้ำท่วมมากกว่า 10 หน้าในหนึ่งสัปดาห์ ซึ่งช่วยอะไรไม่ได้)
2. การแก้ไขช่องโหว่:
ก่อนที่จะประมวลผลและบันทึกข้อมูล ขั้นแรกให้พิจารณาว่าเบราว์เซอร์รับเพจอย่างไร ใช้ตัวแปร $_SERVER["REQUEST_METHOD"] เพื่อรับวิธีการของเบราว์เซอร์ในการรับเพจ ตรวจสอบว่าเป็น "POST" หรือไม่ ใช้เซสชันในสคริปต์เพื่อบันทึกว่าผู้ใช้ส่งข้อมูลผ่านช่องทางปกติหรือไม่ (นั่นคือ หน้าที่กรอกเนื้อหาที่ส่ง) หรือใช้ $_SERVER["HTTP_REFERER"] เพื่อตรวจจับสิ่งนี้ แต่ไม่แนะนำ เนื่องจากเบราว์เซอร์บางตัวไม่ได้ตั้งค่า REFERER ไฟร์วอลล์บางตัวจะบล็อก REFERER ด้วย นอกจากนี้เรายังต้องตรวจสอบเนื้อหาที่ส่งมาเพื่อดูว่ามีเนื้อหาซ้ำกันในฐานข้อมูลหรือไม่ ยกตัวอย่างสมุดเยี่ยมชม และใช้เซสชันในการตัดสินใจ:
ในหน้าที่คุณกรอกเนื้อหาการสืบค้น เราจะเพิ่มที่ส่วนหน้า:
$_SESSION["allowgbookpost"]=time(); //เวลาที่กรอกการลงทะเบียน ในหน้าที่ยอมรับและบันทึกข้อมูลข้อความ เรายังใช้เซสชันเพื่อดำเนินการประมวลผลต่อไปนี้ก่อนการประมวลผลข้อมูล:
if(strtoupper($_SERVER["REQUEST_METHOD"])!="POST"){ die("ข้อผิดพลาด: ห้ามส่งจากภายนอก"); } //ตรวจสอบว่าวิธีการรับหน้าเป็นแบบ POST หรือไม่
if(!isset($_SESSION["allowgbookpost"]) หรือ (time()-$_SESSION["allowgbookpost"] < 10)){ die("ข้อผิดพลาด: ห้ามส่งจากภายนอก"); } //ตรวจสอบข้อความ เวลาที่กรอก
if(isset($_SESSION["gbookposttime"]) และ (time()-$_SESSION["gbookposttime"] < 120)){ die("ข้อผิดพลาด: ช่วงเวลาระหว่างการส่งข้อความสองครั้งต้องไม่น้อยกว่า 2 นาที "); } //ตรวจสอบช่วงเวลาข้อความ
ที่ไม่ได้ตั้งค่า($_SESSION["allowgbookpost"]); //ยกเลิกการลงทะเบียนตัวแปร Allowgbookpost เพื่อป้องกันไม่ให้การส่งหลายครั้งเข้าสู่หน้ากรอกข้อมูลในคราวเดียว
$_SESSION["gbookposttime"]=time(); //ลงทะเบียนเวลาในการส่งข้อความเพื่อป้องกันสแปมหรือการโจมตีที่เป็นอันตราย
-
การประมวลผลและการจัดเก็บข้อมูล
-
หลังจากการตรวจสอบหลายครั้ง โปรแกรมของคุณจะปลอดภัยมากขึ้น