1. 程式人生 > 實用技巧 >PHP程式碼審計05之正則使用不當

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 | 字母和數字 |
| 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函式缺陷進行學習和講解。一起加油吧!