首页 > 技术文章 > sql注入原理及防范方式

ls-shiyi 2019-08-16 21:57 原文

前言

        sql注入是一种危险系数较高的攻击方式,现在由于我们持久层框架越来越多,大部分框架会处理这个问题,因此导致我们对它的关注度越来越少了。最近部门在整理安全漏洞时,提到了一些关于sql注入的修改点,因此共同记录学习一下。

正文

原理

        sql注入的原理是将sql代码伪装到输入参数中,传递到服务器解析并执行的一种攻击手法。也就是说,在一些对server端发起的请求参数中植入一些sql代码,server端在执行sql操作时,会拼接对应参数,同时也将一些sql注入攻击的“sql”拼接起来,导致会执行一些预期之外的操作。

示例:

        比如我们使用的登录接口:在登录界面包括用户名和密码输入框,以及提交按钮,输入用户名和密码,提交。

        登录时调用接口/user/login/ 加上参数username、password,首先连接数据库,然后后台对请求参数中携带的用户名、密码进行参数校验,即sql的查询过程。假设正确的用户名和密码为ls和123456,输入正确的用户名和密码、提交,相当于调用了以下的SQL语句。

SELECT * FROM user WHERE username = 'ls' AND password = '123456'

        sql中会将#及–以后的字符串当做注释处理,如果我们使用“’ or 1=1 #” 作为用户名参数,那么服务端构建的sql语句就如下:

select * from users where username='' or 1=1#' and password='123456'

        而#会忽略后面的语句,因此上面的sql也等价于:

select * from users where username='' or 1=1

        而1=1属于常等型条件,因此这个sql便成为了如下,查询出所有的登陆用户。

select * from users

        其实上面的sql注入只是在参数层面做了些手脚,如果是引入了一些功能性的sql那就更危险了,比如上面的登陆接口,如果用户名使用这个“’ or 1=1;delete * from users; #”,那么在";"之后相当于是另外一条新的sql,这个sql是删除全表,是非常危险的操作,因此sql注入这种还是需要特别注意的。

解决sql注入原理

sql预编译

        在知道了sql注入的原理之后,我们同样也了解到mysql有预编译的功能,指的是在服务器启动时,mysql client把sql语句的模板(变量采用占位符进行占位)发送给mysql服务器,mysql服务器对sql语句的模板进行编译,编译之后根据语句的优化分析对相应的索引进行优化,在最终绑定参数时把相应的参数传送给mysql服务器,直接进行执行,节省了sql查询时间,以及mysql服务器的资源,达到一次编译、多次执行的目的,除此之外,还可以防止SQL注入。

        具体是怎样防止SQL注入的呢?实际上当将绑定的参数传到mysql服务器,mysql服务器对参数进行编译,即填充到相应的占位符的过程中,做了转义操作。
        我们常用的jdbc就有预编译功能,不仅提升性能,而且防止sql注入。

        String sql = "select id, no from user where id=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setInt(1, id);
        ps.executeQuery();

严格的参数校验

        参数校验就没得说了,在一些不该有特殊字符的参数中提前进行特殊字符校验即可。

框架的支持——mybatis

        java生态中很常用的持久层框架mybatis就能很好的完成对sql注入的预防,如下两个mapper文件,前者就可以预防,而后者不行。

<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password, role
from user
where username = #{username,jdbcType=VARCHAR}
and password = #{password,jdbcType=VARCHAR}
</select>

<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password, role
from user
where username = ${username,jdbcType=VARCHAR}
and password = ${password,jdbcType=VARCHAR}
</select>
  1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
    如: where username=#{username},如果传入的值是111,那么解析成sql时的值为where username=“111”, 如果传入的值是id,则解析成的sql为where username=“id”.
  2. sql:whereusername=将传入的数据直接显示生成在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注入。mybatis底层还是使用jdbc的预编译功能。

结语

        以上是对sql注入方式、原理及危害、问题解决做出的一些小小的总结,如果有问题希望大家能够指出,多多交流。

推荐阅读