MyBatis-SQL注入
MyBatis-SQL注入
学习核心
- SQL注入问题
- MyBatis如何避免SQL注入
学习资料
SQL注入
MyBatis提供了两种方式实现DAO层,一种是直接通过注解形式在接口方法中定义SQL,另一种则是通过XML定义方式(动态SQL)
SQL注入则指的是在一些拼接SQL的场景,攻击者利用程序漏洞恶意将一些自定义的SQL指令提交到后台执行,进而触发一些非开发者意图的数据库操作。这种情况一般是因为数据交互的时候前端没有对传入的数据做严格校验,误将一些数据动态拼接到SQL语句中执行,从而导致数据库受损(被脱库、被删除、甚至整个服务器被攻击等)
案例分析:登录免密校验漏洞
以最基础的查询SQL进行分析,例如要根据用户名查询用户信息,后台的SQL定义如下。前端传入用户名后进入查询拼接
String sql = "select * from tb_user where username='" + userName + "' and password = '" + password + "'"
当前端传入的userName:noob' or 1=1 --
;password 为空,那么整个SQL拼接的内容就会如下所示
String sql = "select * from tb_user where username='noob' or 1=1 --' and password ="
可以看到拼接后的SQL语句后面的password校验被当做注释处理了,整条SQL语句变为select * from tb_user where username='noob' or 1=1
,那么这条SQL无论用户输入什么用户名其都是可执行且成立的,攻击者可以通过这个漏洞来骗过系统获取到合法的身份
==解决方案:==前端做严格参数校验,后端尽量避免硬核SQL拼接
MyBatis的SQL注入
MyBatis的SQL语句可以基于注解的方式写在类方法上面,更多的是以xml的方式写到xml文件
MyBatis中SQL语句需要我们自己手动编写或者用generator自动生成。编写xml文件时,Mybatis支持两种参数符号,一种是#,另一种是$
<select id="queryAll" resultMap="resultMap">
SELECT * FROM NEWS WHERE ID = #{id}
</select>
MyBatis解决SQL注入的核心是采用预编译模式,使用#拼接SQL语句(占位符)
- #{}:先编译sql语句,再给占位符传值。底层是PreparedStatement实现,可以防止sql注入
- ${}:先进行sql语句拼接,然后再编译sql语句。底层是Statement实现,存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到
#{}
的使用
<select id="queryAll" resultMap="resultMap">
SELECT * FROM TB_USER WHERE NAME = #{name}
</select>
SQL执行效果参考(传入参数"noob"),日志打印参考
===》 Preparing:SELECT * FROM TB_USER WHERE NAME = ?
===》 Parameters:noob(String)
《=== Total:1
#{}会先进行sql语句的预编译,然后再给占位符传值(其中?表示占位符)
${}
的使用
<select id="queryAll" resultMap="resultMap">
SELECT * FROM TB_USER WHERE NAME = '${name}'
</select>
SQL执行效果参考(传入参数"哈哈哈")
SELECT * FROM NEWS WHERE ID = '哈哈哈'
${}是先进行SQL的拼接然后再编译。此处需注意字符串处理(在SQL语句中应该添加单引号)
如果针对字符串格式的处理,使用${}不添加单引号,则SQL拼接后编译执行就会报错(抛出SQLSyntaxErrorException异常:Unkown column '哈哈哈' in 'where clause'
)
如果是针对SQL关键字的拼接,此处则需要使用${}
。例如需要传入自定义的排序规则,则执行desc、asc等SQL关键字,这个关键字是不需要要加单引号的,但是如果使用#{}就会先预编译后设置值(达不到预期的效果),而使用${}
则可直接拼接即可。例如此处案例传入orderRule用于限定排序规则,则不需要加单引号,只能使用${}
<select id="queryAll" resultMap="resultMap">
SELECT * FROM TB_USER
ORDER BY NAME ${orderRule}
</select>
类似的如果传入的是表名这类不需要加单引号的,则需要使用${}
进行
<select id="queryAll" resultMap="resultMap">
SELECT * FROM ${tableName}
</select>
MyBatis中的SQL注入问题排查
- 先全局检查mapper.xml文件中的
$
,逐个排查分析 - 重点关注like、in、order by关键字的使用
- Java层要做好参数检查,避免用户恶意输入,防范潜在的SQL攻击
一些特殊场景下${} VS #{}
拼接表名
# 指定tableName为tb_user
SELECT * FROM ${tableName}
=> SELECT * FROM tb_user
SELECT * FROM #{tableName}
=> SELECT * FROM 'tb_user' // 无法正常执行
批量操作
# SQL语句定义
delete from t_user where id = 1 or id = 2 or id = 3;
delete from t_user where id in(1, 2, 3);
传入字符串1,2,3
# 如果使用#{} 则其处理效果
delete from t_user where id in('1, 2, 3'); // 执行错误:1292 - Truncated incorrect DOUBLE value: '1,2,3'
# 如果使用${} 则其处理效果
<delete id="deleteBatch">
delete from t_user where id in(${ids})
</delete>
=》 delete from t_user where id in(1, 2, 3);
模糊查询
例如查询name关键字
# 使用${}
<select id="selectByKey" resultType="User">
select * from tb_user
where name like '%${nameKey}%'
</select>
# 使用#{} concat
<select id="selectByKey" resultType="User">
select * from tb_user
where name like concat('%',#{nameKey},'%')
</select>
# 使用#{} 双引号方式
<select id="selectByKey" resultType="User">
select * from tb_user
where name like "%"#{nameKey}"%"
</select>