一道ISCC題引申的PHP正則複習

雲勺發表於2018-05-15

iscc中的一道web題“試試看”,描述為隨意開火

image.png

起初看url,以為是一道常規的檔案包含題,後面試了很多方法都出不來
最後受到其他師傅的啟發才得到payload
這裡有兩種payload都可以

http://118.190.152.202:8006/show.php?img=php://filter/resource=1.jpg/resource=show.php
http://118.190.152.202:8006/show.php?img=php://filter/resource=show.php|jpg
image.png

image.png

對這道題目的匹配規則很感興趣,在本地搭建進行仔細分析,也是對正則以及php函式的複習
在審計程式碼之前,先複習一下php的preg_match、strpos和file_get_contents等函式

1、preg_match函式用於正則匹配,第一個引數是要匹配的正則規則,第二個引數是被匹配的字串。後面的可選引數中,$matches是一個陣列,用於返回匹配的字串結果

# preg_match
(PHP 4, PHP 5, PHP 7)
preg_match — 執行匹配正規表示式
### 說明
int **preg_match** ( string `$pattern` , string `$subject` [, array `&$matches` [, int `$flags` = 0 [, int`$offset` = 0 ]]] )
搜尋`subject`與`pattern`給定的正規表示式的一個匹配.

2、strpos函式用於字串查詢,如果找到則返回位置,位置從0開始計算。如果沒有找到則返回false

# strpos
(PHP 4, PHP 5, PHP 7)
strpos — 查詢字串首次出現的位置
### 說明
int **strpos** ( string `$haystack` , [mixed]`$needle` [, int `$offset` = 0 ] )
返回 `needle` 在 `haystack` 中首次出現的數字位置。
如果提供了引數matches,它將被填充為搜尋結果。 $matches[0]將包含完整模式匹配到的文字, $matches[1] 將包含第一個捕獲子組匹配到的文字,以此類推。

3、file_get_contents函式用於文字讀取,可以獲得檔案內容,它更強大的地方在於可以通過http協議抓取內容

# file_get_contents
(PHP 4 >= 4.3.0, PHP 5, PHP 7)
file_get_contents — 將整個檔案讀入一個字串
### 說明
string **file_get_contents** ( string `$filename` [, bool `$use_include_path` = false [, resource`$context` [, int `$offset` = -1 [, int `$maxlen` ]]]] )

和 file()一樣,只除了 **file_get_contents()** 把檔案讀入一個字串。將在引數 `offset` 所指定的位置開始讀取長度為`maxlen` 的內容。如果失敗,**file_get_contents()** 將返回 **`FALSE`**。

**file_get_contents()** 函式是用來將檔案的內容讀入到一個字串中的首選方法。如果作業系統支援還會使用記憶體對映技術來增強效能。
> **Note**:
> 
> 如果要開啟有特殊字元的 URL (比如說有空格),就需要使用 [urlencode()]進行 URL 編碼。

本題中,經過註釋和改造後的主要程式碼如下

show.php
<?php
error_reporting(0);
ini_set(`display_errors`,`Off`);

include(`config.php`);

$img = $_GET[`img`];
if(isset($img) && !empty($img))
{
    if(strpos($img,`jpg`) !== false)
    {        
        // strpos拿`resource=`到$img中查詢,如果匹配到了則前者為真;注意這裡是全等
        // 如果沒有匹配到`/resource=.*jpg/i`正則模式則後者為真;
        if(strpos($img,`resource=`) !== false && preg_match(`/resource=.*jpg/i`,$img) === 0)
        {
            //滿足上述兩種情況,返回找不到檔案
            die(`File not found.`);
        }
        // 再次進行正則匹配,如果以php://filter開頭,並且字串中存在resource=加上任意不包含|的字串
        // 對$img進行左右兩邊空白或者預定符號的刪除,最後匹配結果存到$matches陣列
        preg_match(`/^php://filter.*resource=([^|]*)/i`,trim($img),$matches);
        // $matches[0]將包含完整模式匹配到的文字, $matches[1] 將包含第一個捕獲子組匹配到的文字,以此類推。
        var_dump($matches);
        if(isset($matches[1]))
        {
            $img = $matches[1];
        }
        echo "<br>";
        echo $img;
        header(`Content-Type: image/jpeg`);
        // 關鍵函式get_contents,去獲得檔案內容
        $data = get_contents($img);
        echo $data;
    }
    else
    {
        die(`File not found.`);
    }

}
else
{
    ?>
    <img src="1.jpg">
    <?php
}
?>    
config.php
<?php  
// 關鍵函式get_contents,去獲得檔案內容
function get_contents($img)
{
    // 如果$img中存在`jpg`,返回$img檔案內容
    if(strpos($img,`jpg`) !== false)
    {
        return file_get_contents($img);
    }
    // 否則返回$img的同時,設定返回頭為hmtl
    else
    {
        header(`Content-Type: text/html`);
        return file_get_contents($img);
    }
}
?>  

這裡通過實際payload在執行中的流程,對關鍵地方進行輸出,方便分析和檢視結果

0x01

首先分析show.php?img=php://filter/resource=config.php|jpg

邏輯中的第一個涉及preg_match的if語句中,只有在傳入的$img中匹配到”resource=”的同時,preg_match中$img匹配規則”/resource=.*jpg/i”匹配不到的情況下成立
在這裡不會對payload形成影響
關鍵點在接下來的正則匹配

// 再次進行正則匹配,如果以php://filter開頭,並且字串中存在resource=加上任意不包含|的字串
        // 對$img進行左右兩邊空白或者預定符號的刪除,最後匹配結果存到$matches陣列
        preg_match(`/^php://filter.*resource=([^|]*)/i`,trim($img),$matches);
        // $matches[0]將包含完整模式匹配到的文字, $matches[1] 將包含第一個捕獲子組匹配到的文字,以此類推。
        var_dump($matches);
        if(isset($matches[1]))
        {
            $img = $matches[1];
        }
        echo "<br>";
        echo $img;
        // header(`Content-Type: image/jpeg`);
        // 關鍵函式get_contents,去獲得檔案內容
        $data = get_contents($img);
        echo $data;

可以看到,匹配規則是要求以php://filter開頭,並且字串中存在resource=加上任意不包含|的字串
([^|]*)代表的意思就是排除|以外的字元,允許重複零次或多次,圓括號包裹則表示這是一個匹配的文字子組
匹配的結果儲存在$matches陣列中,並且$img會被覆蓋為$matches的第2個元素
這裡的關鍵在於$matches的第二個元素內容,第二個元素內容是圓括號包裹的([^|]*)子組的內容

image.png

經過正則後,$img已經被覆蓋,內容為config.php
在config.php中,將通過函式file_get_contents($img)去獲取指定檔案內容並且返回

    // 如果$img中存在`jpg`,返回$img檔案內容
    if(strpos($img,`jpg`) !== false)
    {
        return file_get_contents($img);
    }
    // 否則返回$img的同時,設定返回頭為hmtl
    else
    {
        header(`Content-Type: text/html`);
        return file_get_contents($img);
    }
0x02

接下來分析show.php?img=php://filter/resource=1.jpg/resource=config.php
其他流程和上面的一樣,只要字串中包含jpg就可以,關鍵在於

preg_match(`/^php://filter.*resource=([^|]*)/i`,trim($img),$matches);

payload進去之後的匹配結果將是後面一個resource=config.php,而不是resource=1.jpg,因此拿到的$matches的第二個元素也是config.php!

image.png


相關文章