代码审计复现:Bluecms 1.6

写在前面

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

前些日子说要学代码审计,买了本《代码审计》看了两天,为作者尹毅先生无限打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

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

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

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

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

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

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

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

追踪此类里query函数的定义:

    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中看到:

mysql_query( "SET NAMES gbk");

看一下有没有进行addslashes过滤

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

function deep_addslashes($str)
{
    if(is_array($str))
    {
        foreach($str as $key=>$val)
        {
            $str[$key] = deep_addslashes($val);
        }
    }
    else
    {
        $str = addslashes($str);
    }
    return $str;
}

联想宽字节,先追踪一下处理表单的方法

 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怎么传入:

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

发现无过滤

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

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

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

再看到下面一句:

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

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

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

再追踪login函数得到:

 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

not found!