php学习——phar反序列化学习
之前学反序列化的时候没有仔细看 只简单做了下题 有时间了回头再写一下
phar反序列化
相关知识
Phar 反序列化漏洞是一个 PHP 中的常见安全问题,它通常出现在处理 .phar
文件时。.phar
文件本质上是一种压缩文件,它将多个文件封装成一个文件,并允许对其中的内容进行读取和写入。在 phar
文件中,用户自定义的 meta-data
(元数据)通常是通过 PHP 的序列化机制存储的。
phar是将php文件打包而成的一种压缩文档,类似于Java中的jar包。它有一个特性就是phar文件会以序列化的形式储存用户自定义的meta-data
。以扩展反序列化漏洞的攻击面,配合phar://
协议使用。
对于 PHP 5.3 或更高版本,phar
后缀文件时默认开启支持的,可以直接使用
一般使用文件包含中的**phar
伪协议进行读取.phar
文件**
phar文件结构
- Stub(启动文件)
这是phar
文件标识,格式为xxx<?php xxx;__HALT_COMPILER();?>
(头部信息),这是 PHAR 文件的入口点,当 PHP 解释器加载 PHAR 文件时,会首先执行 Stub 代码
- Manifest(元数据)
压缩文件的属性等信息,以序列化方式存储 这就成为了一个潜在的攻击点
当 Phar 文件被加载时,Manifest 部分会被反序列化为 PHP 对象
- Contents(内容部分)
压缩文件的内容
实际存储了所有打包进 Phar 文件的文件数据。这个部分是最主要的文件内容部分,通常存储 PHP 代码、文本文件或二进制文件等。
- Signature(签名)
签名部分位于 Phar 文件的末尾,通常用于验证文件的完整性和真实性。它确保文件没有被篡改,并且是由一个受信任的源创建的。签名通常使用公钥/私钥加密机制。
phar文件的创建(压缩 )
1.创建phar对象
1 | $phar = new Phar('test2.phar', 0, 'test2.phar'); |
这行代码通过 new Phar()
创建一个新的 Phar 文件,文件名是 test2.phar
。如果文件不存在,它将会创建该文件;如果文件已经存在,它将被覆盖。0
表示没有特殊的创建模式,'test2.phar'
是该 Phar 文件的别名,可以在之后的代码中用来引用该文件。
2.从目录构建 Phar 文件
1 | $phar->buildFromDirectory('f:\0Day'); |
将 f:\0Day
目录中的所有文件打包到 test2.phar
文件中。buildFromDirectory()
方法会递归地将目录中的所有文件添加到 Phar 文件中。
3.设置默认 Stub
1 | $phar->setDefaultStub('test.txt', 'test.txt'); |
这里使用 setDefaultStub()
为 Phar 文件设置默认的入口文件(stub)。'test.txt'
是入口文件的路径,它在创建时会被作为默认的入口文件。第二个 'test.txt'
是该文件在 Phar 内部的路径。
Stub 是 Phar 文件的入口代码,它会在 Phar 文件执行时首先被执行。__HALT_COMPILER()
是一个 PHP 函数,它会停止编译 PHP 代码,使得后续的代码不会被解析。这样,在 Phar 文件中可以包含一些 PHP 代码,而不会立即被执行,直到用户明确调用该文件。
解压缩
1.打开 Phar 文件
1 | $phar = new Phar('test.phar'); |
使用 new Phar('test.phar')
打开名为 test.phar
的 Phar 文件,并将其封装成 Phar
对象。test.phar
文件必须存在,否则会抛出错误。
2.解压到指定目录
1 | $phar->extractTo('test'); |
该方法将 test.phar
文件中的所有内容提取到指定的目录 'test'
中。如果该目录不存在,PHP 会尝试创建该目录。如果已经存在,内容将被解压到该目录下。
创建一个包含恶意代码的 Phar 文件模板
1 | @unlink('test.phar'); |
下面是解析
1 | # 删除之前的test.par文件(如果有) |
漏洞原理
- Phar 伪协议:PHP 支持通过
phar
伪协议来读取.phar
文件。 - 反序列化触发:当使用
phar
伪协议解析.phar
文件时,PHP 会自动触发对manifest
字段中序列化字符串的反序列化操作。这就意味着,如果manifest
里的序列化数据是恶意构造的,就可能触发反序列化漏洞,进而执行恶意代码。
触发条件
PHP 版本需要大于等于 5.2。
在 PHP 的配置文件 php.ini
中,要将 phar.readonly
选项设置为 Off
。因为默认情况下 phar.readonly
是开启的(即设为 On
),这会限制对 Phar 文件的写入等操作,若不关闭该选项,很多利用操作无法进行。
表格中列出了可以使用 phar
伪协议读取 .phar
文件的函数,当这些函数处理带有恶意 phar
伪协议的路径时,就可能触发 Phar 反序列化漏洞。
比如:
file_exists
:用于检查文件或目录是否存在,若传入的路径是 phar://
开头指向恶意 Phar 文件,就可能触发漏洞。
file_get_contents
:用于读取文件内容,同样,若读取的是恶意 Phar 文件,会触发对 manifest
中序列化数据的反序列化。
其他如 fopen
(打开文件)、copy
(复制文件)、stat
(获取文件状态)等函数,在操作带有 phar
伪协议的文件时,也都可能引发该漏洞。
来看一些代码
1 |
|
定义了Testobj
类,包含$output
属性(默认值为echo 'ok';
)和__destruct()
魔术方法
__destruct()
方法会在对象销毁时调用eval()
执行$output
中的代码
接收 GET 参数filename
,并通过file_exists()
检查该文件是否存在
看到这个file_exists
file_exists($filename)
函数的参数$filename
完全由用户控制,且该函数支持phar://
伪协议
当file_exists()
通过phar://
伪协议访问 Phar 文件时,会自动反序列化 Phar 文件中存储的对象
1 |
|
记得改一下配置文件
我这里是7版本
根据文件是否存在返回一个布尔值
因为这里使用的是file_exists
函数,是可以使用phar://
伪协议的,但是可以使用phar
伪协议并不代表一定存在phar
反序列化漏洞,还要看能否触发魔术方法进而可以执行一些操作,在这里就是要触发__destruct()
这个魔术方法进而可以执行命令,所以存在漏洞
发现这个文件已经存在了
最后就可以利用phar
伪协议读取这个文件,会自动将序列化部分的数据进行反序列化,然后就会自动触发__destruct()
魔术方法,从而可以执行eval($_GET["a"]);
,也就是可以进行GET传参一个a
进行RCE
成功利用
漏洞利用条件
phar
文件可以上传到服务器端- 要有可用反序列化魔术方法作为跳板(就是可以通过触发魔术方法
__destruct()
或__wakeup()
进行RCE之类的操作) - 要有文件操作函数,如:
file_exists()
,fopen()
,file_get_contents()
- 文件操作函数的参数可控
- 要使用的伪协议
phar://
中的字符和关键字没有被过滤
小技巧
使用phar://
伪协议读取文件是不看后缀的
如果服务器对上传的文件有限制,只能上传一些png
、jpg
、gif
等图片文件,我们可以把phar
文件修改成其他的任何一个格式的文件,并且不会受到影响
如下,使用mv
命令将test.phar
文件修改成test.png
文件,改变其后缀名
然后使用phar
伪协议读取test.png
文件仍然是可以执行命令的
1 | ?filename=phar://test.png&a=system('ls'); |
所以后缀名是没有影响的,只要可以上传到服务器就行
Phar反序列化例题
[HNCTF 2022 WEEK3]ez_phar
扫目录发现上传接口
这里有上传后缀的限制
写好exp 放phpstudy里访问一下让他生成phar文件
这的上传只能图片 所以我们抓包修改一下后缀
1 | http://node5.anna.nssctf.cn:29141/?filename=phar://upload/test.jpg&a=system('ls /'); |
通过phar伪协议访问
成功看到flag位置位于ffflllaaaggg
1 | ?filename=phar://upload/test.jpg&a=system('cat /ffflllaaaggg'); |
[SWPU 2018]SimplePHP
很经典的一道题目
访问这个可以看到file.php的内容
1 | //file.php |
我这里自己写的时候不理解为什么要试?file=file.php
导航栏中明确有 <a href="file.php?file=">查看文件</a>
,这直接告诉我们:
负责文件查看的后端页面是 file.php
;
该页面通过file参数接收 “要查看的文件路径”(?file=是空参数的默认状态)。
核心结论:
要查看任意文件,需构造 file.php?file=目标文件路径
的 URL
源代码里的
1 | //class.php |
1 | //function.php |
1 | //base.php |
1 |
|
一共就这几个文件
重点在class.php
从后往前看这个链子
首先看test类
我们最终要执行的是
那么现在问题就是这个value怎么替换成我们想要的f1ag.php
往上看
让test类里面的get函数会返回file_get 属性params不为空 且值要为f1ag.php
__get()方法在 读取不可访问(protected 或 private)或不存在的属性的值时被调用
我们想要触发上面的过程
要满足以下两个条件
1.访问test类里不存在的属性(触发_get方法)
2.传入的key要匹配params数组中
就是$key = “source”(因为$params[‘source’] = “f1ag.php”)
那么谁会去访问到test里面不存在的属性
这时候我们的思路就是去寻找“访问对象属性”的代码(如$obj->xxx)
在本题中,Show
类的__toString()
方法里有$this->str['str']->source
—— 这里的->source
就是在访问某个对象的source
属性。
这个时候我们进行一个假设 :
如果这是test类的实例 那么-source就是去访问这个类的source 由于test类没有source 所以就会触发_get 正好符合第一步的需求
这个source是在_tostring里面的
__toString()
的触发条件是 “对象被当作字符串处理”(如echo
、字符串拼接等)
所以我们这一步要找的就是:
哪些地方会进行字符串化操作(如echo $obj
、$obj . "str"
等)
1 | $show = new Show(); |
C1e4r
类的__destruct()
方法里有echo $this->test
如果$this->test
是Show
类的实例,echo
操作就会触发Show::__toString()
。
destruct在对象被销毁(反序列化结束之后)触发
所以只要C1e4r
类的实例被反序列化,就会触发其__destruct()
。
最后我们进行一下正向验证 看环节能不能衔接
- 反序列化
C1e4r
对象 → 触发__destruct()
→ 执行echo $this->test
($this->test
是Show
对象)。 echo Show对象
→ 触发Show::__toString()
→ 执行$this->str['str']->source
($this->str['str']
是Test
对象)。- 访问
Test->source
(不存在) → 触发Test::__get('source')
→ 从$this->params['source']
拿到f1ag.php
,调用file_get()
。
payload:
1 | //class.php |
抓包修改后缀上传 然后查看路径
phar访问就可以了
这个题难的在于代码有点多 链子的构造比phar难我觉得
解码