浅谈php反序列化漏洞

php反序列化漏洞,也叫php对象注入

php反序列化漏洞学习
合上吧,没有新姿势。

序列化与反序列化

php中有两个函数serialize()unserialize()

serialize()

把复杂的数据类型压缩到一个字符串中 数据类型可以是数组,字符串,对象等。测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Test{
private $flag = 'abc';
protected $test = 'kkk';
public $test1 = 'aaa';
public function set_flag($flag){
$this->flag = $flag;
}
public function get_flag(){
return $this->flag;
}
}
$obj = new Test();
$obj->set_flag('fff');
$data = serialize($obj);
echo $data;

序列化打印的结果为

1
2
O:4:"Test":3:{s:10:"Testflag";s:3:"fff";s:7:"*test";s:3:"kkk";s:5:"test1";s:3:"aaa";}
O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}

嗯,如果你发现这个问题,那么说明你认真地思考了,这其实涉及到了 PHP 的属性的访问权限问题,序列化为了能把整个类对象的各种信息完完整整的压缩,格式化,必然也会将属性的权限序列化进去,我们发现我定义的类的属性有三种 private protected 和 默认的 public(写不写都一样),其中

(1)Puiblic 权限:

他的序列化规规矩矩,按照我们常规的思路,该是几个字符就是几个字符,你看test1 属性

(2)Private 权限:

该权限是私有权限,也就是说只能 test类使用,在序列化的时候一定要在 private 属性前面加上自己的名字,但是好像长度还是不对,还少了两个,怎么回事?

这样,我们将其序列化的结果存入一个文件中,我们使用hexdump看一下内部的结构

我们看到 test 的前后出现了两个 %00 ,也就是空白符,在私有属性序列化的时候格式是

1
%00类名%00属性名

(3)Protected 权限:

我们看 hexdump 的结果

如图所示:

格式为

1
%00*%00属性名

unserialize()

恢复原先被序列化的变量,测试代码如下:

1
2
3
4
5
6
7
8
<?php
class xianyu{
public $test = '123';
}
$class = 'O:6:"xianyu":1:{s:4:"test";s:3:"123";}';
$class_unser = unserialize($class);
print_r($class_unser);
?>

反序列化的打印结果如下

1
xianyu Object ( [test] => 123 )

反序列化的数据本质上来说是没有危害的

用户可控数据进行反序列化是存在危害的

可以看到,反序列化的危害,关键还是在于可控或不可控。

PHP(反)序列化有关的常用魔法函数

__construct()

创建一个新的类时,自动调用该方法

__destruct()

当一个类被销毁时自动调用该方法

__toString()

当把一个类当作字符串使用时就会自动调用该方法

__sleep()

当调用serialize()函数时,PHP 将试图在序列动作之前调用该对象的成员函数 __sleep()。这样就允许对象在被序列化之前做任何清除操作

__wakeup

当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数
unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值。返回的是转换之后的值,可为 integer、float、string、array 或 object。如果传递的字符串不可解序列化,则返回 FALSE。

__invoke

当把一个类当作函数使用时,就会自动调用该方法

仅在php5.30+支持

更多关于php魔术方法的使用请参考php魔术方法

php Session序列化

简介

PHP 内置了多种处理器用于存取 $_SESSION 数据时会对数据进行序列化和反序列化。

处理器 对应的存储格式
php 键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize (php>=5.5.4) 经过 serialize() 函数反序列处理的数组

关键点在于,如果脚本中设置的序列化处理器与php.ini设置的不同,或者两个脚本注册session使用的序列化处理器不同,那么就会出现安全问题。原因是未正确处理|,如果以php_serilize方式存入,比如我们构造出| 伪造的序列化值存入,但之后解析又是用的php处理器的话,那么将会反序列化伪造的数据(|之前当作键名,|之后当作键值)。(php5.6.13版本以前是第一个变量解析错误注销第一个变量,然后解析第二个变量,但是5.6.13以后如果第一个变量错误,直接销毁整个session)。

那么我们通过什么方式将数据注入到session中呢?
一方面,开发者本身将用户可控的数据传进了session,比如joomla等;
另一方面,可通过配置不当可造成session被控。当session.upload_progress.enabled打开时,php会记录上传文件的进度,在上传时会将其信息保存在$_SESSION中。详情见https://bugs.php.net/bug.php?id=71101

实际利用

  1. session.auto_start=On

    1
    2
    3
    4
    <?php
    ini_set('session.serialize_handler', 'php');
    session_start();
    $_SESSION['a'] = $_GET['a'];

    由于session.auto_start是打开的,所以它会先以默认的serializer handlers存入,但在读取时却是以php序列化的方式,我们可以注入|,来使得后面任意伪造的序列化字符串,以此来利用反序列化漏洞。

  2. session.auto_start=Off
    当两个脚本的序列化处理器不同就会有问题出现
    test1.php

1
2
3
4
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['a'] = $_GET['a'];

test2.php

1
2
3
4
5
6
7
8
9
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class xianyu123{
var $hi = 'xianyu123';
function __destruct() {
echo $this->hi;
}
}

构造好链接:

1
http://127.0.0.1/test1.php?a=|O:9:"xianyu123":1:{s:2:"hi";s:9:"xianyu123";}

然后访问test2.php,就会执行代码,输出xianyu123

Reference

  1. 有趣的php反序列化总结

  2. php序列化

  3. PHP反序列化漏洞

本文标题:浅谈php反序列化漏洞

文章作者:xianyu123

发布时间:2018年12月11日 - 18:57

最后更新:2019年08月22日 - 15:09

原始链接:http://0clickjacking0.github.io/2018/12/11/浅谈php反序列化漏洞/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------    本文结束  感谢您的阅读    -------------