MybatisPuls分页插件中的SQL注入

  • MybatisPuls分页插件中的SQL注入已关闭评论
  • 10 views
  • A+

  实际业务场景中经常需要执行limit分页语句,返回部分数据。物理分页只返回部分数据占用内存小,能够获取数据库最新的状态,实时性比较强,一般适用于数据量比较大,数据更新比较频繁的场景。

  MybatisPlus插件提供了相应的支持。使用PaginationInnerInterceptor插件可以十分方便的完成相应的功能

简介| MyBatis-Plus

一、关于PaginationInnerInterceptor

  PaginationInnerInterceptor作为plus的分页插件,提供了通用的参数进行统一配置。可以很方便的完成分页的业务逻辑。

1.1 相关依赖

xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.4.3</version>
</dependency>

1.2 Springboot集成

  主要是通过拦截器的方式配置分页插件:

```java
@Configuration
public class MyBatisPlusConfig {

@Bean
public PaginationInterceptor paginationInterceptor() {
    return new PaginationInterceptor();
}

}
```

  新版本改用了MybatisPlusInterceptor,配置如下:

java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

  定义好mapper后就可以直接调用对应的方法进行操作了。

  最常用的是直接继承BaseMapper接口,无需编写mapper.xml文件,即可获得CRUD功能。其中mybatis-plus提供的分页方法是selectPageselectMapsPage ,其中一个入参为com.baomidou.mybatisplus.extension.plugins.pagination.page,以下是具体的function定义:

```Java
/*
* 根据 entity 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
/
> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper queryWrapper);

/*
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
/
>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper queryWrapper);
```

  可以看到,直接调用对应的function即可完成相关的业务需求:

```java
@GetMapping("/userList")
public String userList(@RequestParam(value = "pn", defaultValue = "1")Integer pn, Model model) {

    // 分页查询数据
    Page<User> userPage = new Page<>(pn, 2); //pn:第几页 size:每页记录条数
    Page<User> page = userService.page(userPage, null);

    model.addAttribute("users", page);

    long current = page.getCurrent(); // 当前页码
    long pages = page.getPages();     // 总页数
    long total = page.getTotal();     // 总记录数

    ......
}

```

  另外,对于使用xml和注解自定义的mapper方法,传递参数 Page 即自动分页 ,且参数必须放在第一位。

二、分页参数page

  具体的分页是通过配置Page对象相关的参数实现的。同时,不同阶段的版本com.baomidou.mybatisplus.extension.plugins.pagination.page实现是不一样的。主要区别在于Orderby排序字段上。

2.1 ascs/descs

  • mybatis-plus-extension-3.1.1及以下版本

  通过控制ascs或者descs来进行分页排序(3.1.1版本):

```Java
public class Page implements IPage {
private static final long serialVersionUID = 8545996863226528798L;
private List records;
private long total;
private long size;
private long current;
private String[] ascs;
private String[] descs;
private boolean optimizeCountSql;
private boolean isSearchCount;

......
}

```

2.2 List<OrderItem> orders

  • mybatis-plus-extension-3.1.2及以上版本

  通过List集合orders来进行分页排序(3.4.3版本),也可以通过setAsc、setDescs方法来配置,但是相关方法已弃用:

```java
public class Page implements IPage {
private static final long serialVersionUID = 8545996863226528798L;

protected List records = Collections.emptyList();

protected long total = 0L;

protected long size = 10L;

protected long current = 1L;

protected List orders = new ArrayList<>();

protected boolean optimizeCountSql = true;

protected boolean searchCount = true;

protected String countId;

protected Long maxLimit;

public void setOrders(List orders) {
this.orders = orders;
}
......
}
```

  orders集合封装了OrderItem,在OrderItem中包含了排序分页所需要的字段内容,默认为asc正序排序:

```Java
public class OrderItem implements Serializable {
private static final long serialVersionUID = 1L;

/
* 需要进行排序的字段
*/
private String column;
/

* 是否正序排列,默认 true
*/
private boolean asc = true;

public static OrderItem asc(String column) {
return build(column, true);
}

public static OrderItem desc(String column) {
return build(column, false);
}
......
}
```

三、PaginationInnerInterceptor使用不当导致的SQL注入

  跟所有的框架插件一样,只要涉及到数据库交互,使用不当就会导致SQL注入的安全风险。

3.1 Orderby场景下的SQL注入

  前面提到了分页中会存在Orderby的使用,因为Orderby动态查询没办法进行预编译,所以不经过安全检查的话会存在注入风险。PaginationInnerInterceptor主要是通过设置com.baomidou.mybatisplus.extension.plugins.pagination.page对象里的属性来实现orderby的,主要是以下函数的调用,因为直接使用sql拼接,所以需要对进行排序的列名进行安全检查:

java
page.setAsc();
page.setDesc();
page.setAscs();
page.setDescs();
page.setOrders();
page.addOrder();

3.2 Spring自动绑定导致的潜在SQL注入风险

3.2.1 漏洞原理

  在 Spring框架中,提交请求的数据是通过方法形参来接收的。从客户端请求的 key/value 数据,经过参数绑定,将 key/value 数据绑定到 Controller 的形参上,然后在 Controller 就可以直接使用该形参。

在实际开发场景中,开发人员可能通过直接传递com.baomidou.mybatisplus.extension.plugins.pagination.page来实现业务,例如下面的例子:

Java
@RequestMapping(value = "/getPage")
public String getPage(Page page) {
Page<User> userList =userMapper.selectPage(page, new QueryWrapper<User>().lambda().eq(User::getId, 1));
......
}

  查看Page的属性,直接在request请求时候传入size和current两个参数就可以完成对应的分页需求了,前端的确也是这么设计的:

