PHP程式碼審計05之正則使用不當

雪痕*發表於2020-12-29

前言

根據紅日安全寫的文章,學習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 字母和數字
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結果如下:

例項分析

通過例題和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如下:
http://www.xxx.com/index.php?g=Template&m=Style&a=delete&dir=.....///Application/Install/&file=install.lock

現在訪問install.php,發現確實刪除了,如下圖:

小結

通過這篇文章的學習與講解,是不是對PHP的正則瞭解的更多了呢,下一篇文章會對parse_str函式缺陷進行學習和講解。一起加油吧!

相關文章