MyBatis中的SQL注入

MyBatis中的SQL注入

介绍

MyBatis是Java的一个持久层框架,作用是代替JDBC对数据库进行增删改查的功能。

Mybatis的执行过程为:

  1. MyBatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。MyBatis配置文件,包括MyBatis全局配置文件和MyBatis映射文件,其中全局配置文件配置了数据源、事务等信息;映射文件配置了SQL执行相关的信息(即Mapper.xml)。
  2. 通过SqlSessionFactory,MyBatis可以创建SqlSession(即会话),MyBatis是通过SqlSession来操作数据库的。
  3. MyBatis操作数据库。SqlSession本身不能直接操作数据库,它是通过一个我们定义好的Executor接口中的方法去操作数据库,而该方法的实现就是映射文件文件中定义的(即Mapper.xml)

MyBatis的SQL语句是以xml的方式写在一个Mapper.xml文件中。

实现

假设我们有一个User表,里面有id,name,pwd三个字段:

image-20220316094618350
  • 定义一个MyBatisUtils工具类去获取SqlSession
package com.example.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            // 使用Mybatis第一步:获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取SqlSession连接实例
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }
}
  • 定义User类用于传输和接收参数
package com.example.pojo;

public class User {
    private int id;  //id
    private String name;   //姓名
    private String pwd;   //密码

    //构造,有参,无参
    //set/get
    //toString()
  • 然后就定义Executor 接口 UserMapper,里面需要有执行的方法
package com.example.dao;

import com.example.pojo.User;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    // 根据id查询
    User getUserById(int id);
}
  • 定义映射文件配置 UserMapper.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.example.dao.UserMapper">
    <select id="getUserById" parameterType="int" resultType="com.example.pojo.User">
        select * from mybatis.user where id = #{id}
    </select>
  • 编写测试类
@Test
public void testGetUserById() {
    // 获取SqlSession连接
    SqlSession session = MybatisUtils.getSession();
    // 获取对应接口
    UserMapper mapper = session.getMapper(UserMapper.class);
    // 调用接口的方法,执行sql语句
    User user = mapper.getUserById(1);
    System.out.println(user);
    // 关闭SqlSession连接
    session.close();
}
image-20220316100132274

MyBatis两种取值符号

在上面的演示实例中,只使用到了MyBatis的一种取值方式也就是#{}这种方式的。

除了这种方式还有另外一种方式${value}

当parameterType为基本参数类型时,当取值符号为$时,{}中的值必须为value。当parameterType为对象时,{}中的值为对象属性名。

这两种取值符号的差别是:#{}实现了预编译,而${}没有!

因此在审计代码的时候只需要去留意用到了${}的SQL语句是否存在SQL注入。

SQL注入

Mybatis框架下易产生SQL注入漏洞的情况主要分为以下三种:

模糊查询

SQL中使用%作为通配符进行模糊查询,但是开发者使用下面这种方式拼接%会报错

select * from mybatis.user where name like "%#{value}%"

但是将#换成$就不会:

select * from mybatis.user where name like "%${value}%"

但是上面的语句就能够被 a" or 1=1# 注入。正确的模糊查询语句为:

select * from mybatis.user where name like "%"#{value}"%"
// 或者
select * from mybatis.user where name like concat('%', #{value},'%')

in 之后的多个参数

in之后多个id查询时使用# 同样会报错

select * from mybatis.user where id in (#{ids})   

同样地,将#换成$就不会:

select * from mybatis.user where id in (#{ids})  

但是上面的语句就能够被 1) or 1=1# 注入。正确的查询语句为用 foreach

select * from mybatis.user where id in
<foreach collection="list" item="item" open="("separatosr="," close=")">
#{item} 
</foreach> 

无法使用#的地方

SQL语句中的一些部分,例如order by字段、表名等,是无法使用预编译语句的。

order by字段

${}这种方式在动态排序时更加好用,比如当需要根据数据库字段id进行降序排列查询结果,#{}由于会给传入的值自动加上引号,导致查询语句变为了select * from user order by 'id' desc,此时会根据一个字符常量进行排序,显然不能得到我们想要的结果,此时就必须使用${}这种方式了,因此在涉及到排序相关的业务时很容易导致sql输入的产生。

表名

表名作为变量时,必须使用 ${}。这是因为,表名是字符串,使用 sql 占位符替换字符串时会带上单引号 '',这会导致 sql 语法错误,例如:

select * from #{tableName} where name = #{name}

假设我们传入的参数为 tableName = "user" , name = "John",那么在占位符进行变量替换后,sql 语句变为

select * from 'user' where name='John';

上述 sql 语句是存在语法错误的,表名不能加单引号 ''(注意,反引号`是可以的)。

这时候就导致了,当MyBatis配置文件中设置allowMultiQueries=true允许多条SQL语句执行的时候,就容易在表名处出现SQL注入。

配置文件:

<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;allowMultiQueries=true"/>

当输入的tableName 为 user'; delete user;#,查SQL询语句为:

select * from 'user'; delete user;#' where name = 'xxx';

修复建议

推荐开发在Java层面做映射,设置一个字段/表名数组,仅允许用户传入索引值。这样保证传入的字段或者表名都在白名单里面。(即白名单策略,不允许用户控制输入的内容)

参考资料

MyBatis和MyBatis可能导致的sql注入

MyBatis 一次执行多条SQL语句

Mybatis框架下SQL注入审计分析

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