Mybatis防止sql注入原理
SQL 注入是一种代码注入技术,用于攻击数据驱动的应用,恶意的SQL 语句被插入到执行的实体字段中(例如,为了转储数据库内容给攻击者)。[摘自] SQL注入 - 维基百科SQL注入,大家都不陌生,是一种常见的攻击方式。攻击者在界面的表单信息或URL上输入一些奇怪的SQL片段(例如“或'1'='1'”这样的语句),有可能入侵参数检验不足的应用程序。所以,在我们的应用中需要做一些,来防备这样的攻击方式。在一些安全性要求很高的应用(比如银行软件),经常使用将 SQL 语句全部替换为存储过程这样的方式,来防止SQL注入。当然这的英文一种很安全的方式,但我们平时开发中,可能不需要这种死板的方式。
MyBatis的框架作为一款半自动化的ORM框架,其SQL语句都要我们自己手动编写,这个时候当然需要防止SQL 注入。其实,MyBatis的SQL的是一个具有“ 输入+ 输出 ”的功能,类似于函数的结构,如下:
<select id =“ getBlogById ”resultType =“ Blog ”parameterType =“ int ”>
SELECT id,title,author,content
FROM blog
WHERE id =#{id}
</select>
这里,参数类型表示了输入的参数类型,与resultType表示了输出的参数类型,回应上文,如果我们想防止SQL注入,理所当然地要在输入参数上下功夫。上面代码中黄色高亮即输入参数在SQL中拼接的部分,传入参数后,打印出执行的SQL语句,会看到SQL是这样的:
SELECT id,title,author,content FROM blog WHERE id =?
不管输入什么参数,打印出的SQL都是这样的这是因为MyBatis的启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL ,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。
底层实现原理
其原因就是:采用了JDBC的PreparedStatement,就会将sql语句:“select id,no from user where id =?” 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析sql命令,比如select,from,where,and,or,order by等等。
所以即使你后面输入了这些sql命令,也不会被当成sql命令来执行了,因为这些SQL命令的执行,必须先得通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。
所以的sql语句预编译可以防御SQL注入。而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。
话说回来,是否我们使用的MyBatis就一定可以防止SQL注入呢当然不是,请看下面的代码?
<select id =“ getBlogById ”resultType =“ Blog ”parameterType =“ int ”>
SELECT id,title,author,content
FROM blog
WHERE id = $ {id}
</select>
仔细观察,内联参数的格式由“ # {xxx}”变为了“ $ {xxx}”。如果我们给参数“ id ”赋值为“ 3 ”,将SQL打印出来是这样的:
SELECT id,title,author,content FROM blog WHERE id = 3
(上面的对比示例是我自己添加的,为了与前面的示例形成鲜明的对比。)
<select id =“ orderBlog ”resultType =“ Blog ”parameterType =“ map ”>
SELECT id,title,author,content
FROM blog
ORDER BY $ {orderParam}
</select>
仔细观察,内联参数的格式由“ # {xxx}”变为了“ $ {xxx}”。如果我们给参数“ orderParam ”赋值为“ id ”,将SQL打印出来是这样的:
SELECT id,title,author,content FROM blog ORDER BY id
显然,这样是无法阻止SQL 注入的。在MyBatis中,“ $ {xxx}”这样格式的参数会直接参与SQL 编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用$ {xxx}“这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。
【结论】在编写MyBatis的映射语句时,尽量采用“ # {xxx}”这样的格式。若不得不使用“ $ {xxx}”这样的参数,要手工地做好过滤工作,来防止SQL注入攻击。
#{}
:相当于JDBC中的PreparedStatement的$ {}
:是输出变量的值
简单说,# {}是经过预编译的,是安全的 ; $ {}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。
如果我们通过语句后用了订单$ {},那么不做任何处理的时候是存在SQL注入危险的。你说怎么防止,那我只能悲惨的告诉你,你得手动处理过滤一下输入的内容。如判断一下输入的参数的长度是否正常(注入语句一般很长),更精确的过滤则可以查询一下输入侧的参数是否在预期的参数集合中。
Mybatis解决sql注入问题
sql注入大家都不陌生,是一种常见的攻击方式,攻击者在界面的表单信息或url上输入一些奇怪的sql片段,例如“or ‘1'='1'”这样的语句,有可能入侵参数校验不足的应用程序。
所以在我们的应用中需要做一些工作,来防备这样的攻击方式。在一些安全性很高的应用中,比如银行软件,经常使用将sql语句全部替换为存储过程这样的方式,来防止sql注入,这当然是一种很安全的方式,但我们平时开发中,可能不需要这种死板的方式。
mybatis框架作为一款半自动化的持久层框架,其sql语句都要我们自己来手动编写,这个时候当然需要防止sql注入。其实Mybatis的sql是一个具有“输入+输出”功能,类似于函数的结构,如下:
<select id=“getBlogById“ resultType=“Blog“ parameterType=”int”><br>
select id,title,author,content from blog where id=#{id}
</select>
这里,parameterType标示了输入的参数类型,resultType标示了输出的参数类型。回应上文,如果我们想防止sql注入,理所当然地要在输入参数上下功夫。上面代码中高亮部分即输入参数在sql中拼接的部分,传入参数后,打印出执行的sql语句,会看到sql是这样的:
select id,title,author,content from blog where id = ?
不管输入什么参数,打印出的sql都是这样的。这是因为mybatis启用了预编译功能,在sql执行前,会先将上面的sql发送给数据库进行编译,执行时,直接使用编译好的sql,替换占位符“?”就可以了。因为sql注入只能对编译过程起作用,所以这样的方式就很好地避免了sql注入的问题。
mybatis是如何做到sql预编译的呢?其实在框架底层,是jdbc中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的sql语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行一个sql时,能够提高效率,原因是sql已编译好,再次执行时无需再编译。
话说回来,是否我们使用mybatis就一定可以防止sql注入呢?当然不是,请看下面的代码:
<select id=“orderBlog“ resultType=“Blog“ parameterType=”map”>
select id,title,author,content from blog order by ${orderParam}
</select>
仔细观察,内联参数的格式由“#{xxx}”变为了${xxx}。如果我们给参数“orderParam”赋值为”id”,将sql打印出来,是这样的:
select id,title,author,content from blog order by id
显然,这样是无法阻止sql注入的。在mybatis中,” xxx”这样格式的参数会直接参与sql编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“ {xxx}”这样的参数格式,所以,这样的参数需要我们在代码中手工进行处理来防止注入。
小结一下
在编写mybatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。