前言
这个标题起得有点唬人,一个成熟的标题在正常使用时是大概率不会出现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 |
|
他的返回值必须是User类
,这就以为我们不能拿取数据库的其他信息了吗?
在后端与数据库交互时,返回的User类数据是根据数据库字段和类成员变量对应赋值的,所以我们可以利用sql语句的别名功能,来更改我们所要信息的字段名称.
这里直接贴上所有脚本
1 | name=admin' and 1=2 union select 1, 2,database()# |
${}和#{}占位符分析
通俗来讲,两者就是${}
防不了sql注入,#{}
能防sql注入.
${}
${}叫做表达式语言,亦称EL表达式,是Java中的一种特护的通用编程语言,主要作用于JSP、XML,用以访问页面的上下文以及不同作用域中的对象 ,取得对象属性的值,或执行简单的运算或判断操作.
在mybatis,使用${}嵌入值,其会将数据直接拼接在sql语句中进行预处理,从而引发sql注入.
#{}
我们以如下的Mybatis举例
1 | public interface IUserDao { |
这是一个典型的select查询id的功能,利用#{}
占位符来插入id值.
但是当我们打印sql语句时,得到却是:
1 | select * from user where id = ? |
利用如下函数输出sql语句
1 | in = Resources.getResourceAsStream("SqlMapConfig.xml"); |
我们多尝试几次后发现,不管输入什么参数,打印出的SQL语句永远是将参数转为?
.这是因为MyBatis启动了预编译
功能,在SQL执行前,将SQL语句发给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符?
就可以了.因为SQL注入只对编译过程起作用,所以这样就避免了SQL注入的问题.
注:利用
#{}
占位符插入值时,Mybatis会自动将数据前后加上引号,这也就导致有时无法使用#{}
.
总结
当无法使用#{}
作为占位符时,我们必须将数据格式限制在我们可控范围内,或者将参数进行过滤.