mybatis 防止sql注入原理

mybatis 防止sql注入原理

一、SQL注入

SQL注入是在Web页面的查询入口传入SQL非法参数,在事先定义好的查询语句的结尾上添加额外的SQL语句,修改拼接成SQL语句,传递给Web服务器,进而传给数据库服务器执行,威胁数据库数据信息安全。

二、SQL注入方法

由于编写程序时未对用户输入数据的合理性进行判断,导致攻击者能在SQL的注入点中夹杂代码进行执行,并通过页面返回的提示,获取进行下一步攻击所需的信息。根据输入的参数,可将SQL注入方式大致分为两类:数字型注入、字符型注入。

  • 数字型注入
    当输入的参数为整型时,如ID、年龄、页码等,如果存在注入漏洞,则可以认为是数字型注入。这种数字型注入最多出现在ASP、PHP等弱类型语言中,弱类型语言会自动推导变量类型,例如,参数id=8,PHP会自动推导变量id的数据类型为int类型,那么id=8 and 1=1,则会推导为string类型,这是弱类型语言的特性。而对于Java、C#这类强类型语言,如果试图把一个字符串转换为int类型,则会抛出异常,无法继续执行。所以,强类型的语言很少存在数字型注入漏洞。
  • 字符型注入
    当输入参数为字符串时,称为字符型。数字型与字符型注入最大的区别在于:数字型不需要单引号闭合,而字符串类型一般要使用单引号来闭合。

三、mybatis中的#和$的区别

让我们看两个例子:

<select id="selectUser" parameterType="java.util.Map" resultMap="BaseResultMap">
    select id, username, password from user
    where username = #{username,jdbcType=VARCHAR}
</select>
<select id="selectUser" parameterType="java.util.Map" resultMap="BaseResultMap">
    select id, username, password from user
    where username = ${username,jdbcType=VARCHAR}
</select>
  • #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
    如:where username=#{username},如果传入的值是111,那么解析成sql时的值为where username=“111”, 如果传入的值是id,则解析成的sql为where username=“id”。
  • $将传入的数据直接显示生成在sql中。
    如:where username=${username},如果传入的值是111,那么解析成sql时的值为where username=111;
    如果传入的值是;drop table user;,则解析成的sql为:select id, username, password, role from user where username=;drop table user;
  • #方式能够很大程度防止sql注入,$方式无法防止Sql注入。
  • $方式一般用于传入数据库对象,例如传入表名。
  • 一般能用#的就别用$,若不得不使用${xxx}这样的参数,要手工地做好过滤工作,来防止sql注入攻击。
  • 在MyBatis中,${xxx}这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用${xxx}这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。

结论

在编写MyBatis的映射语句时,尽量采用#{xxx}这样的格式。若不得不使用${xxx}这样的参数,要手工地做好过滤工作,来防止SQL注入攻击。

四、MyBatis如何防止SQL注入

MyBatis框架作为一款半自动化的持久层框架,其SQL语句都要我们自己手动编写,这个时候当然需要防止SQL注入。其实,MyBatis的SQL是一个具有"输入+输出"的功能,类似于函数的结构,参考上面的两个例子。其中,parameterType表示了输入的参数类型,resultType表示了输出的参数类型。回应上文,如果我们想防止SQL注入,理所当然地要在输入参数上下功夫。

上面代码中使用"#{}"的即输入参数在SQL中拼接的部分,传入参数后,打印出执行的SQL语句,会看到SQL是这样的:

select id, username, password from user where username=?

不管输入什么参数,打印出的SQL都是这样的。这是因为MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。

五、mybatis底层实现原理

MyBatis是如何做到SQL预编译的呢?其实在框架底层,是JDBC中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的SQL语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。

  • ${}:表示拼接sql串,将接收到的参数的内容不加修饰拼接在sql中,可能引发sql注入。
  • #{}:是预编译处理,mybatis在处理#{}时,它会将sql中的#{}替换为占位符“?”,然后调用PreparedStatement的set方法来赋值。传入字符串后,会在值的两边加上单引号,使用占位符的方式提高效率,防止sql注入。

总结

总的来说,#{}是经过预编译的,是安全的;${} 是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。因此在编写mybatis的映射语句时,尽量采用#{xxx}这样的格式。若不得不使用${xxx}这样的参数,要手工地做好过滤工作,来防止sql注入攻击。

猜你喜欢

转载自blog.csdn.net/weixin_44571055/article/details/128639318