PHP代碼審計05之正則使用不當
前言
根據紅日安全寫的文章,學習PHP代碼審計的第五節內容,題目均來自PHP SECURITY CALENDAR 2017,講完題目會用一道CTF的題目和實例來加深鞏固。這是之前寫的,有興趣可以去看看:
PHP代碼審計01之in_array()函數缺陷
PHP代碼審計02之filter_var()函數缺陷
PHP代碼審計03之實例化任意對象漏洞
PHP代碼審計04之strpos函數使用不當
漏洞分析
下面看題目,代碼如下:
題目漏洞是正則使用不嚴謹導致任意文件刪除的漏洞,現在來具體分析,引起漏洞的地方在上面代碼的21行,這裡用到了preg_replace()函數,我們打開PHP手冊來看看對這個函數的定義如下:
了解了函數的用法,看上面代碼,[^a-z.-_] 表示匹配除了 a 字符到 z 字符和. 字符到 _ 字符之間的所有字符,但是沒有考慮到目錄路徑字符。這就直接可以任意刪除文件,例如構造如下參數:
action=delete&data=../../config.php
將刪除config.php文件。
CTF練習
通過上面的講解,來用一道CTF題目來練習一下,也是關於正則的問題,先看代碼:
//index.php
<?php
include 'flag.php';
if ("POST" == $_SERVER['REQUEST_METHOD'])
{
$password = $_POST['password'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password))
{
echo 'Wrong Format';
exit;
}
while (TRUE)
{
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $password, $arr))
break;
$c = 0;
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
if ("42" == $password) echo $flag;
else echo 'Wrong password';
exit;
}
}
highlight_file(__FILE__);
?>
//flag.php
<?php $flag = "HRCTF{Pr3g_R3plac3_1s_Int3r3sting}";?>
這道題目考察了是否熟悉PHP正則表達的字符類,大體是下面這個表格:
alnum 字母和數字 | header |
---|---|
alpha 字母 | |
ascii 0 – 127的ascii字符 | |
blank 空格和水平製表符 | |
cntrl 控制字符 | |
digit 十進制數(same as \d) | |
graph 打印字符, 不包括空格 | |
lower 小寫字母 | |
print 打印字符,包含空格 | |
punct 打印字符, 不包括字母和數字 | |
space 空白字符 (比\s多垂直製表符) | |
upper 大寫字母 | |
word 單詞字符(same as \w) | |
xdigit 十六進制數字 |
想要更加詳細的了解,建議翻閱PHP手冊,了解了字符類,下面來分析代碼,上面一共三處正則表達,第一處如下:
if (0 >= preg_match(‘/[1]{12,}$/’, $password))
它表示的含義是匹配到可打印字符12往上包含12,^表示必須某類字符開頭,$表示必須某類字符結尾。
第二處正則如下:
$reg = ‘/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/’;
if (6 > preg_match_all($reg, $password, $arr))
break;
它表示的含義是,把連續的字符,數字,大寫,小寫作為一段,最少分成六段,比如Test+0He 會分為T est + 0 H e六段。
下面看第三處正則:
$ps = array(‘punct’, ‘digit’, ‘upper’, ‘lower’);
foreach ($ps as $pt)
{
if (preg_match(“/[[:$pt:]]+/”, $password))
$c += 1;
}
if ($c < 3) break;
if (“42” == $password) echo $flag;
這裡的含義是輸入的字符必須包含字符,數字,大寫,小寫其中的三種往上。最後與42進行弱類型比較,都符合就輸出flag,現在都解讀清楚了,讓咱們構造payload結果如下:
實例分析
通過例題和CTF題目的講解,是不是感覺棒棒的,現在咱們來分析實例吧,實例是LvyeCMS3.1,是基於ThinkPHP3.2.3框架。這個實例存在的漏洞也是函數使用不規範被繞過,導致任意文件刪除。下面來具體分析:
先查看入口文件index.php
可以看到公共目錄,應用目錄等一些信息。接下來再看看目錄結構:
而漏洞在Application/Template/Controller/StyleController.class.php
文件中,具體如下:
看代碼第117行,這裡是獲取目錄路徑,參數也是我們可以控制的,再向後看,用到了str_replace()函數,它是個字符串替換函數,具體說明如下:
再這裡起到的作用就是將’..\’, ‘../’, ‘./’, ‘.\’替換為空。但是這裡是可以繞過的,如果我們輸入…..///呢,會發生什麼?是不是正好構造成了../,舉個小例子會更清楚,如下:
構造出../我們就可以穿越目錄了,現在訪問install.php文件會提示已安裝,如下圖:
然後嘗試刪除lvyecms/Application/Install/目錄下的 install.lock 文件,構造payload如下:
//www.xxx.com/index.php?g=Template&m=Style&a=delete&dir=…..///Application/Install/&file=install.lock
現在訪問install.php,發現確實刪除了,如下圖:
小結
通過這篇文章的學習與講解,是不是對PHP的正則了解的更多了呢,下一篇文章會對parse_str函數缺陷進行學習和講解。一起加油吧!
-
[:graph:] ↩︎