基于排序的SQL猜解问题

 

问题的发现

最近在对公司后台代码安全审计的过程中,发现了一种有意思的漏洞类型,一种基于排序的SQL猜解攻击问题,我们且抽一段片段代码来看一下

<select id="queryUsers" resultType="com.test.open.mis.dao.Users">
        select u.user_id, u.username, u.email, u.mobile, u.status,u.password_status from crm_user
        <where>
            <if test="username != null and username.trim() != ''">
                and u.`username` like concat('%',#{username},'%')
            </if>
            <if test="userId != null and userId != ''">
                and u.`user_id` != #{userId}
            </if>
        </where>
        <choose>
            <when test="sidx != null and sidx.trim() != ''">
                order by u.${sidx} ${order}
            </when>
            <otherwise>
                order by u.user_id desc
            </otherwise>
        </choose>
        <if test="offset != null and limit != null">
            limit #{offset}, #{limit}``
        </if>
    </select>

如果直接观测这段代码,似乎有SQL注入的问题,在Spring的,SPEL表达式 # 相当于占位符,这有效的防止SQL注入的发生,所以一般对于mapper的审计,我们就观测是否有使用 $ 的地方,在根据对应的id去追溯使用到它的代码,查看是否有SQL注入

@Pattern(regexp = "^(?i)DESC|(?i)ASC$",name="排序",message = "order非法")

继续追踪代码,于是发现程序对 sidx 和 order 做了安全校验,这似乎没有什么问题了, 于是这一处就通过了SDL团队的安全测试。可是再继续追溯起来我发现这段代码依然存在安全问题,因为它虽然防止了SQL注入攻击,但是它没有对排序字段做限制,这就存在了一个问题

当我企图使用password进行排序的时候,服务器成功返回了数据,那么这意味着我能够使用order by进行排序攻击,举个例子,我们查询一张常规的用户表

 mysql> select * from crm_user;
+-----+----------+--------------+
| uid | username | password     |
+-----+----------+--------------+
|   1 | 111      | 111          |
|   2 | enoch    | apple        |
|   3 | sam      | boomsakalaka |
|   4 | belln    | zipzip       |
|   5 | mosuan   | 123456       |
|   6 | xsseng   | 123456       |
+-----+----------+--------------+
6 rows in set (0.00 sec)

我们根据password对数据进行升序排序,就会发现数据由密码的首字符1-9a-z如此升序排序

mysql> select * from crm_user order by password asc;
+-----+----------+--------------+
| uid | username | password     |
+-----+----------+--------------+
|   1 | 111      | 111          |
|   5 | mosuan   | 123456       |
|   6 | xsseng   | 123456       |
|   2 | enoch    | apple        |
|   3 | sam      | boomsakalaka |
|   4 | belln    | zipzip       |
+-----+----------+--------------+
rows in set (0.00 sec)

当然即便这样也可以

mysql> select uid from crm_user order by password asc;
+-----+
| uid |
+-----+
|   1 |
|   5 |
|   6 |
|   2 |
|   3 |
|   4 |
+-----+
rows in set (0.00 sec)

这就有了我们的上一处漏洞的场景,即便没有查询出password,但是对用户表查询过程中可以由用户自定义排序造成了安全隐患,排列出三种攻击手法

1.基于明文储存情况下的密码猜解攻击:

假设belln的密码是zipzip,我们可以通过设置一个密码为yzzzzzzz的用户猜解出belln的密码以z打头,再通过如此反复猜解出后面的密码

mysql> select * from crm_user order by password asc;
+-----+----------+--------------+
| uid | username | password     |
+-----+----------+--------------+
|   1 | 111      | 111          |
|   5 | mosuan   | 123456       |
|   6 | xsseng   | 123456       |
|   2 | enoch    | apple        |
|   3 | sam      | boomsakalaka |
|   7 | attack0  | yzzzzzzzzz   |
|   8 | attack1  | zhzzzzzzzz   |
|   4 | belln    | zipzip       |
|   9 | attack2  | zizzzzzzzz   |
+-----+----------+--------------+
rows in set (0.00 sec)

我们通过attack0用户猜解出它的密码为z打头;又通过attack1、attack2猜解出其密码前两位为zi;如此反复我们可以得到后台每一位用户的密码,这只需要你写一个脚本,并且每次更改以后查询/admin/user/list的数据进行比对,你就可以猜解出所有用户的口令

2.弱口令造成的安全问题

这个问题原理就比较简单,如果用户xsseng通过这种手法一定可以了解到mosuan的密码可能与他相同,因为他们的正反排序都粘合在一起,如下表:

mysql> select uid,username,md5(password) from crm_user order by password asc;
+-----+----------+----------------------------------+
| uid | username | md5(password)                    |
+-----+----------+----------------------------------+
|   1 | 111      | 698d51a19d8a121ce581499d7b701668 |
|   5 | mosuan   | e10adc3949ba59abbe56e057f20f883e |
|   6 | xsseng   | e10adc3949ba59abbe56e057f20f883e |
|   2 | enoch    | 1f3870be274f6c49b3e31a0c6728957f |
|   3 | sam      | 8f1e77c1e064d9b3e20ada1e5ee85175 |
|   7 | attack0  | 54a6d46b0356cec2a7190898ff609af6 |
|   8 | attack1  | fa891568c546c8beec0d7aceeafe3133 |
|   4 | belln    | adc472b32512cf2f1b89641b7933af52 |
|   9 | attack2  | 3c9c1f77627d03b225bb67b538cbc352 |
+-----+----------+----------------------------------+
rows in set (0.00 sec)

这种攻击适用于密码hash后储存在数据库中,中大型互联网公司后台成员人数比较多,这种攻击手法可以在不被前台锁定的情况下对用户账户进行暴力破解。当然也可以对hash值进行猜解,只要后端直接接受加密之后的新密码,这种情况也比较常见,因为这么做可以防止劫持并且给后端减轻工作量,但是如果后端接受的是明文,这种猜解将不复存在

3.大样本安全问题

在思考过如上两种问题后,我将目标放到了前台用户表中,这可是几百上千万的样本数据。结合以上我们会发现如果存在该漏洞,并且样本空间足够大, 即便后台管理员只能查询到前台的用户名,但是利用该漏洞它可以大规模的对前台弱口令用户批量盗号、猜解前台用户支付密码、猜解前台用户手机号,只要这些信息都储存在一张表中并且没有对排序字段进行安全限制

 

问题的严峻性

在我发现问题后,我搜索发现目前相关资料较少,一个是这种问题大多数存在于后台,一个是问题的危害有待挖掘,所以我简单的整理了一下分享给大家。为此我还搜寻了github的代码,以$_GET[‘sort’]为例,第一页就是一段可能存在问题的代码,我十分确信的是这个漏洞和我测试中的漏洞一摸一样,是一个后台用户排序的安全问题,当然还有更多的关键字包括 sort、sidx、order、field等等

 

问题的防御

在开发过程中,后台对排序的需求比较大,开发人员的安全意识薄弱,就会造成这种安全问题。缺乏经验、精力的安全团队在这方面也相对薄弱,可能很多后台还会存在相似的问题。这就要求安全测试人员在安全测试中注意order by可能带来的问题,以及对数据安全的治理。

(完)