PHP反序列化漏洞

疫情严重,在家也要保持学习
先简单介绍一下序列化和反序列化。

序列化与反序列化

序列化是指将对象的状态信息转换为存储和传输的形式的过程。
在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)格式:

字母类型格式
OclassO:strlen(obj_name):obj_name:size:{element1;element2;...}
aarraya:size:{element1;element2;...}
sstrings:size:value;
iintegeri:value;
NnullN;
bbooleanb: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与声明它的类型有关。以上面的类为例,具体如下:

类型参数形式
Publics:3:"pub"
PrivateS:9:"\00YGDX\00pri"ors:13:\00YGDX\00pri"
ProtectedS: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']);

理论上讲,f1kaleido参数为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)会被反序列化。
这个部分如果被注入一些特殊构造,则可能导致某些方法在意料之外被引用。

(个人整理,如有错误欢迎指正)

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值