防止SQL注入的中心思想就是参数化查询
,将输入当作参数传递,而不是直接拼接到 SQL 语句中。
常见的防止SQL注入的方式
-
1、使用
#{}
占位符 -
2、使用动态SQL
-
3、[配置 SQL 注入过滤器](#配置 SQL 注入过滤器)
使用#{}
占位符
- 先来看一个错误的示范
${}
// 错误示范
@PostMapping("mybatiswrong")
public List mybatiswrong(@RequestParam("name") String name) {//curl -X POST http://localhost:18081/sqlinject/mybatiswrong?name=test//python sqlmap.py -u http://localhost:18081/sqlinject/mybatiswrong --data name=test --current-db --flush-sessionreturn userDataMapper.findByNameWrong(name);
}@Select("SELECT id,name FROM `userdata` WHERE name LIKE '%${name}%'")
List<UserData> findByNameWrong(@Param("name") String name);
- 使用sqlmap测试是否注入
C:\Python310\sqlmap>python sqlmap.py -u http://localhost:18081/sqlinject/mybatiswrong --data name=test --current-db --flush-session
...........................[13:32:44] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.6
[13:32:44] [INFO] fetching current database
current database: 'sqlinject'
....................................C:\Python310\sqlmap>
从结果可以看出来,注入成功了,获取到了数据库名称。
${}
占位符会将参数值直接替换到 SQL 语句中。这可能导致 SQL 注入的风险,因此应谨慎使用。只有当你完全信任传入的值并且确定它是安全的时,才可以使用${}
占位符。
#{}
占位符
@PostMapping("mybatisright")
public List mybatisright(@RequestParam("name") String name) {//curl -X POST http://localhost:18081/sqlinject/mybatisright?name=test// python sqlmap.py -u http://localhost:18081/sqlinject/mybatisright --data name=test --current-db --flush-sessionreturn userDataMapper.findByNameRight(name);
}//正确示范,采用#{} 占位符的方式
@Select("SELECT id,name FROM `userdata` WHERE name LIKE CONCAT('%',#{name},'%')")
List<UserData> findByNameRight(@Param("name") String name);
- 使用sqlmap测试是否注入
C:\Python310\sqlmap>python sqlmap.py -u http://localhost:18081/sqlinject/mybatisright --data name=test --current-db --flush-session
................................[13:37:28] [WARNING] POST parameter 'name' does not seem to be injectable
[13:37:28] [CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '--level'/'--risk' options if you wish to perform more tests. If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could try to use option '--tamper' (e.g. '--tamper=space2comment') and/or switch '--random-agent'C:\Python310\sqlmap>
从结果可以看出来注入失败,Mybatis将#{}
参数值视为一个 JDBC 参数,而不是 SQL 语句的一部分。MyBatis 会自动将参数值进行转义,确保其安全性。
${}
VS#{}
对比
这两位同志有各自的用处,
${} | #{} |
---|---|
拼接符 | 占位符 |
字符串替换,SQL拼接 | SQL预编译 |
执行前:select * from t_user where uid= ‘${uid}’ 执行后:select * from t_user where uid= ‘1’ | 执行前: select * from t_user where uid=#{uid} 执行后:select * from t_user where uid= ? |
不能防止sql 注入 | 能防止sql 注入 |
使用动态SQL
MyBatis提供了动态SQL的功能,可以根据条件动态拼接SQL语句。在使用动态SQL时,MyBatis会自动对参数值进行转义,从而防止注入攻击。
<select id="findByNameDynamic" resultType="icu.kevin.dataandcode.sqlinject.UserData">SELECT id, nameFROM `userdata`<where><if test="name != null">AND name LIKE CONCAT('%',#{name},'%')</if></where></select>
- 使用sqlmap探测,发现确实达到了防止注入的能力。
配置 SQL 注入过滤器
// 配置 SQL 注入过滤器
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.addInterceptor(new SqlExplainInterceptor());