Ja va防SQL注入:从根源到边界的实战策略
谈起Ja va Web应用的安全,SQL注入绝对是个绕不开的“经典”话题。攻击者之所以能得手,核心往往在于一个简单的操作:字符串拼接。当用户输入被直接拼接到原始SQL语句中时,就相当于为恶意逻辑的植入打开了一扇门。那么,最根本的解决之道是什么?答案是杜绝拼接,使用参数化查询。
第一道防线:使用PreparedStatement
用PreparedStatement替代Statement来执行SQL,这几乎是开发者的共识。它的原理很清晰:SQL语句的结构在预编译时就被确定了,后续传入的参数只会被当作数据来处理,而无法改变查询的逻辑骨架。这就好比给SQL语句做了一个石膏固定,无论输入什么,骨骼结构都不会变。如此一来,绝大多数基于结构篡改的SQL注入攻击,在源头就被挡住了。
当然,安全防御从来不能只靠单一层面。在数据库访问层筑牢根基的同时,我们还需要在更前端的WEB层建立过滤机制,形成纵深防御。
第二道防线:全局输入过滤(Filter)
在Web层,一个常见的做法是利用Filter来对全局的请求参数进行过滤。思路就是遍历所有传入的表单参数,检查其中是否包含可能用于SQL注入的关键字或特殊字符。一旦发现,就立即中断请求处理流程。下面是一个典型的Filter实现示例,它定义了一个可疑字符串列表,并对参数进行匹配检查:
01 import ja va.io.IOException;
02 import ja va.util.Iterator;
03 import ja vax.servlet.Filter;
04 import ja vax.servlet.FilterChain;
05 import ja vax.servlet.FilterConfig;
06 import ja vax.servlet.ServletException;
07 import ja vax.servlet.ServletRequest;
08 import ja vax.servlet.ServletResponse;
09 import ja vax.servlet.http.HttpServletRequest;
10 import ja vax.servlet.http.HttpServletResponse;
11 /**
12 * 通过Filter过滤器来防SQL注入攻击
13 *
14 */
15 public class SQLFilter implements Filter {
16 private String inj_str = "'|and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|; |or|-|+|,";
17 protected FilterConfig filterConfig = null;
18 /**
19 * Should a character encoding specified by the client be ignored?
20 */
21 protected boolean ignore = true;
22 public void init(FilterConfig config) throws ServletException {
23 this.filterConfig = config;
24 this.inj_str = filterConfig.getInitParameter("keywords");
25 }
26 public void doFilter(ServletRequest request, ServletResponse response,
27 FilterChain chain) throws IOException, ServletException {
28 HttpServletRequest req = (HttpServletRequest)request;
29 HttpServletResponse res = (HttpServletResponse)response;
30 Iterator values = req.getParameterMap().values().iterator();//获取所有的表单参数
31 while(values.hasNext()){
32 String[] value = (String[])values.next();
33 for(int i = 0;i < value.length;i++){
34 if(sql_inj(value[i])){
35 //TODO这里发现sql注入代码的业务逻辑代码
36 return;
37 }
38 }
39 }
40 chain.doFilter(request, response);
41 }
42 public boolean sql_inj(String str)
43 {
44 String[] inj_stra=inj_str.split("\\|");
45 for (int i=0 ; i < inj_stra.length ; i++ )
46 {
47 if (str.indexOf(" "+inj_stra[i]+" ")>=0)
48 {
49 return true;
50 }
51 }
52 return false;
53 }
54 }
局部精准过滤:特定字段处理
除了全局过滤,有时我们可能需要在具体的Ja vaBean字段上进行更精准的清洗。例如,下面这个方法就使用了正则表达式,将字符串中可能用于注入的单引号、分号或SQL注释符(--)替换掉。这种方法更适合于对特定输入进行针对性处理:
1 /**
2 * 防止sql注入
3 *
4 * @param sql
5 * @return
6 */
7 public static String TransactSQLInjection(String sql) {
8 return sql.replaceAll(".*([';]+|(--)+).*", " ");
9 }
话说回来,需要警惕的是,过滤名单(黑名单)的方式永远存在被绕过的风险。因此,真正坚固的防御体系,必然是以参数化查询(PreparedStatement)为核心基石,再辅以各层的输入验证与过滤,这样才能最大程度地将SQL注入的风险降至最低。记住,安全无小事,层层设防才是关键。
