tkMybatis中常见的注入场景

  • tkMybatis中常见的注入场景已关闭评论
  • 10 views
  • A+

一、关于tkMybatis

  Tkmybatis 是基于 Mybatis 框架开发的一个工具,通过调用它提供的方法实现对单表的数据操作,不需要写任何 sql 语句,极大地提高了项目开发效率。

1.1 相关依赖

xml
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>版本号</version>
</dependency>

1.2 使用方法

  引入tk.mybatis的依赖后。定义数据库表映射类:

java
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}

  mapepr继承其提供的接口,常见的有:

  • BaseMapper
  • MySqlMapper
  • IdsMapper
  • ConditionMapper
  • ExampleMapper

  这里以BaseMapper为例:

```Java
import tk.mybatis.mapper.common.BaseMapper;

public interface UserMapper extends BaseMapper{

}
```

  然后就可以调用接口中封装好的方法进行SQL操作了,例如通过selectAll查询所有的user信息:

```Java
@Resource
private UserMapper userMapper;

@RequestMapping(value = "/getUserInfo")
public List<User> getUserInfo(){
        List<User> result =userMapper.selectAll();
        return result;
}

```

wKg0C2D6MfmAJNSAABxJgfqD74254.png

  对应的方法实现原理也很简单,主要是通过mybatis的Provider注解来实现 的:

```java
public interface SelectAllMapper {

/*
* 查询全部结果
*
* @return
/
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
List selectAll();
}
```

```java
/*
* 查询全部结果
*
* @param ms
* @return
/
public String selectAll(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));

// 逻辑删除的未删除查询条件
sql.append("<where>");
sql.append(SqlHelper.whereLogicDelete(entityClass, false));
sql.append("</where>");

sql.append(SqlHelper.orderByDefault(entityClass));
return sql.toString();

}
```

  因为Provider其实只需要返回一个 SQL 字符串 ,所以tkMybatis在防护 SQL 注入时,也是对对应的内容,通过#{}的格式来进行预编译的 ,例如其中的一个拼接method:

java
/**
* 返回格式如:#{entityName.age+suffix,jdbcType=NUMERIC,typeHandler=MyTypeHandler}+separator
*
* @param entityName
* @param suffix
* @param separator
* @return
*/
public String getColumnHolder(String entityName, String suffix, String separator) {
StringBuffer sb = new StringBuffer("#{");
if (StringUtil.isNotEmpty(entityName)) {
sb.append(entityName);
sb.append(".");
}
sb.append(this.property);
if (StringUtil.isNotEmpty(suffix)) {
sb.append(suffix);
}
//如果 null 被当作值来传递,对于所有可能为空的列,JDBC Type 是需要的
if (this.jdbcType != null) {
sb.append(", jdbcType=");
sb.append(this.jdbcType.toString());
}
//为了以后定制类型处理方式,你也可以指定一个特殊的类型处理器类,例如枚举
if (this.typeHandler != null) {
sb.append(", typeHandler=");
sb.append(this.typeHandler.getCanonicalName());
}
//3.4.0 以前的 mybatis 无法获取父类中泛型的 javaType,所以如果使用低版本,就需要设置 useJavaType = true
//useJavaType 默认 false,没有 javaType 限制时,对 ByPrimaryKey 方法的参数校验就放宽了,会自动转型
if (useJavaType && !this.javaType.isArray()) {//当类型为数组时,不设置javaType#103
sb.append(", javaType=");
sb.append(javaType.getCanonicalName());
}
sb.append("}");
if (StringUtil.isNotEmpty(separator)) {
sb.append(separator);
}
return sb.toString();
}

  同时也支持Myabtis的xml和注解的配置方式。此外还提供了扩展机制,通过 Example 和 Criteria 类进行动态SQL 的拼装。

二、常见SQL注入场景

2.1 Example

  通过提供的Example可以添加查询条件,相当于是sql语句中的where后面的条件部分。例如如下的例子:

  mapper继承tk.mybatis.mapper.common.Mapper(包含了tk.mybatis.mapper.common.ExampleMapper):

```java
public interface UserMapper extends Mapper{

}
```

  在service层,通过Example生成对应的动态sql并封装在方法里:

```java
@Service
public class UserServiceImpl implements IUserService{
@Autowired
UserMapper userMapper;

    @Override
    public List<User> findByUserName(String username) {
            // TODO Auto-generated method stub
            Example example = new Example(User.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andLike("name", username);
            List<User> userList = userMapper.selectByExample(example);
            return userList;
    }

}
```

  Controller里调用service的方法:

```java
@Autowired
IUserService userService;

@RequestMapping(value = "/getUserInfoByUserName")
public List<User> getUserInfoByUserName(String username){
        List<User> result =userService.findByUserName(username);
        return result;
}

```

  直接访问接口即可进行模糊查询:

wKg0C2D6MnqAJYkwAABtYItLHCc564.png

  同时也进行了预编译处理:

