疫情严重,在家也要保持学习
先简单介绍一下序列化和反序列化。
序列化与反序列化
序列化是指将对象的状态信息转换为存储和传输的形式的过程。
在PHP中,其指的是将变量和对象转换为某种制式字符串的形式,主要用到的是下面两个函数。
Serialize和Unserialize函数
现有一个类:
class YGDX{
public $pub = 'pubstr';
private $pri = 'pristr';
protected $pro = 'prostr';
}
//对其进行序列化
$obj= new YGDX();
$obj_se = serialize($obj);
此时字符串$obj_se
的值即序列化后的值如下:
O:4:"YGDX":3:{s:3:"pub";s:6:"pubstr";s:9:"YGDXpri";s:6:"pristr";s:6:"*pro";s:6:"prostr";}
常见元素(即表中element
)格式:
字母 | 类型 | 格式 |
---|---|---|
O | class | O:strlen(obj_name):obj_name:size:{element1;element2;...} |
a | array | a:size:{element1;element2;...} |
s | string | s:size:value; |
i | integer | i:value; |
N | null | N; |
b | boolean | b:value; |
将一个序列化后的字符串反序列化:
$obj2_se = 'O:4:"YGDX":3:{s:3:"pub";s:6:"pubstr";S:9:"\00YGDX\00pri";s:6:"pristr";S:6:"\00*\00pro";s:6:"prostr";}';
$obj2 = unserialize($obj2_se);
var_dump($obj2);
得到:
object(YGDX)#1 (3) {["pub"]=>string(6) "pubstr" ["pri":"YGDX":private]=>string(6) "pristr"["pro":protected]=>string(6) "prostr" }
注意到其实每个元素的value
与声明它的类型有关。以上面的类为例,具体如下:
类型 | 参数形式 |
---|---|
Public | s:3:"pub" |
Private | S:9:"\00YGDX\00pri" ors:13:\00YGDX\00pri" |
Protected | S:6:"\00*\00pro" ors:10:"\00*\00pro" |
注:类型为s时,\00
被视为3个字符;类型为S时,\00
被视为1个字符。
更具体的信息可以参考这篇博客,写的很详细。
反序列化漏洞
反序列化漏洞存在的必要条件为:存在可以利用的类,且类中存在魔术方法。
常见魔术方法
方法 | 描述 |
---|---|
__construct() | 当一个对象创建时被调用。 |
__destruct() | 当一个对象销毁时或者php代码执行完毕时被调用。 |
__toString() | 当一个对象被当作一个字符串使用 |
__sleep() | 在对象在被序列化之前运行 |
__wakeup() | 将在反序列化之后立即被调用 |
0x00 __wakeup可被绕过
(CVE-2016-7124)
反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()
的执行。
反序列化时进行的对象属性数量检查如果出现异常,则不会调用__wakeup
方法,但是这个时候对象已经被创建,且创建后会被销毁,于是导致__destruct
直接执行。
我的PHP版本过高,暂时不能复现这个漏洞,回头补上。
-
存在该漏洞的PHP版本
- version<5.6.25
- version=7.0.0
0x01 对象注入
举例说明。
构造代码:
<?php
class f1{
var $kaleido;
function __construct(){
$this->kaleido = new f2;
}
function __destruct(){
$this->kaleido->func();
}
}
class f2{
function func(){
echo "now using class_f2's function";
}
}
class f3{
function func(){
echo "now using class_f3's function";
}
}
unserialize($_GET['input']);
理论上讲,f1
的kaleido
参数为f2
的一个对象,如果创建一个f1
对象,将输出now using class_f2's function
,但我们构造如下payload:
http://xxxxxx?input=O:2:"f1":1:{s:7:"kaleido";O:2:"f3":0:{}}
输出了now using class_f3's function
,即通过注入对象的方式实现了其他同名方法的执行。
0x02 session反序列化漏洞
在一般情况下,Session
是以一个文件的形式保存的。一个Session
对应的是一系列键值对,要存储下来,首先会进行序列化处理。PHP存在一个配置项session.serialize_handler
,定义了序列化默认使用的处理器名字,默认值为php
。
上面介绍的序列化方式所对应的处理器为php_serialize
,与php
方法是不同的。
所谓Session反序列化漏洞即利用了不同处理器的格式差别,使用精心构造过的数据,导致一些意料之外的情形发生。
下举例说明。
构造代码:
<?php
ini_set("session.serialize_handler", 'php_serialize'); //暂时设定处理器为上文介绍的那种
session_start();
if(isset($_GET['input'])){
$input = $_GET['input'];
$_SESSION['name']=$input;
}
var_dump($_SESSION);
我们输入input
:
xxxx?input=|s:6:"asdfgh";}
此时后台session存储的值为:
a:1:{s:4:"name";s:15:"|s:6:"asdfgh";}";}
故输出为:
array(1) { ["name"]=> string(15) "|s:6:"asdfgh";}" }
看起来没有什么问题,输入确实被当作了一个完整的字符串存储。
但如果此时存在另一个页面,其采取的序列化处理器为php
而不是php_serialize
,那么输出session将得到什么呢?注释掉上面代码ini_set
,输出session,发现得到:
array(1) { ["a:1:{s:4:"name";s:15:""]=> string(6) "asdfgh" }
出现这个情况的原因是在序列化处理器php
会将符号|
当作键值的分隔符。故而|
前的a:1:{s:4:"name";s:15:"
被当作键,之后的asdfgh
被当作值。
这样就导致了一些验证机制可能被绕过或某些本不应该被执行的对象、系统方法被执行。
0x03 phar反序列化漏洞
简介:手册传送门
phar文件结构
翻阅手册可知,一个phar文件由3~4个部分组成,如下
组件 | 描述 |
---|---|
Stub | 标识,格式为xxx<?php xxx; __HALT_COMPILER();?> |
Manifest Describing the Contents | 描述存储的文件名,文件大小等信息 |
File Contents | 文件内容 |
Phar Signature | 可选,签名部分 |
PHP仅借助__HALT_COMPILER();
识别是否是Phar包。(这一点可以被利用以绕过文件上传限制)
示例
构造代码:
class Test{
function __wakeup(){
echo "<br/>wakeup called";
}
function __destruct(){
echo "<br/>destruct called";
}
}
$phar = new Phar("test.phar"); //指定要生成的文件
$phar -> startBuffering();
$phar -> setStub("<?php __HALT_COMPILER();");
$a = new Test();
$a -> data = 'kaleido';
$phar -> setMetadata($a); //将自定义的meta-data存入mainfest
$phar -> addFromString("kaleido.txt","filecontent"); //添加要压缩的文件
$phar -> stopBuffering();
运行后生成一个test.phar
文件。
继续构造以下代码
<?php
class Test{
function __wakeup(){
echo "<br/>wakeup called";
}
function __destruct(){
echo "<br/>destruct called";
}
}
echo ("<br/>".file_get_contents('phar://test.phar/kaleido.txt'));
通过phar://
解析文件,原本只是要输出我们创建的kaleido.txt
,但实际上输出如下:
wakeup called
filecontent
destruct called
发现类中的两个魔术方法都被引用了。
这是因为当一个PHAR文件在第一次被phar://
解析时,其中的meta-data部分(即上面我们构造的$a
)会被反序列化。
这个部分如果被注入一些特殊构造,则可能导致某些方法在意料之外被引用。
(个人整理,如有错误欢迎指正)