原创 | MybatisPuls分页插件中的SQL注入

admin 2021年10月25日07:37:51评论110 views字数 6970阅读23分14秒阅读模式
原创 | MybatisPuls分页插件中的SQL注入
点击上方蓝字 关注我吧


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


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


原创 | MybatisPuls分页插件中的SQL注入


关于PaginationInnerInterceptor



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


1.1  相关依赖


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

1.2  Springboot集成


主要是通过拦截器的方式配置分页插件:
@Configurationpublic class MyBatisPlusConfig {
@Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }}


新版本改用了MybatisPlusInterceptor,配置如下:
@Beanpublic 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定义:
 /**   * 根据 entity 条件,查询全部记录(并翻页)   *   * @param page     分页查询条件(可以为 RowBounds.DEFAULT)   * @param queryWrapper 实体对象封装操作类(可以为 null)   */  <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/** * 根据 Wrapper 条件,查询全部记录(并翻页) * * @param page 分页查询条件 * @param queryWrapper 实体对象封装操作类 */  <E extends IPage<Map<StringObject>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);


可以看到,直接调用对应的function即可完成相关的业务需求:
@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版本):
public class Page<T> implements IPage<T> {    private static final long serialVersionUID = 8545996863226528798L;    private List<T> 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方法来配置,但是相关方法已弃用:
public class Page<T> implements IPage<T> {  private static final long serialVersionUID = 8545996863226528798L;
protected List<T> records = Collections.emptyList();
protected long total = 0L;
protected long size = 10L;
protected long current = 1L;
protected List<OrderItem> orders = new ArrayList<>();
protected boolean optimizeCountSql = true;
protected boolean searchCount = true;
protected String countId;
protected Long maxLimit;
public void setOrders(List<OrderItem> orders) { this.orders = orders; } ......}

orders集合封装了OrderItem,在OrderItem中包含了排序分页所需要的字段内容,默认为asc正序排序:
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); } ......}

使用不当导致的SQL注入



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


3.1  Orderby场景下的SQL注入


前面提到了分页中会存在Orderby的使用,因为Orderby动态查询没办法进行预编译,所以不经过安全检查的话会存在注入风险。


PaginationInnerInterceptor主要是通过设置com.baomidou.mybatisplus.extension.plugins.pagination.page对象里的属性来实现orderby的,主要是以下函数的调用,因为直接使用sql拼接,所以需要对进行排序的列名进行安全检查:

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来实现业务,例如下面的例子:
@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两个参数就可以完成对应的分页需求了,前端的确也是这么设计的:
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[],直接传递即可:


原创 | MybatisPuls分页插件中的SQL注入


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


原创 | MybatisPuls分页插件中的SQL注入


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

  • List<OrderItem> orders


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


原创 | MybatisPuls分页插件中的SQL注入

原创 | MybatisPuls分页插件中的SQL注入


3.2.2.2   application/json


除了上述的情况,经常会有通过@RequestBody注解来传递JSON内容的情况:
@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不一样,因为相关字段内容用“”圈定了,所以上下文的逗号不会相互影响:


原创 | MybatisPuls分页插件中的SQL注入

原创 | MybatisPuls分页插件中的SQL注入


  • List<OrderItem> orders

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

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


原创 | MybatisPuls分页插件中的SQL注入

原创 | MybatisPuls分页插件中的SQL注入


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


原创 | MybatisPuls分页插件中的SQL注入


3.2.3  其他


有时候用api级别的方法不能满足分页的需求,比如多张表关联查询的时候,这个时候就需要自定义分页了。对于使用xml和注解自定义的mapper方法,传递参数 Page 即自动分页 (且参数必须放在第一位),同样存在类似的问题。例如下面的例子:
public interface UserMapper extends BaseMapper<User> {
@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进行验证:


原创 | MybatisPuls分页插件中的SQL注入


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


原创 | MybatisPuls分页插件中的SQL注入


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


其他


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

可以修改Controller层,仅仅接受用户可以控制的参数,若需要使用orderby排序相关的参数,加入相关的安全检查即可:
@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进行处理。


相关推荐




原创 | JavaWeb网页截图中的ssrf
原创 | Mybatis-plus框架常见SQL注入场景
原创 | 沙盒逃逸之seccomp学习
原创 | 堆的tcache利用

原创 | MybatisPuls分页插件中的SQL注入
你要的分享、在看与点赞都在这儿~

本文始发于微信公众号(SecIN技术平台):原创 | MybatisPuls分页插件中的SQL注入

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年10月25日07:37:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   原创 | MybatisPuls分页插件中的SQL注入http://cn-sec.com/archives/408939.html

发表评论

匿名网友 填写信息