PHP反序列化字符串逃逸

PHP反序列化字符串逃逸

虽然是大一就学的知识点,但是前段时间做题的时候发现又忘了。所以把他记录下来,方便下次查阅。

反序列化字符串逃逸主要出现在字符串增加或者减少的情况。

前置知识:

php的序列化数据中,使用;来分隔不同的字段,使用}来作为结尾。并且序列化时,严格按照字段的长度来读取字符。反序列化的过程严格按照序列化规则来进行反序列化,否则会反序列化失败。

可以反序列化类中不存在的元素。

image-20230728212301109

在反序列化不存在元素时需要注意前面的成员数需要与后面的成员数相符合

字符增加/减少函数示例

1
2
3
4
5
6
7
8
<?php
function b($data){
return str_replace("aa","bbbb",$data);//字符串增加
}

function a($data){
return str_replace("bbbb","aa",$data);//字符串减少
}

字符串增加

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function b($data){
return str_replace("aa","bbbb",$data);//字符串增加
}

class test{
public $student="aa";
public $tecacher="other";
#public $test;
}

$a = new test();
$a=serialize($a);
#$b=$a;
echo $b=b($a)."\n";
var_dump(unserialize($b));

反序列化结果

1
O:4:"test":3:{s:7:"student";s:2:"bbbb";s:8:"tecacher";s:5:"other";s:4:"test";N;}

可以看到输入的aa已经被替换成了bbbb。字符串实际长度增加了,但是字符串的读取长度并没有增加。所以逃逸了的两个字符bb。

这里我们可以构造;来提前闭合反序列化数据,实现逃逸。

所以字符串增加利用的是插入与增加字符相同长度的反序列化数据,然后再利用闭合符号来让插入数据被当做正常内容进行反序列化。

比如可以把teacher改成如下数据

1
$tecacher='aaaa";}x';

成功反序列化

1
2
3
4
5
6
7
8
O:4:"test":2:{s:7:"student";s:5:"other";s:8:"tecacher";s:8:"bbbbbbbb";}x";}
object(test)#1 (2) {
["student"]=>
string(5) "other"
["tecacher"]=>
string(8) "bbbbbbbb"
}

现在把上面代码test属性注释去掉,然后通过字符串逃逸给test反序列化赋值(一共逃逸26个字符)

1
$tecacher='aaaaaaaaaaaaaaaaaaaaaaaaaa";s:4:"test";s:5:"hello";}'

成功反序列化

1
2
3
4
5
6
7
8
9
O:4:"test":3:{s:7:"student";s:5:"other";s:8:"tecacher";s:52:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"test";s:5:"hello";}";s:4:"test";N;}
object(test)#1 (3) {
["student"]=>
string(5) "other"
["tecacher"]=>
string(52) "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
["test"]=>
string(5) "hello"
}

字符串减少

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function a($data){
return str_replace("aaaa","bb",$data);//字符串增加
}

class test{
public $student="aaaaaaaa";
public $tecacher="other";
#public $test;
}

$a = new test();
$a=serialize($a);
#$b=$a;
echo $b=a($a)."\n";
var_dump(unserialize($b));

反序列化结果

1
O:4:"test":2:{s:7:"student";s:8:"bbbb";s:8:"tecacher";s:5:"other";}

可以看到student字段的长度标识与其实际字段长度不符合,缺少了4个字符。所以在反序列化过程中会继续向后读取四个字符,也就是读取到:。当然这样的结果是导致反序列化失败。

对于字符串减少的利用在于让它把我们不需要的字符都当做字段内容读取掉,那么我们在后面构造的内容就会被当做正常内容进行反序列化,从而实现反序列化任意类

示例如下

首先计算student字段内容后面的长度

1
";s:8:"tecacher";s:XX:"//23个字符

所以需要构造12组aaaa,并在上面的字段内容开头补上一个字符。同时利用teacher参数构造我们想反序列化的类

参数设置如下

1
2
public $student="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
public $tecacher='a";s:8:"tecacher";s:6:"hacker";}';

成功反序列化

image-20230728214400510

tips

最后提一个我自己在做题的时候遇到的小坑,关于不同类型的成员属性在反序列化后的长度变化

  • public无标记,变量名不变,长度不变: s:2:”op”;i:2;
  • protected在变量名前添加标记\00*\00,长度+3: s:5:”\00*\00op”;i:2;
  • private在变量名前添加标记\00(classname)\00,长度+2+类名长度: s:17:”\00FileHandler_Z\00op”;i:2;

PHP反序列化字符串逃逸
https://sammylingsj.github.io/2023/07/29/PHP反序列化字符串逃逸/PHP反序列化字符串逃逸/
作者
s4mmy
发布于
2023年7月29日
许可协议