Kotchasan PHP Framework

แจ้งปัญหาช่องโหว่ LFI ของโปรเจ็คตัวอย่างโปรเจ็คหนึ่งของ Kotchasan (แก้ไขแล้ว)

วันนี้ (2 เมย. 2562) ได้รับแจ้งปัญหาช่องโหว่ LFI ของโปรเจ็คตัวอย่างโปรเจ็คหนึ่งของ Kotchasan จากเพื่อนสมาชิกบน Facebook Somboon Konglee (ซึ่งสถานะปัจจุบันคือได้รับการแก้ไขเรียบร้อยแล้ว)

ช่องโหว่ LFI คืออะไร

LFI ย่อมาจาก Local file Inclusion ความหมายของมันก็คือ ช่องโหว่การเข้าถึงไฟล์ภายในเครื่อง ช่องโหว่นี้จัดเป็นช่องโหว่ร้ายแรงประเภทหนึ่งทีเดียว เนื่องจาก อาจทำให้ Server ของเรา เสร็จโจรได้ง่ายๆ ยกตัวอย่างเช่น หากสามารถเข้าถึงไฟล์ที่ทำหน้าที่ในการเก็บรหัสผ่านได้ รหัสผ่านก็จะอยู่ในมือโจร สามารถเป็นเจ้าของเครื่องแทนเราได้เลยทีเดียว

ช่องโหว่นี้เกิดขึ้นที่โปรเจ็คตัวอย่างหนึ่งของ Kotchasan ครับ คือโปรเจ็ค Ajax (projects/ajax/) มาดูโค้ดที่เป็นปัญหากัน
$url = $request->post('url')->url();
if ($url != '') {
    // โหลด URL ที่ส่งมา
    $content = file_get_contents($url);
    // คืนค่า HTML ไปยัง Ajax
    echo $content;
}

ปัญหาอยู่ที่บรรทัดนี้ครับ
$content = file_get_contents($url);

คำสั่งนี้เป็นการโหลดไฟล์ ที่ส่งมากับ $url เช่นส่งค่า http://goragod.com ก็จะเป็นการโหลด URL ข้างต้นส่งกลับไปด้วย Ajax แต่ปัญหาก็คือ รูปแบบของคำสั่งมันสามารถโหลดไฟล์ภายในเครื่องได้ซะด้วย เช่น /var/www/index.html ทำให้สคริปต์สามารถโหลดไฟล์ใดๆก็ได้ในเครื่อง ซึ่งหากรู้ที่อยู่ของไฟล์ที่สำคัญเช่น ไฟล์เก็บ password ก็จะสามารถอ่านออกมาได้ง่ายๆ

จากปัญหาข้างต้น การแก้ไขคือ ทำการตรวจสอบก่อน ว่าไฟล์เป้าหมายต้องเป็น URL เท่านั้น หรือ หากต้องการโหลดไฟล์ในเครื่องจริงๆก็ต้องจำกัดพื้นที่ ที่จะยอมให้โปรแกรมสามารถเข้าถึงไฟล์ได้
$url = $request->post('url')->url();
if ($url != '' && preg_match('/^https?:\/\/.*/', $url)) {
    // โหลด URL ที่ส่งมา
    $content = file_get_contents($url);
    // คืนค่า HTML ไปยัง Ajax
    echo $content;
}

คำสั่งที่เพิ่มเข้ามา คือ preg_match('/^https?:\/\/.*/', $url) เป็นการกำหนดว่า $url ที่ส่งมาต้องขึ้นต้นด้วย http เท่านั้น เพียงเท่านี้ก็จะไม่สามารถโหลดไฟล์ใดๆในเครื่องได้แล้ว (นอกจากไฟล์ที่สามารถเรียกผ่าน URL ได้ตามปกติ)