PHP程式碼審計05之正則使用不當
阿新 • • 發佈:2020-12-29
#前言
根據紅日安全寫的文章,學習PHP程式碼審計的第五節內容,題目均來自**PHP SECURITY CALENDAR 2017**,講完題目會用一道CTF的題目和例項來加深鞏固。這是之前寫的,有興趣可以去看看:
[PHP程式碼審計01之in_array()函式缺陷](https://www.cnblogs.com/lxfweb/p/13729406.html)
[PHP程式碼審計02之filter_var()函式缺陷](https://www.cnblogs.com/lxfweb/p/13757525.html)
[PHP程式碼審計03之例項化任意物件漏洞](https://www.cnblogs.com/lxfweb/p/13822440.html)
[PHP程式碼審計04之strpos函式使用不當](https://www.cnblogs.com/lxfweb/p/13898553.html)
#漏洞分析
下面看題目,程式碼如下:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201228160937231-25522013.png)
題目漏洞是正則使用不嚴謹導致任意檔案刪除的漏洞,現在來具體分析,引起漏洞的地方在上面程式碼的21行,這裡用到了preg_replace()函式,我們開啟PHP手冊來看看對這個函式的定義如下:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201228162734726-1728777475.png)
瞭解了函式的用法,看上面程式碼,**[^a-z.-_]** 表示匹配除了 a 字元到 z 字元和. 字元到 _ 字元之間的所有字元,但是沒有考慮到目錄路徑字元。這就直接可以任意刪除檔案,例如構造如下引數:
`action=delete&data=../../config.php`
將刪除config.php檔案。
#CTF練習
通過上面的講解,來用一道CTF題目來練習一下,也是關於正則的問題,先看程式碼:
```
//index.php
= 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正則表達的字元類,大體是下面這個表格:
| alnum | 字母和數字 |
| ------------- | ------ |
| 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('/^[[:graph:]]{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結果如下:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229160015554-1041551907.png)
#例項分析
通過例題和CTF題目的講解,是不是感覺棒棒的,現在咱們來分析例項吧,例項是LvyeCMS3.1,是基於ThinkPHP3.2.3框架。這個例項存在的漏洞也是函式使用不規範被繞過,導致任意檔案刪除。下面來具體分析:
先檢視入口檔案index.php
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229172049949-152775478.png)
可以看到公共目錄,應用目錄等一些資訊。接下來再看看目錄結構:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229172548660-1222378931.png)
而漏洞在`Application/Template/Controller/StyleController.class.php`檔案中,具體如下:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229172950641-543274393.png)
看程式碼第117行,這裡是獲取目錄路徑,引數也是我們可以控制的,再向後看,用到了str_replace()函式,它是個字串替換函式,具體說明如下:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229173427362-702119022.png)
再這裡起到的作用就是將'..\\', '../', './', '.\\'替換為空。但是這裡是可以繞過的,如果我們輸入.....///呢,會發生什麼?是不是正好構造成了../,舉個小例子會更清楚,如下:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229174750873-1552218961.png)
構造出../我們就可以穿越目錄了,現在訪問install.php檔案會提示已安裝,如下圖:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229174955034-756372498.png)
然後嘗試刪除lvyecms/Application/Install/目錄下的 install.lock 檔案,構造payload如下:
http://www.xxx.com/index.php?g=Template&m=Style&a=delete&dir=.....///Application/Install/&file=install.lock
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229175524255-407732001.png)
現在訪問install.php,發現確實刪除了,如下圖:
![](https://img2020.cnblogs.com/blog/1996712/202012/1996712-20201229175854454-968611439.png)
#小結
通過這篇文章的學習與講解,是不是對PHP的正則瞭解的更多了呢,下一篇文章會對parse_str函式缺陷進行學習和講解。一起加