如何解决SQL注入?

面试中经常问到,SQL注入是什么?又怎么防止SQL注入?为了不再尴尬得只回答出使用PreparedStatement,我们还是有必要了解一下其他的方式。

一、什么是SQL注入?

说简单点,就是部分用户在表单中输入sql语句的片段,对没有输入检验的网站可能带来毁灭性的打击,轻则绕过登录,重则删库、泄露数据。


二、一个简单的SQL注入实例

假设某个网站没有输入参数检验,并且后台的sql语句为

 String sql = "select * from user where username='" + username + "' and password='" + password + "'";

那么如果我在表单中输入的用户名为

String username="' or 1=1 --";

并且任意输入一个密码后,会发生什么呢?组合后的sql语句为

String afterSql="select * from user where username='' or 1=1 -- and password='123'";

where中的子句username='' or 1=1永远为真,是因为使用了--,将后面的密码判断给注释了。这样子我可以绕过登录,去做一些危害网站的事情,比如删库。即使用下面的注入

String deleteDB="select * from user where username='';drop table user -- and password='123'";

可见,如果一个网站不对输入的参数进行检验的话,很可能会威胁到网站自身的安全。


三、怎么防范sql注入?

sql注入只会发生在sql编译的过程中,那么避免非法sql语句被编译,就是我们要做的事情。

【1】在JDBC中,使用Statement的子类PreparedStatement,这也是我们最先想到的方法

事先将sql语句传入PreparedStatement中,等会要传入的参数使用?代替,那么该sql语句会进行预编译,之后将前台获取的参数通过set方式传入编译后的sql语句中,那么此时被注入的sql语句无法得到编译,从而避免了sql注入的问题。而且使用PreparedStatement在一定程度上有助于数据库执行性能的提升。

 PreparedStatement preparedStatement = connection.prepareStatement(sql);
 preparedStatement.setString(1, username);
 preparedStatement.setString(2, password);

 

【2】Mybatis中尽量使用#{}而不是${},下面给出一个典型的查询写法

<select id="findByUserId" parameterType="edu.usts.sddb.entity.User"
            resultType="edu.usts.sddb.entity.User">
		select * from t_user where us_id=#{us_id}
</select>

#{}就像是JDBC中的?,mybatis通过使用#{}的方式,代表该语句在执行之前会经过预编译的过程,后来传入的参数通过占位符的方式填入到已经编译完的语句中。但使用${}的参数会直接参与编译,不能避免sql注入。如果需求中非要使用到${}的话,只能手动过滤了。

 

【3】手动过滤参数

声明一个危险参数数组danger,将前台传入的参数用“ ”分隔后,产生的参数数组与danger有交集时,终止该sql的执行,并反馈前台,提示禁止输入非法字符。