前言
前几天看了一道本地文件包含的题目 , 利用的是 Zip://
协议 . 但是查看 WriteUp 后发现还可以利用 Phar://
伪协议 . 关于这个伪协议的利用点我没有做过整理 , 因此在这里分析总结一下~
Phar
Phar( PHP文件归档 ) 是 PHP 的一个扩展程序 , 从 官方文档 的角度来说 , Phar 提供了一种将完整的 PHP 应用程序归档为单个文件并从该文件直接运行它的方法 , 而不需要将所有文件提取到磁盘后再运行 .
在 phpinfo 可以看到与 Phar 相关的条目明细 , 如下所示
但要想使用 Phar 的相关功能 , 就需要使用 PHP 流包装器( PHP StreamWrapper ) . 比如 , 使用 include
来在 PHP 脚本中调用外部文件 . 关于 StreamWrapper( 流包装器 )
的内容可以参考 PHP StreamWrapper 实现 WebShell 这篇文章 , 这里我想说的是 : PHP 内置了很多流包装器 , 其中就有 phar://
数据流包装器 .
phar://
phar://
数据流包装器自 PHP 5.3.0
起开始生效 , 详细内容可以参考 Using Phar Archives: the phar stream wrapper
可以看到 , phar://
数据流包装器完全支持最基本的PHP文件系统函数 , 而且 Phar 归档文件中的单个压缩文件和每个文件的元数据都可以用流上下文来进行操作 .
另外 , phar://
数据流包装器是不可以运用于远程文件的 , 且其可用性与 allow_url_fopen
和 allow_url_include
两个参数无关 .
说了这么多 , 还是来谈一谈 phar://
数据流包装器是如何利用的吧
利用环境
现在 phar://
的利用手段主要分为两种 , 一种是 本地文件包含 , 一种是 反序列化漏洞 , 下面来分析一下这两个利用方向的利用条件是什么 , 利用过程是怎样的 .
本地文件包含
文件包含的利用手段比较简单 , 利用环境可以参考 SewellDinG/LFIboomCTF . 简单的说 , 很多情况下 , 后端会对包含文件的类型进行限制 , 或者对包含文件进行重命名 . 当无法绕过这些过滤机制 , 且你又发现了可以上传归档文件时 , 可以考虑利用 phar://
数据流包装器 , 有可能拿到 RCE .
下面举两个例子
1. 对包含文件类型进行限制
参考以下代码
这里从 HTTP GET 方法获取到文件名 , 然后判断该文件是否存在且该文件是否以 .jpg
结尾 , 若判断成立则使用 include
函数包含该文件 .
此时 , 我们可以构造 phpinfo.jpg
文件 , 然后利用 zip
命令压缩为一个 zip
压缩包 , 然后上传到目标服务器
然后访问指定的URL , 即可运行构造的 phpinfo()
代码 .
Payload : ?file=phar://phpinfo.zip/phpinfo.jpg
成功拿到 RCE !
2. 对包含文件进行重命名
参考下面这段代码
这里对包含文件添加了 .jpg
后缀 , 在无法利用 \0
截断时 , 难以直接包含 PHP 文件 . 此时也可以利用 phar://
数据流包装器 .
同样构造 phpinfo.jpg
文件 , 然后利用 zip
命令压缩 , 接着访问指定 URL , 即可运行构造的 phpinfo()
函数 .
Payload : ?file=phar://phpinfo.zip/phpinfo
成功拿到 RCE !
一些注意点
-
tar
格式压缩包是否可以被利用 ?Payload :
http://127.0.0.1/lfi/lfi1.php?file=phar://phpinfo.tar/phpinfo.jpg
Payload :
http://127.0.0.1/lfi/lfi2.php?file=phar://phpinfo.tar/phpinfo
经测试 ,
tar
格式压缩包可以被phar://
流包装器利用 . 并且像tar.gz
,tar.bz2
等归档文件也是可以被phar://
流包装器利用的 . -
rar
格式压缩包是否可以被利用 ?Payload :
http://127.0.0.1/lfi/lfi1.php?file=phar://phpinfo.rar/phpinfo.jpg
Payload :
http://127.0.0.1/lfi/lfi2.php?file=phar://phpinfo.rar/phpinfo
报错提示
__HALT_COMPILER()
这个魔术函数没有被找到 . 因此利用 RAR 工具压缩的归档文件无法被phar://
流包装器利用
Phar 反序列化漏洞
在 SeeBug 的 利用 phar 拓展 php 反序列化漏洞攻击面 一文中很详细的说明了 Phar 反序列化漏洞的成因 , 我这里做简单总结 .
Phar 归档文件结构
在学习 Phar 反序列化漏洞前需要了解 Phar 归档文件的格式 . 官方文档( Edit Report a Bug Ingredients of all Phar archives, independent of file format )将 Phar 归档文件分为 3-4 个部分 .
-
Stub( 翻译为 : 存根 , 可看作是一个加载程序 , 用于加载 Phar 归档文件 , 同时也起到标识符的作用 , 判断该文件是否是一个 Phar 归档文件 )
官方文档中提到 , 一个 Stub 至少要包含
__HALT_COMPILER();?>
这个部分 , 且必须以该部分结尾 , 对该部分的其他内容没有要求 .现在我们知道 , 通过 RAR 工具压缩的文件不会包含
__HALT_COMPILER()
这个部分 , 因此无法被phar://
数据流包装器解析 -
a manifest describing the contents( 内容清单 )
Phar 本质上是一种归档文件 , 其中的每个子文件的文件名长度 , 文件原始大小 , 文件时间戳等信息都会存放在这 .
其中还会以序列化字符串的形式存储用户自定义的
Meta-data
, 反序列化漏洞就产生于此 . -
the file contents( 文件内容 )
这点没什么好说的 , 会存放归档文件的内容
-
[optional] a signature for verifying Phar integrity (phar file format only)( 签名 , 用于验证 Phar 文件的完整性 )
该选项是一个可选选项 , 目前支持 MD5 和 SHA1 两种格式的签名 , 如果启用该选项 , Phar 签名会被自动计算并放置到 Phar 归档文件的末尾 .
创建 Phar 归档文件
从上文中得知了 Phar 文件的结构 , PHP 又内置了一个 Phar 类 , 因此我们可以自定义一个 phar 文件 .
但我们必须先在 php.ini
中关闭 phar.readonly
选项( 该选项默认开启 ) , 然后才能创建 Phar 归档文件
创建完毕后 , 我们可以通过 xxd
工具查看生成的 Phar 文件内容
从中我们可以看到 __HALT_COMPILER();?>
标识符 , 也可以看到以序列化字符串形式出现的 Meta-data
数据 .
Phar 反序列化操作
有序列化的过程 , 就一定会有反序列化操作 . 说到反序列化 , 你肯定最先想到 unserialize()
函数 . 事实上 , 很多 PHP 文件系统函数在通过 phar://
伪协议解析 Phar 归档文件时 , 都会自动将 Meta-data
字符串进行反序列化操作 .
引用 SeeBug 上一张表格 , 受影响的函数如下所示
我们随便选取一个函数( 以 file_get_contents()
为例 )进行测试 . 之前我们生成了一个 test.phar
归档文件 , 现在我们重新构造 TestObject
类并通过 file_get_contents
函数引入数据流并解析 .
能看到我们定义的 __wakeup()
魔术函数被调用和执行 . 这也证明了在不使用 unserialize()
反序列化函数的情况下 , 可以通过一些文件系统函数来反序列化 Phar 归档文件中的 Meta-data
数据 .
Phar 反序列化带来的问题
前面分析 Phar 归档文件结构时提到 , phar://
伪协议是通过 __HALT_COMPILER();?>
来判断目标数据流是否为一个 Phar 归档文件的 . 因此 , 我们可以将一个 Phar 归档文件伪装为其它格式的文件 . 看下面这个例子
如上所示 , 我们在 Stub 区域开头添加 GIF 文件头( GIF89A ) , 此时系统会把该文件当作 GIF 文件处理 , 利用该方式可以绕过很多文件上传的过滤机制 .
沿着这个思路 , 不难想到一个完整的利用链
-
判断目标站点是否能够上传 Phar 文件 , 可以将 Phar 文件伪装成其他格式文件来绕过检测 .
-
找到可以利用的函数与方法 , 然后构造对应 Payload 并上传 .
-
如果目标站点没有过滤掉关键字(
phar
等 ) , 那么可以通过反序列化漏洞来执行恶意代码 .
下面举个例子来说明整个利用过程 .
反序列化漏洞利用过程
来看下面这段代码
代码很简单 , 从 GET 方法获取 filename 参数的值作为文件名 , 然后判断该文件是否存在 . 并且定义了一个类 , 当该类实例化对象的生命周期结束时 , 会调用析构函数 , 执行 output 变量的值 .
但由于 file_exists()
函数在解析 Phar 数据流时会反序列化 Meta-data
字符串 , 因此我们可以构造恶意的 Meta-data
数据 .
在 undemotest.php
中 , 我重新构造了 MyClass
类的 output
变量 , 将其定义为 eval($_GET["code"]);
( 一句话木马 ) , 将该类实例化后添加到 Meta-data
中 , 最后生成 payload.phar
归档文件 .
现在查看 payload.phar
文件内容 , 能发现构造的恶意代码已经被序列化成字符串了 .
此时访问之前的页面 , 将 filename
参数指向 phar://
数据流 , file_exist()
函数在处理 Phar 数据流时会反序列化 Meta-data
字符串 . 其中的恶意代码会被解析执行 .
此时该页面就成为了一个 PHP 后门 , 我们可以通过构造 GET 参数 " _ " 来访问 PHP 命令行 .
Payload : http://127.0.0.1/demotest/demotest.php?filename=phar://payload.phar/demotest.txt&_=phpinfo();
Payload : http://127.0.0.1/demotest/demotest.php?filename=phar://payload.phar/demotest.txt&_=system(%22id%22);
成功拿到目标 WebShell !
其实关于 Phar 反序列化漏洞利用的题目还有很多 , 最经典的当然是 HITCON2017 Baby^H Master PHP 2017
. 网上有很多大牛都给出了非常通俗易懂的 WriteUp , 这里我也不多罗嗦了 .
总结
本文简单的总结了 Phar 伪协议利用的两个方向 : " 本地文件包含 " 和 " 反序列化漏洞 " , 并举了一些例子 . 希望能对你有所帮助 .