什么是序列化
为了储存对象,serialize
产生一个可存储的值的表示(字符串),并且可以通过unserialize
对字符串进行解析。
由于PHP本身对数据类型不敏感,不同类型对数据可以对变量进行赋值,反序列化过程由序列化字符串提供对象数量、对象数据类型、对象长度等信息,因此存在利用。
<?php
class foo{
public $a = 1;
protected $b = 1;
private $c = 3;
}
$tmp = new foo();
echo serialize($tmp);
// O:3:"foo":3:{s:1:"a";i:1;s:4:"�*�b";i:1;s:6:"�foo�c";i:3;}
访问类型 | 变量名 | 序列化后变量长度 | 备注 |
---|---|---|---|
public | a | 1 | |
protected | b | 1 + 3 | |
private | c | 1 + 5 |
不同访问类型的序列化结果是不同的,但PHP7.1版本对数据访问类型不敏感,可使用public
代替其他类型数据
魔术方法及绕过
方法 | 触发 | 用途 | 绕过方式 | 备注 |
---|---|---|---|---|
__sleep() | serialize() | 过滤不必要的对象存储,只存储数组中的变量名 | ||
__wakeup() | unserialize() | 用于反序列化调用之前初始化参数 | 修改序列化字符串中类含有的对象个数,当个数与类不匹配则不触发 | |
__tostring() | echo |
<?php
class foo{
public $a;
protected $b;
private $c;
public function __construct()
{
$this->a = 1;
$this->b = 1;
$this->c = 3;
echo "__construct
";
}
public function __destruct()
{
var_dump($this);
echo "__destruct
";
}
public function __wakeup()
{
echo "__wakeup()
";
}
public function __sleep()
{
echo "__sleep()
";
return array('a','b');
}
public function __toString()
{
echo "__toString()
";
return "";
}
}
$tmp = new foo();
echo $tmp;
echo "serialize start:
";
$tmp = serialize($tmp);
echo "unserialize start:
";
unserialize($tmp);
/*
__construct
__toString()
serialize start:
__sleep()
object(foo)#1 (3) {
["a"]=>
int(1)
["b":protected]=>
int(1)
["c":"foo":private]=>
int(3)
}
__destruct
unserialize start:
__wakeup()
object(foo)#1 (3) {
["a"]=>
int(1)
["b":protected]=>
int(1)
["c":"foo":private]=>
NULL
}
__destruct
*/
注意反序列化是不会调用构造函数的
修改类型
<?php
class test1{
}
class test2{
public function __wakeup(){
// echo "flag";
}
}
class foo{
public $a;
protected $b;
private $c;
public function __construct()
{
$this->a = 1;
$this->b = 1;
$this->c = new test1();
}
}
$tmp = new foo();
var_dump($tmp);
echo "serialize start:
";
$tmp = serialize($tmp);
echo $tmp . "
";
echo "unserialize start:
";
$tmp = unserialize('O:3:"foo":3:{s:1:"a";i:1;s:4:"�*�b";i:1;s:6:"�foo�c";O:5:"test2":0:{}}');
var_dump($tmp);
/*
object(foo)#1 (3) {
["a"]=>
int(1)
["b":protected]=>
int(1)
["c":"foo":private]=>
object(test1)#2 (0) {
}
}
serialize start:
O:3:"foo":3:{s:1:"a";i:1;s:4:"�*�b";i:1;s:6:"�foo�c";O:5:"test1":0:{}}
unserialize start:
object(foo)#1 (3) {
["a"]=>
int(1)
["b":protected]=>
int(1)
["c":"foo":private]=>
object(test2)#2 (0) {
}
}
*/
实验中,变量类test1,可以替换为类test2,并与类test2中的魔术方法一起配合达到攻击的目的
逃逸覆盖
反序列化过程字符串的截取长度是由序列中给出的长度决定的,利用这一点可以伪造
<?php
class foo{
public $a = "aaaaa";
public $b = "bbbbb";
public $c = "ccccc";
}
$tmp = new foo();
$tmp = serialize($tmp);
echo $tmp;
// O:3:"foo":3:{s:1:"a";s:5:"aaaaa";s:1:"b";s:5:"bbbbb";s:1:"c";s:5:"ccccc";}
$payload = 'bbbbb";s:1:"c";s:5:"ddddd";}';
$ser = 'O:3:"foo":3:{s:1:"a";s:5:"aaaaa";s:1:"b";s:5:"'.$payload.'";s:1:"c";s:5:"ccccc";}';
var_dump(unserialize($ser));
/*
{
["a"]=>
string(5) "aaaaa"
["b"]=>
string(5) "bbbbb"
["c"]=>
string(5) "ddddd"
}
*/
- 使字符串长度s变小
- 使字符串本身经过replace后变长