Pages

12/16/2013

PHP flaw in stripos and eregi

เมื่อวันก่อน Longcat เอาปัญหาจาก CTF อันนึงมาให้ดู
ในโจทย์คือ จะต้อง SQL injection เพื่อเอาชื่อตารางออกมาให้ได้
ซึ่งแน่นอนว่าจะต้องเอามาจาก information_schema.tables

แต่ปัญหาคือ โค้ด PHP ตัวนี้มีการป้องกันการโจมตีแบบต่างๆไว้ คร่าวๆ ดังนี้

if(stripos($_GET['val'],'schema')) exit();
if(eregi("union",$_GET['val'])) exit();
if(eregi("select",$_GET['val'])) exit();
if(eregi("from",$_GET['val'])) exit();
if(eregi("/",$_GET['val'])) exit();
if(eregi("\*",$_GET['val'])) exit();
if(eregi("#",$_GET['val'])) exit();
if(eregi("-",$_GET['val'])) exit();
if(eregi(",",$_GET['val'])) exit();
if(eregi("=",$_GET['val'])) exit();
if(eregi("!",$_GET['val'])) exit();
if(eregi("\|",$_GET['val'])) exit();
if(eregi("by",$_GET['val'])) exit();

ซึ่งเอาแค่สรุปหลักๆ จากการป้องกันก็คือ  ป้องกันไม่ให้ใช้คำว่า schema, select, from, union, และเครื่องหมาย comment ต่างๆได้เลย   และถ้าหลอกล่อด้วยการใช้ ตัวใหญ่ตัวเล็ก ก็ไม่ได้

แล้วจะทำยังไงดี?

จนเมื่อ CTF นี้ จบแล้ว  ได้ Xellos มาเฉลยว่า โค้ดชุดนี้ มีช่องโหว่อยู่

ช่องโหว่ของ if + stripos()

ถ้าไปค้นหาคำสั่ง strpos() หรือ stripos() ดูจาก PHP documentation ในเว็บ php.net (http://www.php.net/manual/en/function.stripos.php) ก็จะรู้ว่า

  • stripos จะค้นหาคำจากซ้ายไปขวา
  • ถ้า พบคำที่ค้นหา จะส่งคืนเป็นตำแหน่งของตัวอักษรตัวแรกที่เจอออกมา  โดย เริ่มต้นที่ 0
  • ถ้า ไม่พบ จะคืนค่าออกมาเป็นบูลลีน FALSE
และมีคำเตือนสีแดงชัดเจนว่า...


นั่นหมายความว่า stripos สามารถคืนค่าเป็น 0 ได้  ซึ่งมันจะถูกตีความเป็น FALSE ได้  *0*

นั่นคือ ถ้าเราส่ง $_GET['val'] ให้มีคำว่า "schema" เป็นคำเริ่มต้น
stripos($_GET['val'], 'schema') ก็จะให้ค่าออกมาเป็น 0
ซึ่งถ้าเราเอาไปตรวจสอบด้วย if ( 0 )  มันก็จะได้ค่าเท่ากับ if ( FALSE ) นั่นเอง
แล้วก็เลยทำให้ bypass filter ตัวนี้ไปได้

วิธีป้องกัน
ควรเขียน "===" ใน if ด้วย เพื่อป้องกันการ casting ชนิดตัวแปรจาก integer( 0 ) ไปเป็น boolean(FALSE) แบบที่เราไม่ได้ตั้งใจ ซึ่งโค้ดข้างบน ควรจะเขียนเป็น

if(stripos($_GET['val'],'schema') === TRUE) exit();

ช่องโหว่ของ eregi()

เนื่องจากโค้ดของ ereg() หรือ eregi() ใน PHP ยังใช้การทำงานของภาษา C อยู่ข้างใน

คำถามคือ: เมื่อไหร่ที่โค้ดภาษา C จะหยุดการทำงานกับ String?

ตอบ: เมื่อมันเจอ null byte (0x00, \0, หรือ chr(0) ) ซึ่งปกติจะมีอยู่ตัวเดียวที่อยู่ท้ายสุดของ string นั้น

แต่ถ้าเกิดเราใส่ null byte ให้ตั้งแต่ตัวอักษรแรกเลยล่ะ? แล้วเอาไปค้นหาด้วย ereg
ereg พอวิ่งมาถึงตัวแรก ก็เจอ null byte ซึ่งบอกว่า นี่มันปลายสุดของ string แล้ว ไม่ต้องทำอะไรต่อแล้ว
และมันก็จะถือว่า วิ่งมาไม่พบอะไร และ return FALSE กลับไป

นั่นทำให้ หลัง null byte นี้ เราสามารถใส่อะไรที่ filter ข้างบนห้ามไว้ลงไปได้หมดเลย ถือเป็นการ bypass filter แบบที่ใช้ ereg* ได้

วิธีป้องกัน
ควรย้ายไปใช้ฟังก์ชั่นตระกูล PCRE preg_* จะป้องกันปัญหาของการใช้ null byte กับฟังก์ชั่นตระกูล ereg* ได้
( แต่อาจจะเจอปัญหาอื่นเพิ่มใน preg_* เช่น \n, /e )




No comments:

Post a Comment