程式碼審計Day13 - 特定場合下addslashes函式的繞過
本文由紅日安全成員: l1nk3r 編寫,如有不當,還望斧正。
前言
大家好,我們是紅日安全-程式碼審計小組。最近我們小組正在做一個PHP程式碼審計的專案,供大家學習交流,我們給這個專案起了一個名字叫 ofollow,noindex" target="_blank"> PHP-Audit-Labs 。現在大家所看到的系列文章,屬於專案 第一階段 的內容,本階段的內容題目均來自 PHP SECURITY CALENDAR 2017 。對於每一道題目,我們均給出對應的分析,並結合實際CMS進行解說。在文章的最後,我們還會留一道CTF題目,供大家練習,希望大家喜歡。下面是 第13篇 程式碼審計文章:
Day 13 - Turkey Baster
程式碼如下:
這是一道典型的使用者登入程式,從程式碼來看,考察的應該是通過 SQL%E6%B3%A8%E5%85%A5/">SQL注入 繞過登陸驗證。程式碼 第33行 ,通過 POST 方式傳入 user 和 passwd 兩個引數,通過 isValid() 來判斷登陸是否合法。我們跟進一下 isValid() 這個函式,該函式主要功能程式碼在 第12行-第22行 ,我們看到 13行 和 14行 呼叫 sanitizeInput() 針對 user 和 password 進行相關處理。
跟進一下 sanitizeInput() ,主要功能程式碼在 第24行-第29行 ,這裡針對輸入的資料呼叫 addslashes 函式進行處理,然後再針對處理後的內容進行長度的判斷,如果長度大於20,就只擷取前20個字元。 addslashes 函式定義如下:
addslashes — 使用反斜線引用字串
string addslashes ( string $str )
作用:在單引號(')、雙引號(")、反斜線()與 NUL( NULL 字元)字元之前加上反斜線。
我們來看個例子:
那這題已經過濾了單引號,正常情況下是沒有注入了,那為什麼還能導致注入了,原因實際上出在了 substr 函式,我們先看這個函式的定義:
substr — 返回字串的子串
string substr ( string $string , int $start [, int $length ] )
作用:返回字串 string
由 start
和 length
引數指定的子字串。
我們來看個例子:
那麼再回到這裡,我們知道反斜槓可以取消特殊字元的用法,而注入想要通過單引號閉合,在這道題裡勢必會引入反斜槓。所以我們能否在反斜槓與單引號之間截斷掉,只留一個反斜槓呢?答案是可以,我們看個以下這個例子。
在這個例子中,我們直接使用題目程式碼中的過濾程式碼,並且成功在反斜槓和單引號之間截斷了,那我們把這個payload帶入到題目程式碼中,拼接一下 第17行-第19行 程式碼中的sql語句。
select count(p) from user u where user = '1234567890123456789\' AND password = '$pass'
這裡的sql語句由於反斜槓的原因, user = '1234567890123456789\' 最後這個單引號便失去了它的作用。這裡我們讓 pass=or 1=1# ,那麼最後的sql語句如下:
select count(p) from user where user = '1234567890123456789\' AND password = 'or 1=1#'
這時候在此SQL語句中, user 值為 1234567890123456789\' AND password = ,因此我們可以保證帶入資料庫執行的結果為 True ,然後就能夠順利地通過驗證。
所以這題最後的 payload 如下所示:
user=1234567890123456789'&passwd=or 1=1#
例項分析
這裡的例項分析,我們選擇 蘋果CMS視訊分享程式 8.0 進行相關漏洞分析。漏洞的位置是在 inccommontemplate.php ,我們先看看相關程式碼:
這裡程式碼的 第三行-第四行 位置, $lp['wd'] 變數位置存在字串拼接,很明視訊記憶體在 sql注入 ,但是這個cms具有一些通用的注入防護,所以我們從頭開始一步步的看。
首先在 incmodulevod.php 檔案中的,我們看到 第一行 程式碼當 $method=search 成立的時候,進入了 第3行 中的 be("all", "wd") 獲取請求中 wd 引數的值,並且使用 chkSql() 函式針對 wd 引數的值進行處理。部分關鍵程式碼如下所示:
跟進一下 be() 函式,其位置在 inccommonfunction.php 檔案中,關鍵程式碼如下:
這部分程式碼的作用就是對 GET,POST,REQUEST 接收到的引數進行 addslashes 的轉義處理。根據前面針對 be("all", "wd") 的分析,我們知道 wd 引數的值是通過 REQUEST 方式接收,並使用 addslashes 函式進行轉義處理。再回到 incmodulevod.php 檔案中的,我們跟進一下 chkSql() 函式,該函式位置在 inccommon360_safe3.php 檔案中,具體程式碼如下:
分析一下這部分程式碼的作用,其實就是在 第8行-第12行 針對接收到的的變數進行迴圈的 urldecode (也就是url解碼)動作,然後在 第15行 ,使用 StopAttack 函式解碼後的資料進行處理,最後將處理後的資料通過 htmlEncode 方法進行最後的處理,然後返回處理之後的值。
我們先跟進一下 StopAttack 函式,該函式位置在 inccommon360_safe3.php 檔案中,我們擷取部分相關程式碼如下:
我們看到程式碼的 第13行-第19行 呼叫正則進行處理,而相關的正則表示式是 $ArrFiltReq** 變數。這裡 **第13行** 的 **$ArrFiltReq 變數就是前面傳入的 $getfilter ,即語句變成:
preg_match("/".$getfilter."/is",1)
我們跟進一下 $getfilter 變數。該變數在 inccommon360_safe3.php 檔案中,我們擷取部分相關程式碼如下:
這串程式碼的功能顯而易見,就是檢測 GET,POST,COOKIE 中的惡意資料。剛剛在 chkSql() 函式最後有串程式碼是: return htmlEncode($s); ,我們跟進一下 htmlEncode 函式。該函式位置在 inccommonfunction.php 檔案中,相關程式碼如下:
這段程式碼的功能是針對 & 、 ' 、 空格 、 " 、 TAB 、 回車 、 換行 、 大於小於號 等符號進行實體編碼轉換。但是這裡百密一疏,沒有針對其他的空白字元和反斜槓進行處理。這裡先埋下一個伏筆,我們繼續往下看。
首先注入點是在 inccommontemplate.php ,相關程式碼如下:
我們繼續看看這個 $lp['wd'] 的值是怎麼獲取的,在 inccommontemplate.php 檔案中找到其相關程式碼:
上圖 第13行 ,當 P['wd'] 不為空的時候, $lp['wd']** 是從 **P["wd"]** 中獲取到資料的。根據前面我們的分析,在 **inc\module\vod.php** 檔案中的存在這樣一行程式碼: **$tpl->P["wd"] = $wd;
而 wd 是可以從 REQUEST 中獲取到,所以這裡的 wd 實際上是可控的。
漏洞驗證
現在我們需要針對漏洞進行驗證工作,這就涉及到POC的構造。在前面分析中,我們知道 htmlEncode 針對 & 、 ' 、 空格 、 " 、 TAB 、 回車 、 換行 、 大於小於號 進行實體編碼轉換。但是這裡的注入型別是字元型注入,需要引入單引號來進行閉合,但是 htmlEncode 函式又對單引號進行了處理。因此我們可以換個思路。
我們看到注入攻擊的時候,我們的 $lp['wd'] 引數可以控制SQL語句中的兩個位置,因此這裡我們可以通過引入 反斜槓 進行單引號的閉合,但是針對前面的分析我們知道其呼叫了 addslashes 函式進行轉義處理,而 addslashes 會對 反斜槓 進行處理,但是這裡對使用者請求的引數又會先進行 url解碼 的操作,因此這裡可以使用 雙url編碼 繞過 addslashes 函式。
POST /maccms8/index.php?m=vod-search HTTP/1.1 Host: 127.0.0.1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 98 Connection: keep-alive Upgrade-Insecure-Requests: 1 wd=))||if((select%0b(select(m_name)``from(mac_manager))regexp(0x5e61)),(`sleep`(3)),0)#%25%35%63
payload傳到程式裡,經過拼接後的資料庫語句如下所示:
漏洞修復
這裡的防禦手段其實已經很多了,但就是因為這麼多防禦手段結合在一起出現了有趣的繞過方式。
function htmlEncode($str) { if (!isN($str)){ $str = str_replace(chr(38), " ",$str); $str = str_replace(">", ">",$str); $str = str_replace("<", "<",$str); $str = str_replace(chr(39), " ",$str); $str = str_replace(chr(32), " ",$str); $str = str_replace(chr(34), """,$str); $str = str_replace(chr(9), "",$str); $str = str_replace(chr(13), "<br />",$str); $str = str_replace(chr(10), "<br />",$str); $str = str_replace(chr(92), "<br />",$str);//新增修復程式碼 } return $str; }
反斜槓的ascii碼是92,這裡新增一行程式碼處理反斜槓。
結語
看完了上述分析,不知道大家是否對 htmlentities 函式在使用過程中可能產生的問題,有了更加深入的理解,文中用到的程式碼可以從 這裡 下載,當然文中若有不當之處,還望各位斧正。如果你對我們的專案感興趣,歡迎傳送郵件到 [email protected] 聯絡我們。 Day13 的分析文章就到這裡,我們最後留了一道CTF題目給大家練手,題目如下:
//index.php <?php require 'db.inc.php'; function dhtmlspecialchars($string) { if (is_array($string)) { foreach ($string as $key => $val) { $string[$key] = dhtmlspecialchars($val); } } else { $string = str_replace(array('&', '"', '<', '>', '(', ')'), array('&', '"', '<', '>', '(', ')'), $string); if (strpos($string, '&#') !== false) { $string = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string); } } return $string; } function dowith_sql($str) { $check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str); if ($check) { echo "非法字元!"; exit(); } return $str; } // 經過第一個waf處理 foreach ($_REQUEST as $key => $value) { $_REQUEST[$key] = dowith_sql($value); } // 經過第二個WAF處理 $request_uri = explode("?", $_SERVER['REQUEST_URI']); if (isset($request_uri[1])) { $rewrite_url = explode("&", $request_uri[1]); foreach ($rewrite_url as $key => $value) { $_value = explode("=", $value); if (isset($_value[1])) { $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1])); } } } // 業務處理 if (isset($_REQUEST['submit'])) { $user_id = $_REQUEST['i_d']; $sql = "select * from ctf.users where id=$user_id"; $result=mysql_query($sql); while($row = mysql_fetch_array($result)) { echo "<tr>"; echo "<td>" . $row['name'] . "</td>"; echo "</tr>"; } } ?>
//db.inc.php <?php $mysql_server_name="localhost"; $mysql_database="ctf";/** 資料庫的名稱 */ $mysql_username="root";/** MySQL資料庫使用者名稱 */ $mysql_password="root";/** MySQL資料庫密碼 */ $conn = mysql_connect($mysql_server_name, $mysql_username,$mysql_password,'utf-8'); ?>
//ctf.sql # Host: localhost(Version: 5.5.53) # Date: 2018-08-18 21:42:20 # Generator: MySQL-Front 5.3(Build 4.234) /*!40101 SET NAMES utf8 */; # # Structure for table "users" # DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `Id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `pass` varchar(255) DEFAULT NULL, `flag` varchar(255) DEFAULT NULL, PRIMARY KEY (`Id`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; # # Data for table "users" # /*!40000 ALTER TABLE `users` DISABLE KEYS */; INSERT INTO `users` VALUES (1,'admin','qwer!@#zxca','hrctf{R3qu3st_Is_1nterEst1ng}'); /*!40000 ALTER TABLE `users` ENABLE KEYS */;