wKg0C2D6MnAa1hWAAAzjYhUbBs472.png

  跟所有的SQL注入一样,动态表名&Order by查询没办法进行预编译处理,会存在SQL拼接。Example 和 Criteria也提供了相关的method。

2.1.1 order by排序

  可以通过example.setOrderByClause()方法来进行排序 。这里若前端可控且没有进行相关的安全检查会存在SQL注入风险:

java
@Override
public List<User> sortBy(String sort) {
// TODO Auto-generated method stub
Example example = new Example(User.class);
example.setOrderByClause(sort);
List<User> userList = userMapper.selectByExample(example);
return userList;
}

wKg0C2D6MsyAGcnMAACBbCj1B5M498.png

  尝试写入1/0,成功触发SQL error,存在注入风险:

wKg0C2D6MtKAOt3ZAAB2BgEdANM987.png

  对于这种情况,除了对输入参数进行安全检查,还可以通过Example.builder方式进行查询,其会将查询内容与entity进行绑定,在一定程度上规避了注入的风险:

java
Example example = Example.builder(User.class)
.orderByDesc(sort)
.build();

wKg0C2D6MxmARPX0AAB1jKfTCs4628.png

  或者使用example.orderBy()方法,同样将查询内容与entity进行绑定:

java
private String property(String property) {
if (StringUtil.isEmpty(property) || StringUtil.isEmpty(property.trim())) {
throw new MapperException("接收的property为空!");
}
property = property.trim();
if (!propertyMap.containsKey(property)) {
throw new MapperException("当前实体类不包含名为" + property + "的属性!");
}
return propertyMap.get(property).getColumn();
}

2.1.2 动态表名

  可以通过以下方式实现动态表名。

  首先在实体Dao添加非表字段参数,用于接受动态表名参数:

java
@Transient
private String tableName;

  然后实现接口IDynamicTableName,并实现getDynamicTableName()方法:

```java
@Data
public class User implements IDynamicTableName{

    @Transient
    private String tableName;

    public Long getId() {
            return id;
    }

    public String getDynamicTableName() {
    ......

}
```

  然后通过example.setTableName()即可设置查询的动态表名:

```java
@Autowired
UserMapper userMapper;

    @Override
    public List<User> DynamicTableName(String tableName) {
            // TODO Auto-generated method stub
            Example example = new Example(User.class);
            example.setTableName(tableName);
            example.setOrderByClause("id desc");
            List<User> userList = userMapper.selectByExample(example);
            return userList;
    }

```

  例如查询user表的内容:

wKg0C2D6M22AbsEtAACHVAQ4Xxw756.png

  因为这里是动态拼接的,实际上存在SQL注入风险:

wKg0C2D6M3OAFxP0AACFPx3PkOo483.png

  可以看到user表后的内容where 1=1/0成功拼接到SQL中并执行:

wKg0C2D6M3iAeA94AABLrGhU0Ns762.png

2.2 Condition

  Condition条件查询,其继承了Example:

```java
public class Condition extends Example {
public Condition(Class<?> entityClass) {
super(entityClass);
}

public Condition(Class<?> entityClass, boolean exists) {
super(entityClass, exists);
}

public Condition(Class<?> entityClass, boolean exists, boolean notNull) {
super(entityClass, exists, notNull);
}
}
```

  也就说说动态表名跟order by排序的场景也是存在的:

java
condition.setOrderByClause();
condition.setTableName();

2.3 criteria动态SQL拼接

  Criteria是Example中的一个内部类,在最终sql构建时以括号呈现,Criteria里带了较多构建查询条件的方法,方便生成动态SQL。

java
Example example = new Example(CcompareccicModel.class);
Criteria criteria = example.createCriteria();

  criteria的如下方法也是直接进行SQL拼接的:

java
criteria.andCondition(String condition)
criteria.andCondition(String condition,Object value)
criteria.orCondition(String condition)
criteria.orCondition(String condition,Object value)

  例如andCondition方法实际上是增加了动态查询的条件:

```java
/*
* 手写条件
*
* @param condition 例如 "length(countryname)<5"
* @return
/
public Criteria andCondition(String condition) {
addCriterion(condition);
return (Criteria) this;
}

/**
 * 手写左边条件,右边用value值
 *
 * @param condition 例如 "length(countryname)="
 * @param value   例如 5
 * @return
 */
public Criteria andCondition(String condition, Object value) {
  criteria.add(new Criterion(condition, value));
  return (Criteria) this;
}

```

2.4 注解&xml配置

  因为tkmybatis同时也支持Myabtis的xml和注解的配置方式,所以跟mybatis一样,关注${}格式的动态SQL即可。

三、参考资料

https://github.com/abel533/Mapper

相关推荐: Code Security Guide -Thinkphp3.2开发

一、Thinkphp安装基础 1.1安装ThinkPHP Thinkphp ->MVC结构 ->model(数据库) ->view(显示) ->comtrol(逻辑结构控制) Thinkphp 版本:3.2.3,解开压缩包之后,准备好我…