Mybatis-plus框架常见SQL注入场景

  • Mybatis-plus框架常见SQL注入场景已关闭评论
  • 21 views
  • A+

一、关于Mybatis-plus

  MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

二、常见SQL注入场景

  与SpringDataJpa类似,mybatis-plus提供了相关的funciton进行sql的操作,例如like("name","tks")——>name like '%tks%',同时也很贴心的考虑到了SQL注入问题,对绝大部分场景进行了预编译处理。但是类似动态表名、orderby这种需要拼接的场景在实际开发中还是需要额外的注意。

2.1 条件构造器Wrapper

  条件构造器Wrapper可以用于复杂的数据库操作:大于、小于、模糊查询等等。

wKg0C2CorXAU0RFAAClpz9bmug527.png

  比较常用的是QueryWrapper和UpdateWrapper:

  • Wrapper : 条件构造抽象类,最顶端父类,抽象类中提供4个方法
  • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
  • AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
  • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
  • LambdaUpdateWrapper : Lambda 更新封装Wrapper
  • QueryWrapper : Entity 对象封装操作类,不是用lambda语法
  • UpdateWrapper : Update 条件封装,用于Entity对象更新操作

2.1.1 配置方法

  首先配置mapper:

```java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.samples.wrapper.entity.User;

public interface UserMapper extends BaseMapper {
}

```

  然后直接调用相关的api进行操作即可,例如查询name为“admin”的用户:

java
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","admin")
userMapper.selectOne(wrapper); // 查询一个数据。查询多个使用List或者Map

  传统的mybaits框架对于in范围查询和like模糊查询需要做额外的处理:

  like模糊查询需要在mapperxml配置中用sql的内置函数进行拼接,拼接后再采用#预编译的方式进行查询:

xml hljs
<select id="searchUser" parameterType="String" resultType="com.codeaudit.sqlinject.mybatis.User">
select * from user where name like '%'||'#param#'||'%' #Oracle
select * from user where name like CONCAT('%',#param#,'%') #mysql
select * from user where name like '%'+#param#+'%' #mssql
</select>

  in范围查询的话需要在进行同条件多值查询的时候,可以使用MyBatis自带的循环指令foreach来解决SQL语句动态拼接的问题:

xml
<select id="searchUser" parameterType="String" resultType="com.tk.codeAudit.sqlinject.mybatis.User">
select * from user where id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>

  比较便利的是,mybatis-puls已经考虑到sql注入的影响,相关wrapper的function已进行了相关的预编译处理。例如mybatis常见的like和in注入场景,均进行了预编译处理,例如如下例子:

  • like模糊查询

java
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("id","name").like("name", "jack");
List<User> plainUsers = userMapper.selectList(qw);

  打印相关的查询log,可以看到相关查询已使用?进行预编译处理:

wKg0C2CorgOAaLB5AABb6qL0NL8299.png

  • in范围查询

java
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("id","name").in("age","10","30");
List<User> plainUsers = userMapper.selectList(qw);

  打印相关的查询log,可以看到相关查询已使用?进行预编译处理:

wKg0C2CorgAY6oWAABZYicvYXk732.png

2.1.2 常见注入场景

  以QueryWrapper为例,由于部分场景存在动态sql的场景,官方文档已进行风险提醒:

wKg0C2CorqOAGB1LAABmFz2KTeg098.png

  PS:boolean condition参数表示该条件是否加入最后生成的sql中

  如下是审计过程中发现的比较常见的存在注入风险的点,也是对官方文档的一个补充

apply

  该方法可用于数据库函数的调用:

java
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)

  例如:

java
apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")

  注意:动态入参的params对应前面applySql内部的{index}部分。这样可以进行预编译防止SQL注入问题。

  例如下面的例子,未通过动态入参params进行预编译处理:

java
List<User> plainUsers4 = userMapper.selectList(new QueryWrapper<User>()
.apply("role_id = 2"));
List<User> lambdaUsers4 = userMapper.selectList(new QueryWrapper<User>().lambda()
.apply("role_id = 2"));

  打印相关的查询log,可以看到相关的输入role_id直接拼接到sql语句中,并未做预编译处理:

wKg0C2Cor5eAOy6WAAB7EHgNX0300.png

  使用params进行预编译处理:

java
List<User> plainUsers5 = userMapper.selectList(new QueryWrapper<User>()
.apply("role_id = {0}",2));
List<User> lambdaUsers5 = userMapper.selectList(new QueryWrapper<User>().lambda()
.apply("role_id = {0}",2));

  打印相关的查询log。通过{index}标记的部分最终进行了预编译处理:

wKg0C2Cor6WATd8XAABfnNKEw3Q181.png

last

  该函数用于无视优化规则直接将内容拼接到 sql 的最后,一般在排序场景会用到:

java
last(String lastSql)
last(boolean condition, String lastSql)

  例如下面的例子,直接将order by排序相关语句拼接进行拼接,如果相关内容用户可控,那么会存在Sql注入风险:

