1.mybatis源码(1)-一条sql语句中占位符既有#{}也有${} 会不会存在注入的问题

在这里插入图片描述

本文测试的表数据(这只是该表部分数据)

image-20201122112035950
在这里插入图片描述

StudentMapper.java

@Mapper
public interface StudentMapper {

    List<Student> getByName(@Param("stu") Student student);
}

先分析一条sql语句的占位符全是#{}的情况

mapper.xml查询语句

<select id="getByName" resultType="com.kkb.pojo.Student" parameterType="com.kkb.pojo.Student">
    SELECT
        id as id,
        `name` as name,
        age as age,
        sex as sex
    FROM
        student
    WHERE
    `name` = #{stu.name}
</select>

单元测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
    @Test
    public void getByNameTest() {
        Student student = new Student();
        student.setName("'' or 1=1 or name=''");
        List<Student> byName = studentService.getByName(student);
        System.out.println(JSON.toJSONString(byName));
    }
}

源码执行的过程:

org.apache.ibatis.executor.SimpleExecutor#doQuery

​ —>org.apache.ibatis.executor.SimpleExecutor#prepareStatement

​ —>org.apache.ibatis.executor.statement.StatementHandler#prepare

​ —>org.apache.ibatis.executor.statement.BaseStatementHandler#prepare

​ —>org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement

在这里插入图片描述

观察执行的sql

如图:最终是在PreparedStatementHandler#instantiateStatement方法中通过connection.prepareStatement(sql)来执行boundSql.getSql()得到的sql语句:

在这里插入图片描述

SELECT
    id as id,
    `name` as name,
    age as age,
    sex as sex
FROM
	student
WHERE
	`name` = ?

最终执行的sql:

在这里插入图片描述

SELECT id as id, `name` as name, age as age, sex as sex
 FROM student
 WHERE `name` = '' or 1=1 or name='';

执行的结果:

在这里插入图片描述

  • 按道理,sql的条件里面有or 1=1 会导致将上面的表里面的所以数据查询出来,但是这里没有,这里查询的结果是没有一条符合这个sql查询条件的记录,这是为什么呢?

  • 个人的理解是因为入参的占位符用的是#{},导致前面的代码org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement通过boundSql.getSql();拿到的sql是将#{}占位符替换为问号的"?",然后通过prepareStatement对象对这个sql进行预编译(预编译代表这个sql的结构已经编译固定了,后面再填充进来的sql关键字不会当做sql的关键字处理,而是普通的字符串值),最终参数填充只是作为值来替换掉问号"?". 所以,这里的name条件设置的是name='' or 1=1 or name='', 但其中的or 1=1不会当做被当or条件处理,它只是不同的字符串值。在这个表中,因为没有name='' or 1=1 or name=''的记录,所以查询的结果没有数据。

  • 可以看出通过使用#{}作为参数的占位符时,会先将sql#{}的占位符替换为问好"?",然后通过prepareStatement对象对sql进行预编译,最后用其他过程来填充入参。因此可以知道使用#{}作为占位符的参数不会出现sql注入的问题。

看一下这里的connection和Statement对象

  • 可以看出connection是一个代理对象,而它的h属性是ConnectionLogger,并且h属性中的connection属性的对象是DruidPooledConnection,这是因为我这个项目里面用的数据库连接池是阿里的druid,所以这里的connection最终是DruidPooledConnection这个连接池里面的connection

在这里插入图片描述

扯一点题外话,通常以$ProxyXxx开头的类基本上就知道这个类是通过JDK动态代理运行时动态编译出来的代理类的对象,而这个对象中的h属性肯定是InvocationHandler接口的实现类,而这里的ConnectionLogger里面的connection属性我认为就是$Proxy100@7151代理对象代理的目标对像。

在这里插入图片描述
在这里插入图片描述

分析一条sql语句的占位符全是${}的情况

mapper.xml查询语句

  • 占位符改为${}
<select id="getByName" resultType="com.kkb.pojo.Student" parameterType="com.kkb.pojo.Student">
    SELECT
        id as id,
        `name` as name,
        age as age,
        sex as sex
    FROM
        student
    WHERE
    `name` = ${stu.name}
</select>

单元测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
    @Test
    public void getByNameTest() {
        Student student = new Student();
        student.setName("'' or 1=1 or name=''");
        List<Student> byName = studentService.getByName(student);
        System.out.println(JSON.toJSONString(byName));
    }
}

