เมื่อวันที่ 9 พ.ย. ที่ผ่านมาได้มีการปล่อย Exploit SQL Injection ของ IP.Board 3.4.7 ซึ่งเป็นเวอร์ชั่นล่าสุด ในเว็บไซต์ของ Full Disclosure และได้มีการ Public โค้ด Python ที่ใช้ในการโจมตีช่องโหว่ดังกล่าวด้วย จึงเป็นที่มาของบทความนี้ครับ
IP.Board ที่ผมเอามาลองคือเวอร์ชั่น 3.4.6 ครับจากการอ่านโค้ดนั้นเขียนด้วย PHP OOP ไฟล์ที่มีปัญหาก็คือไฟล์ ipsconnect.php ที่ส่งค่าไปยัง ipsMember.php ครับ
/** * * Map - can modify to add additional parameters, but the IPS Community Suite will only send the defaults * */ $map = array( 'login' => array( 'idType', 'id', 'password', 'key', 'redirect', 'redirectHash' ), 'logout' => array( 'id', 'key', 'redirect', 'redirectHash' ), 'register' => array( 'key', 'username', 'displayname', 'password', 'email', 'revalidateurl' ), 'cookies' => array( 'data' ), 'check' => array( 'key', 'id', 'username', 'displayname', 'email' ), 'change' => array( 'id', 'key', 'username', 'displayname', 'email', 'password', 'redirect', 'redirectHash' ), 'validate' => array( 'id', 'key' ), 'delete' => array( 'id', 'key' ) ); /** * * Process Logic - do not modify * */ $ipsConnect = new ipsConnect(); if ( isset( $_REQUEST['act'] ) and isset( $map[ $_REQUEST['act'] ] ) ) { $params = array(); foreach ( $map[ $_REQUEST['act'] ] as $k ) { $params[ $k ] = $_REQUEST[ $k ]; } call_user_func_array( array( $ipsConnect, $_REQUEST['act'] ), $params ); } exit;
จากโค้ดมีการเรียกใช้คลาส ipsConnect() ที่อยู่ในไฟล์เดียวกันผ่านฟังก์ชั่น call_user_func_array โดยมีการส่งค่าจากตัวแปร $param ซึ่งรับค่า $_REQUEST ที่เก็บไว้ส่งเข้าไปด้วย
เมื่อนำมาประกอบกันก็จะได้เป็น
call_user_func_array(array(ipsConnect,login),$param); // $param ค่าพารามิเตอร์ส่งเข้าไปในฟังก์ชั่น login
เป็นการเรียกฟังก์ชั่น login ที่อยู่ในคลาส ipsConnect นั่นเองไล่ไปดูฟังก์ชั่น login กัน
public function login( $identifier, $identifierValue, $md5Password, $key, $redirect, $redirectHash ) { $member = NULL; $statusCode = 'MISSING_DATA'; $secondsUntilUnlock = 0; $revalidateUrl = ''; /* Check */ if ( in_array( $identifier, array( 'id', 'email', 'username' ) ) ) { $member = IPSMember::load( $identifierValue, 'none', $identifier ); if ( $member['member_id'] ) {
มีการนำค่าที่ $param (idType) ที่รับมาเก็บอยู่ในตัวแปร $identifier จากนั้นเช็คว่าค่าที่ส่งเข้ามานั้นตรงกับค่าใดค่าหนึ่งใน array หรือไม่ด้วยฟังก์ชั่น in_array (idType=id) เมื่อเป็นจริงจึงส่งตัวแปร $identifier และ $identifierValue (id=-1,id[]=-1) เข้าไปในฟังก์ชั่น load ของคลาส IPSMember ในไฟล์ ipsMember.php ครับ
ตามไปดูฟังก์ชั่น load ในคลาส IPSMember กันครับ
static public function load( $member_key, $extra_tables='all', $key_type='' ) ...[snip]... switch( $key_type ) { default: case 'id': if ( is_array( $member_key ) ) { $multiple_ids = $member_key; } else { $member_value = intval( $member_key ); } $member_field = 'member_id'; break;
จะเห็นว่าค่าพารามิเตอร์ที่ 3 ($key_type) ที่ส่งเข้ามาในฟังก์ชั่น load นั้นถูกส่งไปทีี่ switch case และตกที่ case id จากนั้นเช็คว่าค่า id ที่รับมานั้นเป็น array หรือไม่ด้วยฟังก์ชั่น is_array ซึ่งการโจมตีที่เกิดขึ้นทำให้เงื่อนไขนี้เป็นจริง และเก็บค่าดังกล่าวไว้ในตัวแปร $multiple_ids ตามไปดูตัวแปรนี้กันครับ
else if( count($multiple_ids) AND is_array($multiple_ids) ) { $_totalUsers = count($multiple_ids); $_gotFromCache = 0; $_fromCache = array(); foreach( $multiple_ids as $_memberValue ) { $member = self::_fetchFromCache( $_memberValue, $_usedTables ); if ( $member !== FALSE ) { $_fromCache[ $member['member_id'] ] = $member; $_gotFromCache++; } }
เงื่อนไขนี้จะเช็คตัวแปร $multiple_ids โดยฟังก์ชั่น count นับจำนวนใน array และเช็คว่าตัวแปรนี้เป็น array หรือไม่โดยฟังก์ชั่น is_array อีกแล้ว และเมื่อเป็นจริงก็ถูกจับใส่ foreach เพื่อวนเอาค่าในตัวแปร $multiple_ids ออกมาทีละตัวใส่ในตัวแปร $_memberValue และส่งตัวแปร $_memberValue เข้าไปในฟังก์ชั่น _fetchFromCache ครับ ปัญหาอยู่ตรงนี้ครับ ถ้าค่าใน $multiple_ids ไม่ใช่ array จะถูกเข้าฟังก์ชั่น intval ก่อนนำไปคิวรี่ในฟังก์ชั่น _fetchFromCache ($member_value = intval( $member_key );)
ทดสอบการโจมตีด้วย Firefox + HackBar เมื่อค่าในตัวแปร $multiple_ids ไม่ใช่ array
สังเกตว่าผมใส่ id=1icheernoom แต่เวลาคิวรี่จะเหลือแค่ 1 เพราะถูกฟังก์ชั่น intval ตัดออกไปเหลือแต่ตัวเลข แต่ถ้าค่าในตัวแปร $multiple_ids เป็น array
Array ช่องที่ 0 (id[]) คือ 1icheernoom และเมื่อนำไปคิวรี่ยังเป็น 1icheernoom อยู่เพราะไม่ได้เข้าฟังก์ชั่น intval ไว้ ช่องโหว่จึงเกิดขึ้นตรงนี้ครับ
เมื่อนำคิวรี่ที่ผมไฮท์ไลท์ไว้ไปคิวรี่ใน phpMyAdmin ก็จะได้เป็น
ผลลัพธ์ของ COUNT(*) FROM members คือ 1 เพราะตอนทดสอบมีแค่ admin คนเดียว และเนื่องจากช่องโหว่นี้เป็น Error based และ IP.Board ได้มีการเก็บ SQL Error ไว้ในไฟล์ /cache/sql_error_latest.cgi (อ้างอิงจากในโค้ด python) ทำให้สามารถไปดึงค่าที่ Error นั้นออกมาได้ นับถือคนเจอบัคนี้เลย ชาบูๆ (=/\=)
ถึงตอนนี้ Patch สำหรับแก้ไขช่องโหว่ IP.Board <= 3.4.7 SQL Injection ได้ออกมาแล้วครับและแก้ปัญหานี้โดย ถ้าค่าในตัวแปร $multiple_ids เป็น array ก็ใช้ฟังก์ชั่น intval ครอบไว้ทุกค่าใน array โดยฟังก์ชั่น array_map ครับ
$multiple_ids = array_map( 'intval', $member_key );
เป็นการส่งฟังก์ชั่น intval เข้าไปทำกับทุกค่าใน array ของตัวแปร $member_key ครับ
Ref : IP.Board version 3.4.7 (latest) suffers from a SQL injection vulnerability.
Ref : IP.Board 3.3.x, 3.4.x Security Update
Ref : 2600 Thailand
ไม่มีความคิดเห็น:
แสดงความคิดเห็น