其他
原创 | MyBatisPlus通用IService的注入场景
关于Mybatis plus通用IService
1.1 相关依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.4.3</version>
</dependency>
1.2 使用方法
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
public interface IUserService extends IService<User> {
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
@Autowired
IUserService userService;
@Test
void lambdaPageTest() {
LambdaQueryChainWrapper<User> wrapper2 = userService.lambdaQuery();
wrapper2.like(User::getName, "a");
userService.page(new Page<>(1, 10), wrapper2.getWrapper()).getRecords().forEach(System.out::print);
}
通用IService中的注入场景
同样的,通用IService中也会存在使用不当导致的SQL注入风险。下面列举常见的场景。
2.1 分页查询
IService 接口提供了 page/pageMaps 方法实现分页查询,同时会自动识别是何种数据库,然后自动拼接相应分页语句(若是 mysql 则自动通过 limit 分页,若是 oracle 则自动通过 rownum 进行分页)。
2.1.1 漏洞原理
查看其具体方法实现,实际上还是调用了BaseMapper里的分页方法 :
/**
* 无条件翻页查询
*
* @param page 翻页对象
* @see Wrappers#emptyWrapper()
*/
default <E extends IPage<T>> E page(E page) {
return page(page, Wrappers.emptyWrapper());
}
/**
* 翻页查询
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
return getBaseMapper().selectPage(page, queryWrapper);
}
/**
* 翻页查询
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default <E extends IPage<Map<String, Object>>> E pageMaps(E page, Wrapper<T> queryWrapper) {
return getBaseMapper().selectMapsPage(page, queryWrapper);
}
/**
* 无条件翻页查询
*
* @param page 翻页对象
* @see Wrappers#emptyWrapper()
*/
default <E extends IPage<Map<String, Object>>> E pageMaps(E page) {
return pageMaps(page, Wrappers.emptyWrapper());
}
@Autowired
IUserService userService;
@RequestMapping(value = "/getPageByJson")
public String getPage(@RequestBody Page page) {
IPage userList =userService.page(page, new QueryWrapper<User>().lambda().eq(User::getId, 1));
......
}
com.baomidou.mybatisplus.extension.plugins.pagination.Page
结合Spring自动绑定,会存在SQL注入风险,更详细的分析可以参考
https://sec-in.com/article/1088:
不同版本对应的风险参数:
ascs/descs List<OrderItem> orders
2.1.2 利用方式
利用方式也比较简单,根据不同mybatis-plus版本操作对应的参数进行注入即可:
打印日志成功注入并执行1/0,触发SQL Error:
高版本主要操作orders集合,同理:
打印日志成功注入并执行1/0,触发SQL Error:
2.2 链式调用(查询与更新)
IService 接口支持链式操作。例如提供了一个 query 方法,该方法返回 QueryChainWrapper 对象。我们可以使用该对象实现链式查询,避免每次都创建 QueryWrapper (条件构造器)对象。
/**
* 以下的方法使用介绍:
*
* 一. 名称介绍
* 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作
* 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的
*
* 二. 支持介绍
* 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作
* 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作
*
* 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推
* 1. 根据条件获取一条数据: `query().eq("column", value).one()`
* 2. 根据条件删除一条数据: `update().eq("column", value).remove()`
*
*/
/**
* 链式查询 普通
*
* @return QueryWrapper 的包装类
*/
default QueryChainWrapper<T> query() {
return new QueryChainWrapper<>(getBaseMapper());
}
/**
* 链式查询 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaQueryWrapper 的包装类
*/
default LambdaQueryChainWrapper<T> lambdaQuery() {
return new LambdaQueryChainWrapper<>(getBaseMapper());
}
/**
* 链式更改 普通
*
* @return UpdateWrapper 的包装类
*/
default UpdateChainWrapper<T> update() {
return new UpdateChainWrapper<>(getBaseMapper());
}
/**
* 链式更改 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaUpdateWrapper 的包装类
*/
default LambdaUpdateChainWrapper<T> lambdaUpdate() {
return new LambdaUpdateChainWrapper<>(getBaseMapper());
}
2.2.1 漏洞原理
那么也就是说条件构造器对应Method直接sql拼接的风险方法,链式查询对象也是存在的。
例如QueryWrapper的apply(),实际上是父类AbstractWrapper的方法:
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T>
implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> {
......
@Override
public Children apply(boolean condition, String applySql, Object... value) {
return doIt(condition, APPLY, () -> formatSql(applySql, value));
}
}
public abstract class AbstractChainWrapper<T, R, Children extends AbstractChainWrapper<T, R, Children, Param>, Param>
extends Wrapper<T> implements Compare<Children, R>, Func<Children, R>, Join<Children>, Nested<Param, Children> {
@Override
public Children apply(boolean condition, String applySql, Object... value) {
getWrapper().apply(condition, applySql, value);
return typedThis;
}
@SuppressWarnings("rawtypes")
public AbstractWrapper getWrapper() {
return (AbstractWrapper) wrapperChildren;
}
}
2.2.2 利用方式
查询query
例如如下case,通过query方法得到QueryChainWrapper,然后使用orderByAsc方法进行排序(该排序方法跟所有Orderby排序一样,都是动态拼接的):
@RequestMapping(value = "/getPage")
public String getPage(String sort,Integer UserId) {
List<User> userList = userService.query().eq("id", UserId).orderByAsc(sort).list();
......
}
打印日志成功注入并执行1/0,触发SQL Error:
更新update
通过UserId来更新username,通过调用setSql()方法来设置username(存在sql直接拼接的操作):
@RequestMapping(value = "/updateUserName")
public String getPage1(String username,Integer UserId) {
boolean isTrue = userService.update().eq("id", UserId).setSql("name='"+username+"'").update();
if(isTrue){
......
}
......
打印日志成功注入并执行1/0,触发SQL Error:
其他query和update的函数同理,只要对应的method存在sql拼接,即可利用。
2.2.3 相关函数
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
last(String lastSql)
last(boolean condition, String lastSql)
exists(String existsSql)
exists(boolean condition, String existsSql)
notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
orderBy(boolean condition, boolean isAsc, R... columns)
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
groupBy(R... columns)
groupBy(boolean condition, R... columns)
notInSql(boolean condition, R column, String inValue);
notInSql(R column, String inValue)
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
setSql(String sql)
setSql(boolean condition, String sql)
apply("role_id = {0}",2)
安全加固建议
参考资料
https://baomidou.com/guide/crud-interface.html#service-crud-%E6%8E%A5%E5%8F%A3
原创 | Digvuln Tricks之JS泄露全到后台越权
原创 |【胖哈勃的七月公开赛】NewSql
原创 | 记一次完成的钓鱼实战
原创 | AMSI 浅析及绕过