反序列化介绍
利用工具phpggc
序列化格式
a -->array --> a:1:{"1","abc"}
b -->boolean --> b:0;
d -->double --> d:1.2323;d:INF;d:NAN;
i -->integer --> i:3;
s/S -->string,S可以有16进制字符 --> s:3:"abc"; 或者 S:3:"\61bc"
O -->class --> O:1:"A":3:{s:4:"\00A\00a";N;s:4:"\00*\00b";N;s:1:"c";s:5:"hello";}
N -->null --> N;
r -->对象引用 --> r:1 (数字为所引用的对象在序列化字符串中第一次出现的位置)
R -->指针引用 --> R:1 (会修改指针指向的内容)
php magic function
函数介绍
__construct() #当对象创建(new)时会自动调用,注意在unserialize()时并不会自动调用
__destruct() #对象被销毁时会自动调用
__sleep() #serialize()时会先被调用
__wakeup() #unserialize()时会先被调用
__call() #在对象中调用一个不可访问方法时调用
__callStatic() #用静态方式中调用一个不可访问方法时调用
__get() #获得一个类的成员变量时调用
__set() #设置一个类的成员变量时调用
__isset() #当对不可访问属性调用isset()或empty()时调用
__unset() #当对不可访问属性调用unset()时被调用。
__toString() #类被当成字符串时的回应方法
__invoke() #调用函数的方式调用一个对象时的回应方法
__set_state() #调用var_export()导出类时,此静态方法会被调用。
__clone() #当对象复制完成时调用
__autoload() #尝试加载未定义的类
__debugInfo() #打印所需调试信息
注意事项
- 当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)
- private的参数被反序列化后变成 \00test\00test1
- public的参数变成 test1
- protected的参数变成 \00*\00test1
最近发现在php7.2版本以上,可以用public来构建反序列化字符串,当php处理时,会自动识别源码中类的属性,变成private或者protected
small trick
如果php反序列化中过滤了控制符
如果版本大于7.2就可以直接用public来构建
同时还有一个方法可以使用大写S表示字符串,支持16进制表示,例如 S:5:"A\00B\09C"
Session反序列化
session共有三个处理器
处理器 | 对应存储格式 |
---|---|
php_binary | 键名的长度对应的ASCII字符+键名+serialize() 处理的值 |
php | 键名+竖线+serialize()处理的值 |
php_serialize | serialize()处理后的数组方式 |
例如m0yuqi=lover
php_serialize处理下为
a:1:{s:6:"m0yuqi";s:5:"lover";}
php处理下为
m0yuqi|s:5:"lover";
session处理器默认设置为php_serialize,如果一个页面设置为php
ini_set('session.serialize_handler', 'php');
session_start();
当session_start()使用时,php就将session默认文件夹中的session进行反序列化
只要控制session中的数据,那么这些数据就可以反序列化
那我们如何控制session数据呢?
查阅资料可知道
session.upload_progress.enabled打开时,php会记录上传文件的进度,在上传时会将其信息保存在$_SESSION
那么我们可以用利用session.upload_progress.enabled构造session
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时
当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据
所以构建session.upload_progress如下
<form action="target" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1"/>
<input type="file" name="file" />
<input type="submit" value="go" />
</form>
session里面记录的值是file的值
所以要抓包修改file的值,改成php处理器处理的序列化的值就可以反序列化了
phar://反序列化
phar文件的meta-data是以序列化的形式存储的
php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化
受影响的函数列表 | |||
---|---|---|---|
fileatime | filectime | file_exists | file_get_contents |
file | filegroup | fopen | file_put_contents |
fileinode | filemtime | fileowner | fileperms |
is_dir | is_file | is_link | is_executable |
is_readable | is_writable | iswriteable | parse_ini_file |
copy | unlink | stat | readfile |
在跟踪了受影响函数的调用情况后发现,除了所有文件函数,只要是函数的实现过程直接或间接调用了php_stream_open_wrapper
。都可能触发phar反序列化漏洞
以下这些方式都可触发phar反序列化漏洞
exif
exif_thumbnail
exif_imagetype
gd
imageloadfont
imagecreatefrom***
hash
hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file
file / url
get_meta_tags
get_headers
standard
getimagesize
zip
$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');
MYSQL
LOAD DATA LOCAL INFILE
也会触发这个php_stream_open_wrapper
可以配合mysql读文件和phar来达到RCE
这种攻击的利用条件
- phar文件要能够上传到服务器端。
- 要有可用的魔术方法作为“跳板”。
- 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。
利用时,一般将phar伪造成其他格式的文件,添加文件头和修改后缀名
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
//设置stub,增加gif文件头
$o = new TestObject();
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
最后访问phar://phar.gif/test.txt
就可以反序列化
如果过滤了phar在开头的情况可以使用姿势如下绕过
compress.bzip2://phar://
compress.zlib://phar://
php://filter/read=convert.base64-encode/resource=phar://
php://filter//resource=phar://
some trick
内置类的同名函数
使用如下代码获取有open函数的内置类
<?php
foreach (get_declared_classes() as $class) {
foreach (get_class_methods($class) as $method) {
if ($method == "open")
echo "$class->$method\n";
}
}
?>
伪造任意的php内置类
php内置类:SoapClient,这个类可以发送url请求
SoapClient的参数中有user_agent项可以进行CRLF(\r\n
)注入,修改发送的请求Content-type
如下代码,反序列化出来的SoapClient对象调用不存在的函数是,就会调用__call方法访问target
<?php
$target='http://www.m0yuqi.cn/index.php?action=login';
$a = new SoapClient(null,array('location'=>$target,'uri' => "m0yuqi.cn"));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();
参考
https://github.com/ambionics/phpggc
Phar与Stream Wrapper造成PHP RCE的深入挖掘
利用 phar 拓展 php 反序列化漏洞攻击面