Mybatis

目录

简介

Mybatis是java里的数据库模块,用于对数据库进行操作,属于dao层。

原理

\$和#

Mybatis在定义sql语句时有两种方式,注释形式和xml形式,其中xml形式用的更多一点

xml形式:

注释形式,本质上和xml形式差不多只是写在了持久层接口的注释里,用的不多。

在mybatis里,sql语句与参数进行拼接时,有两种符号#和\$

SELECT * FROM NEWS WHERE ID = #{id}
SELECT * FROM NEWS WHERE ID = ${id}

他们的区别就是#是启用了预编译,而\$没有启用。

所以审mybatis时可以重点关注\$符的拼接。

同时还有下面的函数也值得关注

like

新手在like后面插入参数时往往会写错像这样

Select * from news where title like ‘%#{title}%’

但这样会报错,有可能新手程序员就直接把#改成\$了,从而造成注入

这样改就没事了,安全了

select * from news where tile like concat(‘%’,#{title}, ‘%’)

in

同理,in新手也可能写成这样从而报错,然后改成\$

Select * from news where id in (#{ids})

正确写法如下

id in
<foreach collection="ids" item="item" open="("separatosr="," close=")">
##{ids} 
</foreach>

order by

只能用\$,不能用#开启预编译

原理是order by后面的参数不能参数化,在参数被预编译参数化的时候会被加上引号,从而导致order by后面的值是一个字符串值,但是order by后面只能跟字段名而不能跟字符串值,否则会报错。

具体注入payload

报错注入
select * from ha order by updatexml(1,if(1=1,1,user()),1);#查询正常
select * from ha order by updatexml(1,if(1=2,1,user()),1);#查询报错
select * from ha order by extractvalue(1,if(1=1,1,user()));#查询正常
select * from ha order by extractvalue(1,if(1=2,1,user()));#查询报错

rand盲注 当rand里的值分别为false和true的时候,排列状况会不一样
rand(ascii(mid((select database()),1,1))>96)

时间盲注
order by if(1=1,1,sleep(1))

if盲注
order by if(表达式,1,(select id from information_schema.tables))
如果表达式为false时,sql语句会报ERROR 1242 (21000): Subquery returns more than 1 row的错误,导致查询内容为空,如果表达式为true是,则会返回正常的页面。

OGNL注入

if

众所周知Mybatis有很多通过XML来定义的SQL语句。

if是其中用的较多的一类

<select id="findActiveBlogWithTitleLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = 'ACTIVE'
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

其中if标签的test属性是可以传入OGNL表达式来造成OGNL注入的。 然后if标签的属性处一般都是写死的,不太可控,所以通过if标签来造成OGNL注入希望不大。

choose

Mybatis的一种类似switch case的标签

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = 'ACTIVE'
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

when的test属性可传入OGNL表达式造成OGNL注入,但如if标签一样一般写死,希望不大。

bind

bind标签用于初始化一个变量并用于上下文

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

bind标签的value属性存在着OGNL注入,且很有可能是可控的。 但是 由于value属性对OGNL表达式的解析顺序问题,导致bind标签实际上也无法造成OGNL注入,具体原理如下。

我们有如下Mybatis xml,可见name是一个可控的值,且存在于bind标签的value属性。

<if test="name != null and name !=''">
  <bind name="likename" value="name" />
       name like #{likename}
</if>

若我们传入name=**${@java.lang.Math@min(4,10)}**, 理想情况下value属性应该为4,但实际上为字符串值的${@java.lang.Math\@min(4,10)}

究其原因,就是OGNL在解析name这个变量时就已经触发了OGNL解析:把name解析为\${@java.lang.Math\@min(4,10)}。如果能再来一次OGNL解析就能把value整成4,可是OGNL只发生一次。

所以bind标签是基本上无法ognl注入的,除非程序员直接在value放一个OGNL表达式(但这样也貌似不叫OGNL注入了

<if test="name != null and name !=''">
  <bind name="likename" value="${@java.lang.Math@min(4,10)}" />
       name like #{likename}
</if>

\${param} 参数

<select id="findTeacherByName" resultMap="BaseResultMap" parameterType="com.example.mybatis.entity.Teacher">
select id,email from Teacher where name = ${name};
</select>

存在可控变量name,依旧无法注入,原理同bind的value属性,解析顺序的问题

spirngboot 注释(唯一的可能)

Mybatis 为springboot提供了一些用于简化SQL操作的注释,意在让开发人员不用写那么多繁琐的XML文件,以达到动态SQL的效果

@Insert
@Update
@Delete
@Select
@InsertProvider
@SelectProvider
@UpdateProvider
@DeleteProvider

可见注释分为了两类:带Provider的和不带Provider的

不带Provider的注释使用起来也很笨拙和鸡肋,因为也要写一次XML,只不过是写在注释里。由于本质还是XML,所以造成OGNL注入的风险也是很低很低的

@Update({"<script>",
      "update Author",
      "  <set>",
      "    <if test='username != null'>username=#{username},</if>",
      "    <if test='password != null'>password=#{password},</if>",
      "    <if test='email != null'>email=#{email},</if>",
      "    <if test='bio != null'>bio=#{bio}</if>",
      "  </set>",
      "where id=#{id}",
      "</script>"})
void updateAuthorValues(Author author);

带Provider的注释使用起来就高级些了,通过注释来指定生成SQL语句的方法

@SelectProvider(type = UserDaoProvider.class, method = "findTeacherByName")
Teacher findUserByName(Map<String, Object> map);

SelectProvide 调用的方法为 findTeacherByName,如下:

public String findTeacherByName(Map<String, Object> map) {
      String name = (String) map.get("name");
      String s = new SQL() {
          {
              SELECT("id,email");
              FROM("Teacher");
              if(map.get("id")!=null)            
              WHERE("name=#{name}");
          }
      }.toString();
      return s;
  }
}

这样的动态SQL,可以先进行一轮OGNL解析,再把解析结果生成为对应的XML文件的形态(实际上并未生成,只是一个比喻),最后XML文件被OGNL再次解析,从而导致OGNL注入。

如下情形都可使用,只要是拼接都可以打

WHERE("name=#{name}");
WHERE("name=" + name);
String sql = "select id,email from Teacher where name = " + name;

        String name = (String) map.get("name");
      String finalName = String.format(" name in (%s)", name);
      String sql = new SQL() {{
        SELECT("id,email");
        FROM("Teacher");
        WHERE(finalName);
        ORDER_BY("id desc");
      }}.toString();



可用范围

mybatis-spring-boot-starter >=2.0.1(mybatis-spring-boot-starter组件从2.0.1版本开始支持Provider动态SQL)
或者

Mybatis 全版本
或者

mybatis-plus-boot-starter >=3.1.1

关键字

直接全局搜索xml文件的 \$ 符即可

找到可疑的sql语句后就看xml标签里的id属性定位到调用类,如果能一步定位到controller最好,然后向上摸索。