0x01 背景
首先我們瞭解下寬位元組注入,寬位元組注入源於程式員設定MySQL串連時錯誤配置為:set character_set_client=gbk,這樣配置會引發編碼轉換從而導致的注入漏洞。具體原理如下:
1.正常情況下當GPC開啟或使用addslashes函數過濾GET或POST提交的參數時,駭客使用的單引號 ‘ 就會被轉義為: \’;
2.但如果存在寬位元組注入,我們輸入%df%27時首先經過上面提到的單引號轉義變成了%df%5c%27(%5c是反斜線\),之後在資料庫查詢前由於使用了GBK多位元組編碼,即在漢字編碼範圍內兩個位元組會被編碼為一個漢字。然後MySQL伺服器會對查詢語句進行GBK編碼即%df%5c轉換成了漢字“運”(註:GBK的漢字編碼範圍見附錄),而單引號逃逸了出來,從而造成了注入漏洞。
現在基本上都會將mysql的串連配置為“set character_set_client=binary”來解決這個問題,所以這篇文章將介紹出現在php中因為字元編碼導致的注入問題。
思路來源於FreeBuf:
http://www.freebuf.com/articles/web/31537.html
漏洞來源於烏云:
http://www.wooyun.org/bugs/wooyun-2014-063219
0x02 環境搭建
看背景我們使用了低版本的74cms程式,版本為3.4(20140310)
①源碼網上可以搜到,我打包了一份: http://pan.baidu.com/s/1c1mLCru
②解壓到www的74cms(20140310)目錄下,瀏覽器訪問http://localhost/74cms(20140310)),然後按照提示一步步安裝即可,安裝遇到問題請自行百度或Google,成功後訪問如:
0x03 漏洞分析
Part1:源碼結構
源碼的結構比較清晰,應該是審計過最清晰的結構了,主要有下面三塊內容:
index.php引入了common.inc.php檔案,我們跟進common.inc.php,發現了處理gpc的函數:
if (!empty($_GET)){$_GET = addslashes_deep($_GET);}if (!empty($_POST)){$_POST = addslashes_deep($_POST);}$_COOKIE = addslashes_deep($_COOKIE);$_REQUEST = addslashes_deep($_REQUEST);
可以看到,服務端處理GET和POST請求的變數時都會做addslashes處理。
而且74cms為了防止寬位元組注入,將MySQL串連設定為二進位讀取,配置在/include/mysql.class.php中:
function connect($dbhost, $dbuser, $dbpw, $dbname = '', $dbcharset = 'gbk', $connect=1){ $func = empty($connect) ? 'mysql_pconnect' : 'mysql_connect'; if(!$this->linkid = @$func($dbhost, $dbuser, $dbpw, true)){ $this->dbshow('Can not connect to Mysql!'); } else { if($this->dbversion() > '4.1'){ mysql_query( "SET NAMES gbk"); if($this->dbversion() > '5.0.1'){ mysql_query("SET sql_mode = ''",$this->linkid); //character_set_client=binary即二進位方式 mysql_query("SET character_set_connection=".$dbcharset.", character_set_results=".$dbcharset.", character_set_client=binary", $this->linkid); } } }
接下來看看php中iconv函數的使用會造成什麼樣的後果。
Part2:審計過程
注入一分析:
1.在/plus/ajax_user.php註冊處:
elseif ($act=='do_reg'){$captcha=get_cache('captcha');if ($captcha['verify_userreg']=="1"){ $postcaptcha=$_POST['postcaptcha']; if ($captcha['captcha_lang']=="cn" && strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $postcaptcha=iconv("utf-8",QISHI_DBCHARSET,$postcaptcha); } if (empty($postcaptcha) || empty($_SESSION['imageCaptcha_content']) || strcasecmp($_SESSION['imageCaptcha_content'],$postcaptcha)!=0) { exit("err"); }}require_once(QISHI_ROOT_PATH.'include/fun_user.php');$username = isset($_POST['username'])?trim($_POST['username']):exit("err");$password = isset($_POST['password'])?trim($_POST['password']):exit("err");$member_type = isset($_POST['member_type'])?intval($_POST['member_type']):exit("err");$email = isset($_POST['email'])?trim($_POST['email']):exit("err");if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0){//對註冊的名字進行utf-8到GBK的編碼轉換$username=iconv("utf-8",QISHI_DBCHARSET,$username);$password=iconv("utf-8",QISHI_DBCHARSET,$password);} $register=user_register($username,$password,$member_type,$email);
這裡我們思考下“錦”這個字,它的utf-8編碼是e98ca6,它的gbk編碼是e55c,而上面提到過反斜線\正好為5c。
所以如果我們將username設定為:錦’,首先經過addlashes函數或GPC對單引號轉義變為:錦\’,然後這裡註冊時會經過icnov函數會對”錦”轉化為gbk編碼,最後就是:%e5%5c%5c%27。反斜線被轉義了(%5c%5c),從而單引號逃逸出來引發注入漏洞。
2.我們繼續跟進$register=user_register($username,$password,$member_type,$email);
這裡的user_register函數,在/include/fun_user.php裡:
//檢查簡曆的完成程度//註冊會員function user_register($username,$password,$member_type=0,$email,$uc_reg=true){global $db,$timestamp,$_CFG,$online_ip,$QS_pwdhash;$member_type=intval($member_type);//這裡是用get_user_inusername函數來判斷使用者名稱是否已經存在,我們跟進$ck_username=get_user_inusername($username);$ck_email=get_user_inemail($email);... ...return $insert_id;}
3.繼續跟進get_user_inusername函數,在/include/fun_user.php裡:
function get_user_inusername($username){global $db;//帶入查詢,可注入~$sql = "select * from ".table('members')." where username = '{$username}' LIMIT 1";return $db->getone($sql);}
注入二分析:
在plus/ajax_street.php中:
elseif($act == 'key'){$key=trim($_GET['key']);if (!empty($key)){if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) //對參數key進行utf-8到GBK編碼的轉換$key=iconv("utf-8",QISHI_DBCHARSET,$key);//帶入查詢,可以注入$result = $db->query("select * from ".table('category')." where c_alias='QS_street' AND c_name LIKE '%{$key}%' ");//將查詢結果輸出到頁面中,可回顯while($row = $db->fetch_array($result)){ if ($listtype=="li") { $htm.="{$row['c_name']}"; } else { $_GET['streetid']=$row['c_id']; $url=url_rewrite('QS_street',$_GET); $htm.="
{$row['c_name']}{$row['stat_jobs']}"; };}if (empty($htm)){$htm="沒有找到關鍵字: {$key} 相關道路!";}exit($htm);}}
這裡分析發現頁面將查詢結果回顯出來,構造一些union的查詢語句即可擷取資料庫的敏感資訊。
0x04 漏洞證明
我們使用注入二(有回顯)的來做證明
發現74cms的category表有9個欄位,所以構造擷取資料庫使用者和相關資訊的POC:
http://localhost/74cms(20140310)/plus/ajax_street.php?act=key&key=%E9%8C%A6%27%20union%20select%201,2,3,user(),5,6,7,database(),9%23
查看sql語句發現查詢語句裡反斜線被轉移,單引號成功逃逸出來:
最後,有興趣的同學可以繼續擷取其它的管理員賬戶等相關欄位的資訊。
附GBK的漢字編碼範圍:
漢字區包括:
a. GB 2312 漢字區。即 GBK/2: B0A1-F7FE。收錄 GB 2312 漢字 6763 個,按原順序排列。
b. GB 13000.1 擴充漢字區。包括:
(1) GBK/3: 8140-A0FE。收錄 GB 13000.1 中的 CJK 漢字 6080 個。
(2) GBK/4: AA40-FEA0。收錄 CJK 漢字和增補的漢字 8160 個。CJK 漢字在前,按 UCS 代碼大小排列;增補的漢字(包括部首和構件)在後,按《康熙字典》的頁碼/字位排列。
可以看到,GBK編碼中的兩個字元是一個漢字,第一個字元需要大於128。
原文地址:
http://www.cnbraid.com/2016/02/28/sql4/