本文共 11260 字,大约阅读时间需要 37 分钟。
目录
1. 序列化的定义2. serialize:序列化3. unserialize:反序列化4. 序列化、反序列化存在的安全风险5. Use After Free Vulnerability in unserialize() with DateTime* [CVE-2015-0273]6. PHP中的内存破坏漏洞利用(CVE-2014-8142和CVE-2015-0231) 7. PHP多种序列化、反序列化处理机制不同导致对象注入8. 序列化/反序列化在开源CMS上存在的漏洞9. pch-031.md: Use After Free Vulnerabilities in Session Deserializer10. 反序列化和PHP自动加载(__autoload()、spl_autoload_register())组合产生入侵向量
1. 序列化的定义
序列化在计算机科学中通常有以下定义:
1. 对同步控制而言,表示强制在同一时间内进行单一存取 2. 在数据储存与传送的部分是指将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等,或者透过网络传送资料时进行编码的过程,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这程序被应用在不同应用程序之间传送对象,以及服务器将对象储存到档案或数据库。相反的过程又称为反序列化
序列化有多个优点
1. 一个简单和持久的方法使对象持续2. 一个发起远程过程调用的方法,例如在SOAP内的 3. 一个分发对象的方法,尤其是在如COM及CORBA的软件组件化内
Relevant Link:
http://zh.wikipedia.org/wiki/%E5%BA%8F%E5%88%97%E5%8C%96http://baike.baidu.com/view/160029.htm
2. serialize:序列化
serialize: 产生一个可存储的值的表示
serialize() 返回字符串,此字符串包含了表示 value 的字节流,可以存储于任何地方。这有利于存储或传递 PHP 的值,同时不丢失其类型和结构serialize() 可处理除了 resource 之外的任何类型,包括1. 指向其自身引用的数组2. serialize() 的数组/对象中的引用也将被存储(引用本身也会被序列化)3. ...
从本质上来讲,序列化的过程是一个"对象(广义上的对象,包括integer、float、string、array、object)"进行"对象销毁",然后转换为一个通用的中间可存储字符串,在整个序列化过程中,对象经历的声明周期如下
1. __sleep(): 在执行对象销毁前获得执行权限2. __destruct():执行实际的对象销毁操作
code
"; $this->protected_var = "protected_var"; $this->private_var = "private_var"; } function __destruct() { echo "function __destruct() is called" . ""; } public function __sleep() { echo "function __sleep() is called" . ""; } public function __wakeup() { echo "function __wakeup() is called" . ""; } } //initialize a var $obj = new Connection(); //var_dump($obj); $result = serialize($obj); //var_dump($result); unserialize($result);?>
Relevant Link:
http://php.net/manual/zh/function.serialize.phphttp://php.net/manual/zh/language.oop5.magic.php#object.wakeuphttp://php.net/manual/zh/language.oop5.decon.php
0x1: 序列化字符串格式
1. a – array: 复合类型2. b – boolean: 标量类型3. d – double: 标量类型4. i – integer: 标量类型5. o – common object 6. r – reference: 对象引用 7. s – string: 标量类型8. C – custom object: C 是 PHP5 中引入的,它表示自定义的对象序列化方式9. O – class: 复合类型10. N – null: N 表示的是 NULL11. R – pointer reference: 指针引用 12. U – unicode string: U 是 PHP6 中才引入的,它表示 Unicode 编码的字符串。因为 PHP6 中提供了 Unicode 方式保存字符串的能力,因此它提供了这种序列化字符串的格式
0x2: NULL和标量类型的序列化
NULL和标量类型的序列化是最简单的,也是构成复合类型序列化的基础
1. NULL 的序列化在 PHP 中,NULL 被序列化为: N;2. boolean 型数据的序列化boolean 型数据被序列化为: b:0; 或 b:1;3. integer 型数据的序列化integer 型数据(整数)被序列化为: i:-2147483648 ~ 2147483647;数字前可以有正负号,如果被序列化的数字超过这个范围,则会被序列化为浮点数类型而不是整型。如果序列化后的数字超过这个范围(PHP 本身序列化时不会发生这个问题),则反序列化时,将不会返回期望的数值 4. double 型数据的序列化double 型数据(浮点数)被序列化为:d:double float;其范围与 PHP 中浮点数的范围一样。可以表示成整数形式、浮点数形式和科学技术法形式。如果序列化后的数字范围超过 PHP 能表示的最大值,则反序列化时返回无穷大(INF),如果序列化后的数字范围超过 PHP 所能表示的最小精度,则反序列化时返回 05.string 型数据的序列化string 型数据(字符串)被序列化为:s:length:"";前一个冒号中的是长度,是非负整数,数字前可以带有正号(+)。后面的冒号中为字符串值,这里的每个字符都是单字节字符,其范围与 ASCII 码的 0 – 255 的字符相对应。每个字符都表示原字符含义,没有转义字符, 两边的引号("")是必须的,但不计算在长度当中
0x3: 简单复合类型的序列化
1. 数组序列化: a:2:{i:0;s:6:"prefix";s:7:"payload";s:6:"attack";} 1) a:2表示数组元素的个数 2) i:0表示数组元素的下标 3) s:6:"prefix"表示与下标对应的数组元素的值 4) s:7:"payload"表示数组下标payload 5) s:6:"attack"表示对应下标的值2. 对象序列化: O:3:"Foo":4:{s:4:"var1";i:1;s:4:"var2";i:2;s:7:"*var3";i:3;s:9:"Foovar4";i:4;} 1) O:3:"Foo": 代表对象的类名长度,以及类名字符串 2) :4: 代表该对象有4个成员 3) var 和 public 声明的字段都是公共字段,因此它们的字段名的序列化格式是相同的。公共字段的字段名按照声明时的字段名进行序列化,但序列化后的字段名中不包括声明时的变量前缀符号 $ 4) protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上\0*\0的前缀。这里的 \0 表示 ASCII 码为 0 的字符,而不是 \0 组合 5) private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,字段名前面会加上\0\0的前缀。这里 表示的是声明该私有字段的类的类名,而不是被序列化的对象的类名。因为声明该私有字段的类不一定是被序列化的对象的类,而有可能是它的祖先类//字段名被作为字符串序列化时,字符串值中包括根据其可见性所加的前缀。字符串长度也包括所加前缀的长度。其中 \0 字符也是计算长度的
0x4: 嵌套复合类型的序列化
在PHP中,复合类型数据(对象和数组)是引用传递的,但是引用传递也分为两类
1. 对象引用: 普通的符合类型数据赋值就是对象引用2. 指针引用: 赋值对象前加上&的赋值就是指针引用
看下面的例子
value = $a; $b = new SampleClass();$b->value = &$b; $a->value = 1;$b->value = 1; var_dump($a);var_dump($b);?>
改变 $a->value 的值仅仅是改变了 $a->value 的值,而改变 $b->value 的值却改变了 $b 本身,这就是对象引用和指针引用的区别
0x5: 引用标示后的数字
对象引用(r)和指针引用(R)的格式为
r:number;R:number;
这个number,就是所引用的对象在序列化串中第一次出现的位置,但是这个位置不是指字符的位置,而是指对象(这里的对象是泛指所有类型的量,而不仅限于对象类型)的位置
int = 1;$a->str = “Hello”;$a->bool = false;$a->obj = $a;$a->pr = &$a->str;echo serialize($a);?>
1. 在这个例子中,首先序列化的对象是 ClassA 的一个对象,那么给它编号为 12. 接下来要序列化的是这个对象的几个成员,第一个被序列化的成员是 int 字段,那它的编号就为 23. 接下来被序列化的成员是 str,那它的编号就是 34. 依此类推,到了 obj 成员时,它发现该成员已经被序列化了,并且编号为 1,因此它被序列化时,就被序列化成了 r:1;5. 在接下来被序列化的是 pr 成员,它发现该成员实际上是指向 str 成员的一个引用,而 str 成员的编号为 3,因此,pr 就被序列化为 R:3; 了
PHP 是如何来编号被序列化的对象的呢
1. PHP 在序列化时,首先建立一个空表2. 然后每个被序列化的对象在被序列化之前,都需要先计算该对象的 Hash 值,然后判断该 Hash 值是否已经出现在该表中了3. 如果没有出现,就把该 Hash 值添加到这个表的最后,返回添加成功4. 如果出现了,则返回添加失败,但是在返回失败前先判断该对象是否是一个引用(用 & 符号定义的引用),如果不是则也把 Hash 值添加到表后(尽管返回的是添加失败)5. 如果返回失败,则同时返回上一次出现的位置 6. 在添加 Hash 值到表中之后,如果添加失败,则判断添加的是一个引用还是一个对象,如果是引用,则返回 R 标示,如果是对象,则返回 r 标示。因为失败时,会同时返回上一次出现的位置,因此,R 和 r 标示后面的数字,就是这个位置
0x6: 对象引用的反序列化
PHP 在反序列化处理对象引用时很有意思,如果反序列化的字符串不是 PHP 的 serialize() 本身生成的,而是人为构造或者用其它语言生成的,即使对象引用指向的不是一个对象,它也能正确地按照对象引用所指向的数据进行反序列化
大家会发现,上面的例子反序列化后,$a->b 的值与 $a->a 的值是一样的,尽管 $a->a 不是一个对象,而是一个字符串。因此如果大家用其它语言来实现序列化的话,不一定非要把 string 作为标量类型来处理,即使按照对象引用来序列化拥有相同字符串内容的复合类型,用 PHP 同样可以正确的反序列化。这样可以更节省序列化后的内容所占用的空间
3. unserialize:反序列化
从已存储的表示中创建 PHP 的值
unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值在反序列化中,经历的对象声明周期为
1. __construct():执行对象注册、包括对象中成员的注册2. __wakeup:在构造函数执行后获得执行权限
Relevant Link:
http://php.net/manual/zh/function.unserialize.php
4. 序列化、反序列化存在的安全风险
0x1: 对象注入
secret = "?????????????????????????????"; if ($o->secret === $o->enter) echo "Congratulation! Here is my secret: ".$o->secret; else echo "Oh no... You can't fool me"; } else echo "are you trolling?"; }?>
serialize一个just4fun的对象,序列化之前先进行引用赋值
$o->enter = &$o->secret
0x2: PHP Session 序列化及反序列化处理器
http://drops.wooyun.org/tips/3909
0x3: 基于序列化、反序列化的Webshell隐藏技巧
http://www.cnblogs.com/LittleHann/p/3522990.html搜索:0x22: PHP的序列化、反序列化特性布置后门
Relevant Link:
http://drops.wooyun.org/papers/660
5. Use After Free Vulnerability in unserialize() with DateTime [CVE-2015-0273]
A use-after-free vulnerability was discovered in unserialize() with DateTime/DateTimeZone objects's __wakeup() magic method that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
0x1: Affected Versions
Affected is PHP 5.6 < 5.6.6Affected is PHP 5.5 < 5.5.22Affected is PHP 5.4 < 5.4.38Affected is PHP 5.3 <= 5.3.29
0x2: 漏洞源代码分析
\php-src-master\ext\date\php_date.c
static int php_date_initialize_from_hash(php_date_obj **dateobj, HashTable *myht){ zval *z_date; zval *z_timezone; zval *z_timezone_type; zval tmp_obj; timelib_tzinfo *tzi; php_timezone_obj *tzobj; z_date = zend_hash_str_find(myht, "date", sizeof("data")-1); if (z_date) { convert_to_string(z_date); z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type")-1); if (z_timezone_type) { convert_to_long(z_timezone_type); z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone")-1); if (z_timezone) { convert_to_string(z_timezone);...
The convert_to_long() leads to the ZVAL and all its children is freed from memory. However the unserialize() code will still allow to use R: or r: to set references to that already freed memory. There is a use after free vulnerability, and allows to execute arbitrary code.
0x3: poc
>= 8; } return $out;}?>
gdb php
run uafpoc.php assert "system\('sh'\)==exit\(\)"Relevant Link:
https://github.com/80vul/phpcodz/tree/master/research
6. PHP中的内存破坏漏洞利用(CVE-2014-8142和CVE-2015-0231)
待研究
Relevant Link:
http://drops.wooyun.org/papers/4864
7. PHP多种序列化、反序列化处理机制不同导致对象注入
PHP 内置了多种处理器用于存取 $_SESSION 数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式
1. php: 键名 | 经过 serialize() 函数反序列处理的值2. php_binary: 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值3. php_serialize(php>=5.5.4): 经过 serialize() 函数反序列处理的数组
0x1: ini_set("session.serialize_handler", "php");
0x2: ini_set("session.serialize_handler", "php_serialize");
0x3: ini_set("session.serialize_handler", "php_binary");
0x4: 安全隐患
构造通过ini_set("session.serialize_handler", "php_serialize");方式生成的SESSION本地化文件
D:\wamp\tmpa:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}
然后通过PHP默认的ini_set("session.serialize_handler", "php");方式从磁盘上load这个SESSION文件
造成反序列化对象注入
Relevant Link:
http://drops.wooyun.org/tips/3909
8. 序列化/反序列化在开源CMS上存在的漏洞
Relevant Link:
http://drops.wooyun.org/papers/596
9. pch-031.md: Use After Free Vulnerabilities in Session Deserializer
Multiple use-after-free vulnerabilities were discovered in session deserializer (php/php_binary/php_serialize) that can be abused for leaking arbitrary memory blocks or execute arbitrary code remotely.
>= 8; } return $out;}?>
When session deserializer (php/php_binary) deserializing multiple data it will call to php_var_unserialize() multiple times. So we can create ZVAL and free it via the php_var_unserialize() with a crafted serialized string, and also free the memory (reduce the reference count of the ZVAL to zero) via zval_ptr_dtor() with deserialize two identical session data, then the next call to php_var_unserialize() will still allow to use R: or r: to set references to that already freed memory. It is possible to use-after-free attack and execute arbitrary code remotely.
In some other cases, session deserializer (php/php_binary/php_serialize) may also lead to use-after-free vulnerabilities: i) via crafted Serializable::unserialize() ii) via unserialize()'s callback function and zend_lookup_class() call a crafted __autoload().Relevant Link:
https://github.com/80vul/phpcodz/blob/master/research/pch-031.md
待研究
http://www.ptsecurity.com/upload/iblock/dac/daca495893852753dac1d0b17f51df19.pdfhttps://sektioneins.de/en/blog/14-08-27-unserialize-typeconfusion.html
10. 反序列化和PHP自动加载(__autoload()、spl_autoload_register())组合产生入侵向量
0x1: spl_autoload_register自身存在的binary漏洞
0x2: __autoload() —— 自动加载函数
__autoload — 尝试加载未定义的类,可以通过定义这个函数来启用类的自动加载
//创建class文件夹,分别创建3个类库文件
index.php文件中写入
';$test1 = new class2();echo '';$test1 = new class3();//结果是//class1//class2//class3?>
PHP自动加载了class下面所有的要加载的类
0x3: spl_autoload_register() —— 注册__autoload()函数
spl_autoload_register()本质上和__autoload()是一致的,区别在于spl_autoload_register只要运行注册一次即全局有效,不需要在每个单独的文件中都声明__autoload()
';$test1 = new class2();echo '';$test1 = new class3();?>
0x4: 入侵向量
1. 反序列化注入一个注入点当前代码空间没有的类
1. 如果存在反序列化注入的文件加载的class很有限,当前代码空间并不包含可以进行代码执行、或文件写入的函数2. 如果目标CMS或者框架使用了spl_autoload_register()自动加载机制,可以直接注入一个当前代码控制不存在的class3. 在反序列化对象重建的时候,PHP会帮我们自动加载引入所需的class
2. 利用自动类加载机制绕过上传限制
1. spl_autoload_register函数有个特点,如果不指定处理用的函数,就会自动包含"类名.php"或"类名.inc"的文件,并加载其中的"类名"类2. 如果目标框架对上传文件进行了重命名(例如MD5),或者不进行重命名且我们能知道上次后的文件名3. 上传webshell,后缀为.inc,被重命名为xxxx.inc4. 序列化一个类名为xxxx的类对象,生成payload后,发送给目标服务器5. 服务器反序列化这个字符串后,将会自动加载xxxx类,由于之前spl_autoload_register函数注册的方法,会自动加载xxxx.inc,从而造成文件包含漏洞,getshell成功
Relevant Link:
http://honkwin.com/show/1962.htmlhttp://php.net/manual/zh/function.autoload.phphttp://my.oschina.net/alexskywinner/blog/92737http://drops.wooyun.org/tips/10564
Copyright (c) 2014 LittleHann All rights reserved