1. 问题描述
SQL 注入是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息
SQL 注入问题只在 SQL 的预编译过程中才能起作用
2. 问题发生场景
在日常开发中sql操作是每个系统必备的, 不好的编码习惯会导致 SQL 注入的存在, 出现 SQL 注入的场景:
- 拼接 SQL 语句
- Mybatis 中 ${} 不正当使用
3. 拼接 SQL 语句
在编写 SQL 语句时,通过占位符的形式, 会将sql进行预编译后,这样传入的参数不会参与预编译过程, 也就不会发生 SQL 注入问题, 使用下面代码会存在 SQL 注入风险
String query = "SELECT * FROM t_user WHERE name = '" + userName + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
正确的编码方式如下
PreparedStatement statement = connection.prepareStatement("SELECT * FROM t_user WHERE name = ?");
statement.setString(1, userName);
ResultSet resultSet = statement.executeQuery();
4. Mybatis 中 ${} 不正当使用
在 Mybatis 中有两种情况经常使用 ${}, 分别是 like, order by
4.1 Like 场景
select * from t_user where name like '%${userName}%'
4.1.1 修复方案
- mysql
select * from t_user where name like concat('%', #{userName}, '%')
select * from t_user where name like '%'||#{userName}||'%'
- oracle
select * from t_user where name like '%'||#{userName}||'%'
- SQL Server
select * from t_user where name like '%' + #{userName} + '%'
4.2 Order By 场景
select * from t_user where name like '%${name}%'
4.2.1 修复方案
order by 场景是无法像 like 那样去调整写法规避 SQL 注入, 对于这种只能用 ${} 场景, 需要在代码里面做白名单验证处理
select * from t_user where name = #{name} order by ${orderByName}
- 内部转换
/**
* 修复 order by 场景 SQL Injection
*/private final static Map<Integer, String> ODER_NAME_MAP = new HashMap<>(3);
static {
ODER_NAME_MAP.put(1, "name");
ODER_NAME_MAP.put(2, "age");
ODER_NAME_MAP.put(3, "id");
}
@GetMapping(value = "orderBy")
public ResponseVO orderBy(@RequestParam("name") String name, @RequestParam("orderNameId") String orderNameId) {
// 通过内部转换形式, 从接口传入 1, 2, 3 在内部分别转换表的字段名 name, age, id String realOrderName = ODER_NAME_MAP.get(orderNameId);
// 参数转换失败, 入参非法
if (StringUtils.isBlank(realOrderName)) {
return ResponseVO.fail(ResponseCodeEnum.FAIL_JOB_CHECK_ERROR,null);
}
List<User> userList = userService.queryByOrderByName(realOrderName, name);
return ResponseVO.success(userList);
}
- 白名单校验
/**
* Order By 场景白名单检查
*/
private final static Set<String> ORDER_NAME_CHECK_SET = new HashSet<>(3);
static {
ORDER_NAME_CHECK_SET.add("name");
ORDER_NAME_CHECK_SET.add("age");
ORDER_NAME_CHECK_SET.add("id");
}
@GetMapping(value = "orderByWhiteLIst")
public ResponseVO orderByWhiteLIst(@RequestParam("name") String name, @RequestParam("orderName") String orderName) {
// 如果传入的表名不在白名单范围内,直接返回参数校验不通过
if (!ORDER_NAME_CHECK_SET.contains(orderName)) {
// 返回参数校验不通过
return ResponseVO.fail(ResponseCodeEnum.FAIL_JOB_CHECK_ERROR,null);
}
List<User> userList = userService.queryByOrderByName(orderName, name);
return ResponseVO.success(userList);
}
## 4.3 Table Name 场景
select * from ${tableName} where name = #{name}
4.3.1 修复方案
- 内部转换
/**
* 修复 table name 场景 SQL Injection
*/private final static Map<Integer, String> TABLE_NAME_MAP = new HashMap<>(1);
static {
TABLE_NAME_MAP.put(1, "t_user");
}
@GetMapping(value = "tableName")
public ResponseVO tableName(@RequestParam("tableNameId") String tableNameId, @RequestParam("name") String name) {
String realTableName = TABLE_NAME_MAP.get(tableNameId);
// 参数转换失败, 入参非法
if (StringUtils.isBlank(realTableName)) {
return ResponseVO.fail(ResponseCodeEnum.FAIL_JOB_CHECK_ERROR,null);
}
List<User> userList = userService.queryByTableName(realTableName, name);
return ResponseVO.success(userList);
}
- 白名单校验
/**
* Table Name 场景白名单检查
*/
private final static Set<String> TABLE_NAME_CHECK_SET = new HashSet<>(1);
static {
TABLE_NAME_CHECK_SET.add("t_user");
}
@GetMapping(value = "tableNameWhiteLIst")
public ResponseVO tableNameWhiteLIst(@RequestParam("tableName") String tableName, @RequestParam("name") String name) {
// 如果传入的表名不在白名单范围内,直接返回参数校验不通过
if (!TABLE_NAME_CHECK_SET.contains(tableName)) {
// 返回参数校验不通过
return ResponseVO.fail(ResponseCodeEnum.FAIL_JOB_CHECK_ERROR,null);
}
List<User> userList = userService.queryByTableName(tableName, name);
return ResponseVO.success(userList);
}
更多推荐
Fortify SQL Injection
发布评论