Hpdoger's Blog.

代码审计复现:Bluecms 1.6

Word count: 1,213 / Reading time: 6 min
2018/08/18 Share

写在前面

最近一阵子得了一场病,加之情感上的一件事,痛不欲生。陆陆续续的缓过来了,渡劫余生,留下该留下的。病也慢慢在恢复了。

前些日子说要学代码审计,买了本《代码审计》看了两天,为作者尹毅先生无限打call,人生导师一样的人物,经历是传奇的,努力是可见的。书中开篇点题为什么要代码审计?这是web狗的一项技能。其实,当初学安全的时候我一直想要走的方向是渗透,虽然至今也是。但是渗透就仅仅是用工具来attack么?不,渗透是一种思路,是一种积累,也是一种艺术。它是我们基础的升华,经验的绽放。脚本小子use tools will nerver be a hacker。我们要学的、做的要很多,知识面要很宽,尽管路会很难。

从今天起,至未来的一个月,会把学习的全部精力都投入到审计方向,立下flag:未来半个月内拿自己的cve

环境

cms: bulecms 1.6 sp1
php: 5.4 + mysql 5.5.53

sql注入一

代码审计

问题文件位于:/uploads/ad_js.php

1
$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id);

变量未用单引号闭合,可能会引起注入

跟踪一下$ad_id,查找该参数如何获得

1
$ad_id = !empty($_GET['ad_id']) ? trim($_GET['ad_id']) : '';

trim去掉ad_id两侧空格,未过滤参数,可注入

再追踪一下getone()函数怎么定义的,一个定义mysql相关操作的文件位于/uploads/include/mysql.class.php:

1
2
3
4
5
function getone($sql, $type=MYSQL_ASSOC){
$query = $this->query($sql,$this->linkid);
$row = mysql_fetch_array($query, $type);
return $row;
}

追踪此类里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;
}
}

查询出错则dbshow进行报错,有结果则返回$query集合后,$row进行取值

复现

sql注入二

一开始审了一个前台/uploads/user.php的宽字节注入,记一下思路:

在mysql.class.php中看到:

1
mysql_query( "SET NAMES gbk");

看一下有没有进行addslashes过滤

果然对POST\GET过滤,追踪deep_addslashes

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
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
elseif($act == 'index_login'){
$user_name = !empty($_REQUEST['user_name']) ? trim($_REQUEST['user_name']) : '';
$pwd = !empty($_REQUEST['pwd']) ? trim($_REQUEST['pwd']) : '';
$remember = isset($_REQUEST['remember']) ? intval($_REQUEST['remember']) : 0;
if($user_name == ''){
showmsg('�û�������Ϊ��');
}
if($pwd == ''){
showmsg('���벻��Ϊ��');
}
$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$user_name'");
if($row['num'] == 1){
showmsg('ϵͳ�û��鲻�ܴ�ǰ̨��¼');
}
$w = login($user_name, $pwd);

if(defined('UC_API') && @include_once(BLUE_ROOT.'uc_client/client.php')){
list($uid, $username, $password, $email) = uc_user_login($user_name, $pwd);
if($uid>0){
$password = md5($password);
if(!$w){
$db->query("INSERT INTO ".table('user')." (user_name, pwd, email, reg_time) VALUES ('$username', '$password', '$email', '$timestamp')");
$w = 1;
}
$ucsynlogin = uc_user_synlogin($uid);
}
elseif($uid === -1){
if($w == 1){
$user_info = $db->getone("SELECT email FROM ".table('user')." WHERE user_name='$user_name'");
$uid = uc_user_register($user_name, $pwd, $user_info['email']);
if($uid > 0) $ucsynlogin = uc_user_synlogin($uid);
}else $w = -1;
}
elseif($uid == -2){
showmsg('�������');
}
echo $ucsynlogin;
}
if($w == -1 || $w == 0){
showmsg('��������û��������벻��ȷ');
}
elseif($w == 1){
update_user_info($user_name);
if($remember==1){
setcookie('BLUE[user_id]', $_SESSION['user_id'], time()+172800, $cookiepath, $cookiedomain);
setcookie('BLUE[user_name]', $user_name, time()+172800, $cookiepath, $cookiedomain);
setcookie('BLUE[user_pwd]', md5(md5($pwd).$_CFG['cookie_hash']), time()+172800, $cookiepath, $cookiedomain);
}
showmsg('��ӭ�� '.$user_name.' ���������ڽ�ת����Ա����', 'user.php');
}
}

追踪user_name怎么传入:

1
$user_name = !empty($_REQUEST['user_name']) ? trim($_REQUEST['user_name']) : '';

发现无过滤

再追踪一下对suername的sql语句如何执行:

1
$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$user_name'");

看到调用了getone()函数,第一个注入有介绍。

再看到下面一句:

1
2
3
if($row['num'] == 1){
showmsg('ϵͳ�û��鲻�ܴ�ǰ̨��¼');
}

在admin的表中查询admin_name表中是否有传入的user_name,若存在,$row[‘num’]值为1,然后执行showmsg函数,输出:“前台无法登陆”后返回主页。值为0进行以下操作:

1
$w = login($user_name, $pwd);

再追踪login函数得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function login($user_name,$pwd){
global $db;
$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('user')." WHERE user_name='$user_name'");
if($row['num']==0){
$result = 0;
}else{
$sql = "SELECT COUNT(*) AS num FROM ".table('user')." WHERE user_name='$user_name' and pwd=md5('$pwd')";
$user_num = $db->getone($sql);
if($user_num['num']){
$result = 1;
}else $result = -1;
}
return $result;
}

到这里我们可以理解,这个页面的登陆逻辑是这样的:

如果我们的用户名是admin表中用户名,则不允许登陆
若不是表中的用户名,则会进行user表的对比查询,再判断是否有这个用户

明确思路:盲注
注入是否成功的判断条件:$row[‘num’]返回值

复现

success injection:

default injection:

google一下发现别人挖过后台登陆验证的宽字节,能够利用…

相关链接

p师傅的浅析白盒审计中的字符编码及SQL注入:http://www.freebuf.com/articles/web/31537.html

CATALOG
  1. 1. 写在前面
  2. 2. 环境
  3. 3. sql注入一
    1. 3.1. 代码审计
    2. 3.2. 复现
  4. 4. sql注入二
    1. 4.1. 复现
  5. 5. 相关链接