Java
public class Page<T> implements IPage<T> {
private static final long serialVersionUID = 8545996863226528798L;
protected List<T> records; //分页对象记录列表
protected long total; //总记录跳数
protected long size; //每页显示条数
protected long current; //当前页码
protected List<OrderItem> orders; //排序信息
protected boolean optimizeCountSql; //自动优化 COUNT SQL【 默认:true 】
protected boolean isSearchCount; //进行 count 查询 【 默认: true 】
protected boolean hitCount; //设置是否命中count缓存
protected String countId; //MappedStatement 的 id
protected Long maxLimit; //最大每页分页数限制,优先级高于分页插件内的 maxLimit
......
}

  根据spring自动绑定的特性,若此时加入orders参数的传递,同样的后端会进行对应的实体封装,最终带入到sql查询中,同时因为order by场景下MybatisPlus并没有相关的安全措施 ,会导致SQL注入风险。

3.2.2 利用方式

  以上面的代码为例,看看具体的利用方式。同时前面也提到了不同版本com.baomidou.mybatisplus.extension.plugins.pagination.page对象里排序相关的属性会不一样,这里结合常见的reqeust提交方式分情况讨论(主要是普通的post和json请求):

  根据自动绑定的特性,可以将page对象中相关的属性为参数(前面第二节提到的ascs、descs以及List<OrderItem> orders),追加在request中,尝试利用,这里以H2 database为例,通过注入恶意sql请求dnslog来进行验证:

3.2.2.1 application/x-www-form-urlencoded

  • ascs/descs

  以descs为例,其参数类型是String[],直接传递即可:

wKg0C2C68JqAMA9JAABW3CGcgrE629.png

  通过print相关的sql日志可以看到追加的内容id,1/0成功引入到sql查询中,同时返回除0错误,说明SQL注入利用成功:

wKg0C2C68NyAciKcAAB9S1qa0Qo702.png

  这里有个需要注意的点是,因为descs的数据类型是是String[],逗号会自动进行分隔,在实际利用的时候会比较麻烦

  • List<OrderItem> orders

  因为自动绑定的内容是List,在传参时可以通过entity[index].attribute 的方式进行传递:

wKg0C2C67WiAM5pmAABm5dmc2wA126.png

wKg0C2C67TmAUjFHAABN84MuIAg175.png

3.2.2.2 application/json

  除了上述的情况,经常会有通过@RequestBody注解来传递JSON内容的情况:

java
@RequestMapping(value = "/getPageByJson")
public String getPage1(@RequestBody Page page) {
Page<User> userList =userMapper.selectPage(page, new QueryWrapper<User>().lambda().eq(User::getId, 1));
......
}

  • ascs/descs

  以descs为例,其参数类型是String[],可以通过[content,content]的方式进行传递,跟application/x-www-form-urlencoded不一样,因为相关字段内容用“”圈定了,所以上下文的逗号不会相互影响:

wKg0C2C68jCAVOMoAABydyxlTk720.png

wKg0C2C68haAZGjPAABNpV9BFEw982.png

  • List<OrderItem> orders

  通过前面查看源码,可以知道orders参数的类型是List集合。

  @RequestBody可以通过如下方式来自动绑定list集合:

wKg0C2C680mARDQnAABtwBR1scg056.png

wKg0C2C68yqAMBa0AABcIvSfxrQ899.png

  同样的print相关日志,可以看到恶意的sql语句成功写入:

wKg0C2C69GqAQFC6AACBiMVbees267.png

3.2.3 其他

  有时候用api级别的方法不能满足分页的需求,比如多张表关联查询的时候,这个时候就需要自定义分页了。对于使用xml和注解自定义的mapper方法,传递参数 Page 即自动分页 (且参数必须放在第一位),同样存在类似的问题。例如下面的例子:

```java
public interface UserMapper extends BaseMapper {

 @Select({"< script>",
"SELECT id, name, age, email FROM user where id=#{id}</script>"})
 IPage<User> queryPageById(Page<?> page, @Param("id")long id);

}
```

  同理,在controller直接调用mapper对应的function即可:

@RequestMapping(value = "/getPage")
public String getPage(Page page,long id) {
IPage<User> userlist = userMapper.queryPageById(page,id);
....
}

  因为直接传递了Page实体,所以同样的可以直接在相关的参数写入恶意sql语句,通过自动绑定即可利用,数据库为h2 database,这里通过请求dnslog进行验证:

wKg0C2C67ZeAdbZfAABnCtRkKRY238.png

  可以看到dnslog成功记录到请求,sql注入利用成功:

wKg0C2C66qqAXxkOAABDcAm3o747.png

  综上所述,由于MybatisPlus分页插件的使用方法不当,导致了SQL注入风险。

四、其他

  对于上述情况,在实际开发时需要额外的注意。可以通过如下方法来规避自动绑定导致的SQL注入问题。

  可以修改Controller层,仅仅接受用户可以控制的参数,若需要使用orderby排序相关的参数,加入相关的安全检查即可:

```Java
@GetMapping("/userList")
public String userList(@RequestParam(value = "pn", defaultValue = "1")Integer pn, Model model) {

    // 分页查询数据
    Page<User> userPage = new Page<>(pn, 2); //pn:第几页 size:每页记录条数
    Page<User> page = userService.page(userPage, null);
    ......

}
```

  也可以通过设置@InitBinder可以绑定的白名单,但是例如List<OrderItem> orders不是简单的类型,需要定义相应的Editor进行处理。

相关推荐: Windows中CVE-2021-28316漏洞分析

译文声明 本文是翻译文章,文章原作者 Matthew Johnson. 原文地址:https://shenaniganslabs.io/2021/04/13/Airstrike.html 译文仅供参考,具体内容表达以及含义以原文为准 默认情况下,Windows…