php(phar)反序列化漏洞及各种绕过姿势
本文为看雪论坛优秀文章
看雪论坛作者ID:pank1s
一、简介
序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。简单来说就是我在一个地方构造了一个类,但我要在另一个地方去使用它,那怎么传过去呢?于是就想到了序列化这种东西,将对象先序列化为一个字符串(数据),后续需要使用的时候再进行反序列化即可得到要使用的对象,十分方便。
来看看官方手册( https://www. php.net/manual/zh/langu age.oop5.serialization.php )怎么说:
所有php里面的值都可以使用函数serialize()( https://www. php.net/manual/zh/funct ion.serialize.php )来返回一个包含字节流的字符串来表示。unserialize()( https://www. php.net/manual/zh/funct ion.unserialize.php )函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中反序列化一个对象,这个对象的类必须在反序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()( https://www. php.net/manual/zh/funct ion.spl-autoload-register.php )来实现。
php 将数据序列化和反序列化会用到两个函数:
serialize() 将对象格式化成有序的字符串。unserialize() 将字符串还原成原来的对象。
序列化的目的是方便数据的传输和存储,在PHP中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。
注意:php中创建一个对象和反序列化得到一个对象是有所不同的,例如创建一个对象一般会优先调用 __construct() 方法 ,而反序列化得到一个对象若 存在 __wakeup() 方法则会优先调用它而不去执行 __construct() 。
二、常见序列化格式介绍
基本上每个编程语言都有各自的序列化和反序列化方式,格式也各不相同
像有:
- 二进制格式
- 字节数组
- json字符串
- xml字符串
- python的opCode码 参考( https:// xz.aliyun.com/t/7436 )
简单例子
<?php
$arr = array('aa', 'bb', 'cc' => 'dd');
$serarr = serialize($arr);
echo $serarr;
var_dump($arr);
输出
a:3:{i:0;s:2:"aa";i:1;s:2:"bb";s:2:"cc";s:2:"dd";}
array(3) {
[0]=> string(2) "aa"
[1]=> string(2) "bb"
["cc"]=> string(2) "dd"
}
输出的这一串序列表示的是什么呢?a:3:{i:0;s:2:"aa";i:1;s:2:"bb";s:2:"cc";s:2:"dd";}a:array代表是数组,后面的3说明有三个属性。i:代表是整型数据int,后面的0是数组下标(O代表Object,也是类)。s:代表是字符串,后面的2是因为aa长度为2,是字符串长度值。后面类推。
同时要注意序列化后只有成员变量,没有成员函数。
注意如果变量前是protected,则会在变量名前加上\x00*\x00,private则会在变量名前加上\x00类名\x00,输出时一般需要url编码,如下:
<?php
class test {
protected $name;
private $pass;
function __construct($name, $pass) {
$this->name = $name;
$this->pass = $pass;
$a = new test('pankas', '123');
$seria = serialize($a);
echo $seria.'<br/>';
echo urlencode($seria);
直接输出输出则会导致不可见字符\x00的丢失。
O:4:"test":2:{s:7:"*name";s:6:"pankas";s:10:"testpass";s:3:"123";}
O%3A4%3A%22test%22%3A2%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A6%3A%22pankas%22%3Bs%3A10%3A%22%00test%00pass%22%3Bs%3A3%3A%22123%22%3B%7D
三、反序列化常用魔术方法
详细用法请参考 官方文档( https://www. php.net/manual/zh/langu age.oop5.magic.php )
__construct()//类的构造函数,创建类对象时调用
__destruct()//类的析构函数,对象销毁时调用
__call()//在对象中调用一个不可访问方法时调用
__callStatic()//用静态方式中调用一个不可访问方法时调用
__get()//获得一个类的成员变量时调用
__set()//设置一个类的成员变量时调用
__isset()//当对不可访问属性调用isset()或empty()时调用
__unset()//当对不可访问属性调用unset()时被调用。
__sleep()//执行serialize()时,先会调用这个函数
__wakeup()//执行unserialize()时,先会调用这个函数,执行后不会执行__construct()函数
__toString()//类被当成字符串时的回应方法
__invoke()//调用函数的方式调用一个对象时的回应方法
__set_state()//调用var_export()导出类时,此静态方法会被调用。