mybatis 中使用 sqlMap 进行 sql 查询时,经常需要动态传递参数,例如sql 如下:
select * from student where uid=#{uid} AND student_name='${studentName}'
在动态 SQL 解析阶段, #{ } 和 ${ } 会有不同的表现:
#{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符占位符 ?。
上面的例子就被解析为
...... uid=?
${ } 仅仅为一个纯碎的 string 替换,在mybatis的动态 SQL 解析阶段将会进行变量替换。比如:传入的studentName参数是“张三”
...... student_name='张三'
最后解析完成的完整语句就是
select * from student where uid= ? AND student_name='张三'
然后把解析好的语句在通过JDBC执行。
所以因为在${ } 在预编译之前已经被变量替换了,这会存在 sql 注入问题。比如:传入的studentName参数是“1' OR '1'='1”
在mybatis中解析完成后变成了
select * from student where uid= ? AND student_name='1' OR '1'='1'
这样就查询出所有的数据了。
源码查看:
在执行mybtis的查询是,会执行到PreparedStatementHandler#query,查看生成的PreparedStatement
如果传入参数传入的studentName参数是“1' OR '1'='1”
所以就出现注入的情况了。
如果我们把${studentName}换成#{studentName},在传入“1' OR '1'='1”呢
在mybatis解析出的结果
在数据库的执行日志中查询执行的sql语句
发现我们传入的“'”被转义了,
所以通过预编译,用占位符的方式传值可以把一些特殊的字符进行转义,这样可以防止一些sql注入。
其原因就是:采用了JDBC的PreparedStatement,就会将sql语句:“select * from student where uid= ? AND student_name= ?” 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析sql命令,比如select,from,where,and,or,order by等等。所以即使你后面输入了这些sql命令,也不会被当成sql命令来执行了,因为这些SQL命令的执行,必须先得通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。所以的sql语句预编译可以防御SQL注入。而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。
默认使用PreparedStatement是不能执行预编译的,这需要在url中给出useServerPrepStmts=true参数(MySQL Server 4.1之前的版本是不支持预编译的,而Connector/J在5.0.5以后的版本,默认是没有开启预编译功能的)。
例如:jdbc:mysql://localhost:3306/test?useServerPrepStmts=true
这样才能保证mysql驱动会先把SQL语句发送给服务器进行预编译,然后在执行executeQuery()时只是把参数发送给服务器。
发表回复
评论列表(0条)