HongCMS 代码审计

挺不错的一个CMS,防护做的很好,某些细节上的问题导致了漏洞

背景介绍

HongCMS是一个轻量级的中英文企业网站系统,访问速度极快,使用简单,程序代码简洁严谨,功能强大,完全免费开源,可用于建设各种类型的中英文网站,同时它是一个小型开发框架。

审计过程

先看下入口文件index.php

1
2
require(ROOT . 'includes/core.php');  //加载核心文件
APP::run();

core.php里定义了一些常量,并且连接了数据库,app的run方法则是框架的主方法,定义了如何加载文件
对于输入处理有两个方法,ForceStringFrom以及ForceIntFrom
首先看下ForceIntFrom

1
2
3
4
5
6
7
8
9
10
function ForceIntFrom($VariableName, $DefaultValue = 0) {
if (isset($_GET[$VariableName])) {
return ForceInt($_GET[$VariableName], $DefaultValue);
} elseif (isset($_POST[$VariableName])) {
return ForceInt($_POST[$VariableName], $DefaultValue);
} else {
return $DefaultValue;
}

}

如果$VariableName是通过GET或者POST获取到的则送入ForceInt函数处理,否则返回默认值
看下ForceInt

1
2
3
4
function ForceInt($InValue, $DefaultValue = 0) {
$iReturn = intval($InValue);
return ($iReturn == 0) ? $DefaultValue : $iReturn;
}

intval返回的是一个整数,所以被ForceIntFrom处理过的参数不存在注入,再来看下ForceStringFrom,是将ForceIntForm的ForceInt改为了ForceString,直接看ForceString

1
2
3
4
5
6
7
8
9
function ForceString($InValue, $DefaultValue = '') {
if (is_string($InValue)) {
$sReturn = EscapeSql(trim($InValue));
if (empty($sReturn) && strlen($sReturn) == 0) $sReturn = $DefaultValue;
} else {
$sReturn = EscapeSql($DefaultValue);
}
return $sReturn;
}

EscapeSql中最后是

1
$query_string = addslashes($query_string);

看来这个也没有希望了,看下别的。
发现GetIP函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function GetIP() {
if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
$thisip = getenv('HTTP_CLIENT_IP');
} elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
$thisip = getenv('HTTP_X_FORWARDED_FOR');
} elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
$thisip = getenv('REMOTE_ADDR');
} elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
$thisip = $_SERVER['REMOTE_ADDR'];
}

preg_match("/[\d\.]{7,15}/", $thisip, $thisips);
$thisip = $thisips[0] ? $thisips[0] : gethostbyname($_SERVER['HTTP_HOST']);
return $thisip;
}

注意最后的

1
$thisip = $thisips[0] ? $thisips[0] : gethostbyname($_SERVER['HTTP_HOST']);

这里的gethostbyname

​ 成功时返回 IPv4 地址,失败时原封不动返回 hostname 字符串。

只要gethostbyname失败,我们就可以使GetIP得到的返回值可控!
怎么样使它失败呢,很简单,只要Host不是一个可用地址就可以,也就是说如果我们填入SQL语句,它是肯定会失败的,之后会将我们的SQL语句返回
全局搜索下GetIP
在register.php

1
APP::$DB->exe("INSERT INTO " . TABLE_PREFIX . "user (groupid, activated, username, password, verifycode, joindate, joinip, lang, nickname, email) VALUES ($groupid, $activated, '$username', '".md5($password)."', '$verifycode', '".time()."', '".GetIP()."', $lang, '$nickname', '$email')");

没做任何过滤,利用起来就简单了
还有别的就不再写出来了
漏洞利用:http://noel.xin/2019/04/05/hongcms/

你来的话,日子会甜一点。