文章目錄
  1. 1. mybatis 概述
  2. 2. mybatis存在的sql注入问题
  3. 3. demo演示
  4. 4. 实战分析
  5. 5. 修复方案
  6. 6. 容易发生注入的点
  7. 7. 参考资料

mybatis 概述

MyBatis是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录

mybatis存在的sql注入问题

mybatis避免了用户直接拼接SQL语句,但是认为只要使用了mybatis就可以杜绝SQL注入的观点是不正确的。mybatis在配置SQL语句的时候,有两种描述参数的方式。#{}${}

比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

这个语句被称作 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap类型的对象,其中的键是列名,值便是结果行中的对应值。

注意参数符号:

#{id}

这就告诉 MyBatis 创建一个预处理语句参数,通过 JDBC,这样的一个参数在 SQL中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

使用#mybatis会使用预编译来处理参数,这样子可以避免SQL注入。

但是使用$描述参数会直接把变量拼接到SQL语句中,不做任何处理。相当于直接字符串拼接SQL。这样的方式就可能存在SQL注入。下面的描述来自mybatis官方文档。

默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:

ORDER BY ${columnName}
这里 MyBatis 不会修改或转义字符串。

NOTE 以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

demo演示

使用MyBatis使用示例作为demo测试。

mapper文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test.mybatis.UserMapper">
    <!-- 这里namespace必须是UserMapper接口的路径” -->
    <insert id="insertUser" parameterType="User">
        insert into user(name,age) values(#{name},#{age})
        <!-- 这里sql结尾不能加分号,否则报“ORA-00911”的错误 -->
    </insert>
    <!-- 这里的id必须和UserMapper接口中的接口方法名相同 -->
    <select id="getUser" resultType="User">
        select * from user where name=#{name}
    </select>
</mapper>

在输入中插入单引号

public static void getUser() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUser("'zhangsan");
        System.out.println("name: " + user.getName() + "|age: "
                + user.getAge());
    } finally {
        sqlSession.close();
    }

查看执行的sql语句:

可以看到输入被单引号包裹,并且对字符串中的单引号进行了转义。

#改成$,mapper文件为:

<select id="getUser" resultType="User">
    select * from user where name=${_parameter}
</select>

这里由于代码的写法问题,name需要改成_parameter,参考Mybatis中传参包There is no getter for property named XXX in class java.lang.String

查看执行的SQL语句:

输入没有任何改变的拼接到SQL中,造成了SQL注入。

实战分析

mybatis generator插件默认生成的order by 条件是使用$表示变量。

...
 <select id="selectByExample" resultMap="BaseResultMap" parameterType="com.tgwoo.ctspmt.model.MtVMsgItemExample" >
    <include refid="OracleDialectPrefix" />
    select
    <if test="distinct" >
      distinct
    </if>
    <include refid="Base_Column_List" />
    from CTSPMT.MT_V_MSG_ITEM
    <if test="_parameter != null" >
      <include refid="Example_Where_Clause" />
    </if>
    <if test="orderByClause != null" >
      order by ${orderByClause}
    </if>
    <include refid="OracleDialectSuffix" />
  </select>
...
  <sql id="OracleDialectPrefix" >
    <if test="page != null" >
      select * from ( select row_.*, rownum rownum_ from ( 
    </if>
  </sql>
  <sql id="OracleDialectSuffix" >
    <if test="page != null" >
      <![CDATA[ ) row_ ) where rownum_ > #{page.begin} and rownum_ <= #{page.end} ]]>
    </if>
  </sql>
...

所以使用mybatis generator插件生成的代码中,需要自己过滤order by参数,如果用户可控就会导致SQL注入。而大部分情况下,开发者可能没有意识到这个问题。

以freecms为例。Freecms是一款开源javacms。最新版下载地址。freecms中的orderby都是使用的$描述变量。

前面的分析可以知道,这种情况,mybatis会直接拼接字符串到SQL中不做任何处理。

注册用户登录访问:

http://192.168.99.100:8080/freecms/member/creditlog_list.do?order=extractvalue(1,concat(0x7C,(select%20user()),0x7C))

查看执行的SQL为:

修复方案

尽量使用#描述参数,如果一定要使用$,则需要自己过滤用户输入。

容易发生注入的点

Mybatis框架下SQL注入漏洞面面观中提到了3个点。

1. 模糊查询like SQL注入修复建议
按照新闻标题对新闻进行模糊查询,可将SQL查询语句设计如下:
select * from news where tile like concat(‘%’,#{title}, ‘%’),
采用预编译机制,避免了SQL语句拼接的问题,从根源上防止了SQL注入漏洞的产生。

2.  in之后的参数SQL注入修复建议
在对新闻进行同条件多值查询的时候,可使用Mybatis自带循环指令解决SQL语句动态拼接的问题:
select * from news where id in
<foreach collection="ids" item="item" open="("separator="," close=")">#{item} </foreach>

3. order by SQL注入修复建议--在Java层面做映射
预编译机制只能处理查询参数,其他地方还需要研发人员根据具体情况来解决。如前面提到的排序情景: Select * from news where title =‘京东’ order by #{time} asc,这里time不是查询参数,无法使用预编译机制,只能这样拼接:Select * from news where title =‘京东’ order by ${time} asc 。
针对这种情况研发人员可以在java层面做映射来进行解决。如当存在发布时间time和点击量click两种排序选择时,我们可以限制用户只能输入12。当用户输入1时,我们在代码层面将其映射为time,当用户输入2时,将其映射为click。而当用户输入12之外的其他内容时,我们可以将其转换为默认排序选择time(或者click)。

目前在使用mybatis中,一般会使用插件自动生成代码。插件自动生成的代码中,in语句已经是默认安全的方式了。只有orderby和limit处是使用字符串拼接。而默认情况下应该是没有like语句。如果程序员需要使用like需要自己实现。

参考资料

Mybatis框架下SQL注入漏洞面面观

文章目錄
  1. 1. mybatis 概述
  2. 2. mybatis存在的sql注入问题
  3. 3. demo演示
  4. 4. 实战分析
  5. 5. 修复方案
  6. 6. 容易发生注入的点
  7. 7. 参考资料