源码执行的过程(和上述一样):

org.apache.ibatis.executor.SimpleExecutor#doQuery

​ —>org.apache.ibatis.executor.SimpleExecutor#prepareStatement

​ —>org.apache.ibatis.executor.statement.StatementHandler#prepare

​ —>org.apache.ibatis.executor.statement.BaseStatementHandler#prepare

​ —>org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement

在这里插入图片描述

观察执行的sql

在这里插入图片描述

  • 从这里就可以看出占位符使用${}#{}的区别:用#{}的话,就是先将占位符#{}替换为问号"?",然后使用prepareStatement对带有问号的sql进行预编译,最后参数填充执行最终的sql。而用${}的话,则会直接将参数替换${}占位符,然后直接通过prepareStatement执行参数填充后的sql

  • #{}:会先通过prepareStatement预编译sql,再进行参数填充,执行最终的sql

  • ${}:会直接用参数替换${}占位符,然后用prepareStatement执行参数填充后的sql,该过程没有sql语句的预编译。所以这里使用${}占位符,会存在sql注入的问题,因为name值里面注入了or 1=1,所以会导致所以的数据被查询出来。

最终执行的sql

在这里插入图片描述

SELECT id as id, `name` as name, age as age, sex as sex
 FROM student
 WHERE `name` = ''or 1=1 or name='';

执行结果

  • 从图中可以看到查询的结果是1000573行记录。证实了${}会导致sql注入的问题。
    在这里插入图片描述
    在这里插入图片描述

看一下这里的connection和Statement对象

  • 这里和上面#{}占位符的情况一样

在这里插入图片描述

分析一条sql语句的占位符同时包含${}和#{}的情况

mapper.xml查询语句

<select id="getByName" resultType="com.kkb.pojo.Student" parameterType="com.kkb.pojo.Student">
    SELECT
        id as id,
        `name` as name,
        age as age,
        sex as sex
    FROM
    	student
    WHERE
    	`name` = ${stu.name}
    	and age = #{stu.age}
 </select>

单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
    @Test
    public void getByNameTest() {
        Student student = new Student();
        student.setName("'' or 1=1 or name=''");
        student.setAge(1);
        List<Student> byName = studentService.getByName(student);
        System.out.println(JSON.toJSONString(byName));
    }
}

源码执行过程

org.apache.ibatis.executor.SimpleExecutor#doQuery

​ —>org.apache.ibatis.executor.SimpleExecutor#prepareStatement

​ —>org.apache.ibatis.executor.statement.StatementHandler#prepare

​ —>org.apache.ibatis.executor.statement.BaseStatementHandler#prepare

​ —>org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement

在这里插入图片描述

观察执行的sql

  • 可以看出当一条sql里面的占位符#{}${}都存在时,#{}占位符依然会被问号"?“替换,${}占位符则直接被入参替换,然后通过prepareStatement对象对sql预编译,最后用对应的参数替换问号”?",因此得出的结论是:[当一条sql里面的占位符#{}${}都存在时,#{}作为占位符的入参不会导致sql注入的问题,而${}作为占位符的入参依然会导致sql注入的问题]
    在这里插入图片描述
SELECT
    id as id,
    `name` as name,
    age as age,
    sex as sex
FROM
	student
WHERE
	`name` = ''or 1=1 or name=''
	and age = ?

最终执行的sql

在这里插入图片描述

SELECT id as id, `name` as name, age as age, sex as sex
 FROM student
 WHERE `name` = ''or 1=1 or name='' and age = 1;

执行的结果

在这里插入图片描述

看一下这里的connection和Statement对象

  • 这里和上面#{}占位符的情况一样

在这里插入图片描述

解惑

通过这次的研究也纠正了我之前的错误理解。

之前我的理解是:

  • 通过${}作为占位符的话,sql就是通过Statement对象来处理sql
  • 通过#{}作为占位符的话,sql就是通过PrePareStatement对象来处理sql

现在知道这样理解是错误的,其实它都是通过PrePareStatement来处理sql的,只是如果参数的占位符是#{}的话,则这个参数在预编译的时候是用问号"?"替换了#{}占位符;如果参数的占位符是${}的话,则这个参数在预编译的时候是用参数的值替换了${}占位符,然后最终预编译之后,再用原来#{}占位符上的参数值来填充对应的参数。所以#{}不会有sql注入的问题,而${}会有sql注入的问题。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值