实际业务场景中经常需要执行limit分页语句,返回部分数据。物理分页只返回部分数据占用内存小,能够获取数据库最新的状态,实时性比较强,一般适用于数据量比较大,数据更新比较频繁的场景。
MybatisPlus插件提供了相应的支持。使用PaginationInnerInterceptor插件可以十分方便的完成相应的功能。
关于PaginationInnerInterceptor
1.1 相关依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.4.3</version>
</dependency>
1.2 Springboot集成
public class MyBatisPlusConfig {
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
MybatisPlusInterceptor
,配置如下:public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
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<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@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(); // 总记录数
......
}
分页参数page
com.baomidou.mybatisplus.extension.plugins.pagination.page
实现是不一样的。主要区别在于Orderby排序字段上。2.1 ascs/descs
-
mybatis-plus-extension-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及以上版本
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;
}
......
}
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 漏洞原理
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));
......
}
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
......
}
3.2.2 利用方式
com.baomidou.mybatisplus.extension.plugins.pagination.page
对象里排序相关的属性会不一样,这里结合常见的reqeust提交方式分情况讨论(主要是普通的post和json请求):3.2.2.1 application/x-www-form-urlencoded
-
ascs/descs
通过print相关的sql日志可以看到追加的内容id,1/0成功引入到sql查询中,同时返回除0错误,说明SQL注入利用成功:
-
List<OrderItem> orders
3.2.2.2 application/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
-
List<OrderItem> orders
@RequestBody可以通过如下方式来自动绑定list集合:
同样的print相关日志,可以看到恶意的sql语句成功写入:
3.2.3 其他
public interface UserMapper extends BaseMapper<User> {
"< script>", ({
"SELECT id, name, age, email FROM user where id=#{id}</script>"})
IPage<User> queryPageById(Page<?> page, @Param("id")long id);
}
@RequestMapping(value = "/getPage")
public String getPage(Page page,long id) {
IPage<User> userlist = userMapper.queryPageById(page,id);
....
}
对于上述情况,在实际开发时需要额外的注意。可以通过如下方法来规避自动绑定导致的SQL注入问题。
"/userList") (
public String userList( (value = "pn", defaultValue = "1")Integer pn, Model model) {
// 分页查询数据
Page<User> userPage = new Page<>(pn, 2); //pn:第几页 size:每页记录条数
Page<User> page = userService.page(userPage, null);
......
}
本文始发于微信公众号(SecIN技术平台):原创 | MybatisPuls分页插件中的SQL注入
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论