Mybatis框架下的sql注入

Mybatis是作为一款半自动化的持久层框架,因为sql语句都要我们自己手动编写还要注意安全问题,所以Mybatis在一定程度上帮我们避免了这些问题,但是Mybatis不意味着一定的安全,它总共有两个参数# {id}${id}
- #{id}:相当于JDBC中的PreparedStatement,预编译处理参数,可以有效的避免注入
- ${id}:是未经过预编译,仅仅取输出变量的值
总的来说,使用#{id}是通过预编译处理后 ,不会存在注入的情况,而${id}则可能会出现,在实际业务中,有些情况还必须使用出这种拼接的方式。主要出现在以下几种情况:

  • 使用like 关键字
  • 使用order by关键字
  • 使用in关键字

一、idea环境配置:

为了方便测试语句的顺利执行,暂时对sql语句的报错进行忽略,进行强制编译。idea设置如下:

1.setting->java compiler

2.run->Edit configuration->选择编译,不进行错误校验。

二、数据库、PO、映射文件、配置文件等。

其中数据库的user表为下图所示:

三、使用like 关键字

3.1关键代码:

User的xml配置如下:

<select id="findUserByName" parameterType="String" resultType="it.lingwu.sec.PO.User">
    SELECT * FROM USER WHERE USERNAME LIKE '%${value}%'
</select>

测试用例如下:

public class TestLike {
    @Test
    public  void test() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = build.openSession();
        //User user = session.selectOne("findUserById", 1);
        List<User> user=session.selectList("findUserByName","陈");
        System.out.println(user);
        session.close();
    }
}

可以看到,此时正确返回2个结果,返回结果如下:

[User{id=22, username='陈大明', birthday=2015-02-09, sex='1', address='天津市'}, 
User{id=25, username='陈小明', birthday=2015-02-01, sex='1', address='深圳市'}]

修改传入的值为”陈%’ or username like ‘%’ – “”,返回User表中所有数据,

List<User> user=session.selectList("findUserByName","陈%' or username like '%' -- ");

3.2修复方式

可将‘%${value}%’ ,替换为#{id}的形式,并用concat函数将%与其拼起来, 如下代码所示:

<select id ="findUserByNameTrue" parameterType="String" resultType="it.lingwu.sec.PO.User">
    SELECT * FROM USER WHERE USERNAME LIKE CONCAT('%',#{id},'%')
</select>

能过这种拼接的形式,仍然采用#{}占位的方法,有效避免sql注入的风险。

四、使用order by关键字

4.1关键代码:

User的xml配置如下:

<select id="findUserById" parameterType="String" resultType="it.lingwu.sec.PO.User">
    select * from user ORDER BY ${value} DESC
</select>

测试用例:

public class TestOrderBy {
    @Test
    public  void test() throws IOException {
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);
    SqlSession session = build.openSession();
    List<User> user=session.selectList("findUserById","IF(1=1,id,sex) ");
    System.out.println(user);
    session.close();
    }
}

执行IF(1=1,id,sex)语句后,输出结果正常,可以看到,以id的逆序排序:

[User{id=25, username='陈小明', birthday=2015-02-01, sex='1', address='深圳市'}, User{id=24, username='张三丰', birthday=2015-01-09, sex='1', address='天津市'}, User{id=22, username='陈大明', birthday=2015-02-09, sex='1', address='天津市'}, User{id=16, username='张小明', birthday=2015-07-09, sex='1', address='上海市'}, User{id=10, username='张三', birthday=2014-07-10, sex='1', address='北京市'}, User{id=1, username='王五', birthday=null, sex='2', address='null'}]

执行IF(1=12,id,sex)语句后,可以看到,以性别的逆序排序:

[User{id=1, username='王五', birthday=null, sex='2', address='null'}, User{id=10, username='张三', birthday=2014-07-10, sex='1', address='北京市'}, User{id=16, username='张小明', birthday=2015-07-09, sex='1', address='上海市'}, User{id=22, username='陈大明', birthday=2015-02-09, sex='1', address='天津市'}, User{id=24, username='张三丰', birthday=2015-01-09, sex='1', address='天津市'}, User{id=25, username='陈小明', birthday=2015-02-01, sex='1', address='深圳市'}]

4.2修复方式

通过映射的方式解决该问题。如,id映射为1,sex映射为2,只允许用户输1与2。当输入1和2之外的其他内容时,将其转换为默认排序选择1。

五、使用in关键字

5.1关键代码:

User的xml配置如下:

<select id="findUserByIn" parameterType="String" resultType="it.lingwu.sec.PO.User">
    SELECT * FROM USER where id in (${value})
</select>

测试用例:

@Test
public  void test() throws IOException {
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);
    SqlSession session = build.openSession();
    List<User> user=session.selectList("findUserByIn","1,24");
    System.out.println(user);
    session.close();
}

执行后,正常返回两个结果:

[User{id=1, username='王五', birthday=null, sex='2', address='null'}, User{id=24, username='张三丰', birthday=2015-01-09, sex='1', address='天津市'}]

将执行语句改成“select id from user”后,返回6个结果:

[User{id=1, username='王五', birthday=null, sex='2', address='null'}, User{id=10, username='张三', birthday=2014-07-10, sex='1', address='北京市'}, User{id=16, username='张小明', birthday=2015-07-09, sex='1', address='上海市'}, User{id=22, username='陈大明', birthday=2015-02-09, sex='1', address='天津市'}, User{id=24, username='张三丰', birthday=2015-01-09, sex='1', address='天津市'}, User{id=25, username='陈小明', birthday=2015-02-01, sex='1', address='深圳市'}]

5.2修复方式

UserMapeper.xml文件如下:

<select id="findUserByInTrue"  parameterType="String" resultType="it.lingwu.sec.PO.User">
    SELECT * FROM USER
    <where>
        <if test="ids!=null and ids.size>0">
            <foreach collection="ids" item="id" open="id in (" close=")"  separator="," >
                #{id}
            </foreach>
        </if>
    </where>
</select>

测试用例:

public class TestIn {
    @Test
    public  void test() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);
               SqlSession session = build.openSession();
        UserMap userMapper=session.getMapper(UserMap.class);
        User query = new User();
        ArrayList<Integer> ids = new ArrayList<Integer>();
        ids.add(1);
        ids.add(10);
        query.setIds(ids);
        List<User> users = userMapper.findUserByInTrue(query);
        System.out.println(users);
        session.close();
    }
}

六、总结

从白盒角度来看,可直接从相关文件搜索“$”,再进一步确认;
从黑盒角度来看,可重点关注可能有like,order by,in等关键词的sql语句的地方。