java
.last("order by " + StringUtil.isEmpty(queryParam.getColumns()) + " " + queryParam.getSort()));

exists/notExists

  这两个场景同样的是进行sql的拼接,如果相关内容用户可控,那么会存在Sql注入风险:

  • 拼接 EXISTS ( sql语句 )

java
exists(String existsSql)
exists(boolean condition, String existsSql)

  • 拼接 NOT EXISTS ( sql语句 )

java
notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)

  例如如下例子,若exists中的内容用户可控,则会存在注入风险:

java
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("id","name").exists("select id from table where role_id = 2");

wKg0C2CosGeAQr6rAAAuAWehvrQ394.png

having

  用于Having查询,一般用配合groupby在对分组统计函数进行过滤的场景中:

java
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)

  跟apply一样,动态入参的params对应前面applySql内部的{index}部分。这样可以进行预编译防止SQL注入问题。例如如下的例子:

java
QueryWrapper<User>().select("id","name").groupBy("name").having(true, "role_id>{0}",2));

Order By

  Order by无疑是SQL注入的常客了。

  • 原始orderby

java
orderBy(boolean condition, boolean isAsc, R... columns)

  • ORDER BY 字段, ... DESC

java
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)

  • ORDER BY 字段, ... ASC

java
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)

  因为Order by排序时不能进行预编译处理,所以相关内容用户可控的话会存在sql注入风险。

  例如如下H2 database的例子:

java
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("id","name").orderByAsc(str);

  str用户可控,那么这里尝试带外请求dnslog,成功接收到带外请求:

wKg0C2CosNAKuqtAAA5sofauXE760.png

group By

  主要用于用于结合聚合函数,根据一个或多个列对结果集进行分组。

java
groupBy(R... columns)
groupBy(boolean condition, R... columns)

  例如如下H2 database的例子:

java
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("id","name").groupBy(str);

  str用户可控,那么这里尝试带外请求dnslog,成功接收到带外请求:

wKg0C2CosQSAaAjBAAAqOAT5G4736.png

insql/notinsql

  虽然mybatis-plus提供的in/not in方法进行处理,但是无法满足对子查询结果进行范围查询的场景。此时提供了如下方法:

  通过sql 注入方式调用in/not in 方法:

  • notInSql

java
notInSql(boolean condition, R column, String inValue);
notInSql(R column, String inValue)

  • inSql

java
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)

  例如如下H2 database的例子:

java
userMapper.selectList(new QueryWrapper<User>().inSql(str, "select id from role where id = 2"));

  str用户可控,那么这里尝试带外请求dnslog,成功接收到带外请求:

wKg0C2CosWKABGjqAAAmcTYPFU0861.png

  同理,除了column字段以外,inValue字段可控的情况下也会存在注入风险。

2.2 使用 Wrapper 自定义SQL(特殊的预编译场景)

  虽然mybatis-plus提供了很多函数使用,但是不一定能满足所有的业务需要,此时Wrapper提供了自定义SQL场景,虽然跟传统的mybatis一样使用$进行注解,但是实际上ew已经做了预编译处理。同样的也支持注解&xml配置。

  • 注解方式

java
@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);

  • xml方式

xml
<select id="getAll" resultType="MysqlData">
SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>

2.3 其他

  除此之外,官方文档中还推荐使用com.github.pagehelper进行分页处理:

x
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>

  因为Order by排序时不能进行预编译处理,所以在使用插件时需要额外注意如下function,同样会存在SQL注入风险:

  • com.github.pagehelper.Page

  • 主要是setOrderBy(java.lang.String)方法

  • com.github.pagehelper.page.PageMethod

  • 主要是startPage(int,int,java.lang.String)方法

  • com.github.pagehelper.PageHelper

  • 主要是startPage(int,int,java.lang.String)方法

三、安全加固建议

  上述场景中,除了apply、having是因为方法使用不当导致注入以外,其他场景更多的类似动态拼接,可以参考如下方法进行安全加固:

  1. 在代码层使用白名单验证方式,如设置表名白名单,如果输入不再白名单范围内则设置为一个默认值如user;
  2. 在代码层使用间接引用方式,如限制用户输入只能为数字1、2,当输入1时映射到user,为2时映射到product,其他情况均映射到一个默认值例如product;
  3. 使用sdk对用户输入进行安全检查。

四、参考资料

https://www.cnblogs.com/nongzihong/p/12661446.html

https://github.com/baomidou/mybatis-plus-samples

相关推荐: 堆的unlink利用

在刷Buuctf时看了一下关于pwn的堆部分题,发现一般题型都是有套路的,所以整理下堆题中常用的技术,本文介绍unlink的原理与利用。 unlink介绍 产生原因 我们知道link有链的意思,那unlink同理有解链的意思,unlink实际上就是在free堆…