001.【提升】SQL注入攻击原理及防御策略

发布于 2022年 01月 11日 21:05

[前言]

作为一个后端开发人员,可能在工作中或者面试的时候,总回被人问到SQL注入的问题。可能也会因为没有实际遇到过或者解决过这类问题,总是一知半解。本文着重讲清楚什么是SQL注入,以及列举一些示例,更直观的看到那些情况会导致SQL注入,以及可能带来的危害。

一.什么是SQL注入

SQL注入,一般指web应用程序对用户输入数据的合法性没有校验或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。 总的来说就是,攻击者通过系统正常的输入数据的功能,输入恶意数据,而系统又未作任何的校验,直接信任了用户输入,使得恶意输入改变原本的SQL逻辑或者执行了额外的SQL脚本达,从而造成了SQL注入攻击。

二.SQL注入的危害及案例

1.SQL注入攻击的危害

SQL注入攻击的危害有很多,比如:

  • 使得系统业务功能异常或者失效
    
  • 恶意的破坏,比如修改数据,删数据,删表等恶意破坏;
    
  • 探查数据库类型,结构,获取数据库敏感数据,造成数据泄露;
    
  • 修改数据库配置,控制服务器,进行恶意活动等
    

sql注入过程及危害,如图:

2.业界的案例

2011年6月,LulzSec的黑客组织攻击了索尼公司网站,并获取了100万用户的个人信息,包括用户名,密码,家庭住址,生日等一些敏感信息。造成了很大的影响。LulzSec组织声称入侵手段就是简单的SQL注入技术,通过SQL注入,就获取到了全部的敏感信息。

由此可以看出,SQL注入攻击的危害很多,其实最主要还是对系统的正常运行,数据安全等有很大的威胁,一旦造成敏感数据泄露,就将会是很严重的事件,也将会对公司的业务和声誉造成很大的影响。因此这就要求我们系统设计开发人员要有安全意识,系统设计和开发中就要做好防御策略,从而使系统更加健壮。

三.SQL注入防御策略

目前要的防御方案主要包括:参数化查询(预编译),输入验证,以及转码。各策略内容和措施如下:

单一的策略并不是最可靠的,需要多种防御策略结合使用,多重防御,提升防御的坚固性。建议采用如下方案:

后边按照以上的防御策略,结合实际开发中的一些案例,讲述如何实施。

四.一些正反面案例

1.SQL注入示例场景

以系统登录场景来说,SQL注入的场景:

可以看出,如果不做任何的防御措施,无条件相信用户输入,直接将用户输入拼接到sql语句到数据库执行,最终可能就会被SQL注入攻击。

2.场景1:JDBC执行SQL

场景描述:登录直接使用外部输入来拼接SQL查询语句。

(1) 错误代码示例:

public void loginVertify(String userName,char[] password){
  try{
       String passwordStr = hashPassword(password);
       String sqlStr = "SELECT * FROM t_user WHERE user_name='" + userName + "' AND password = '" + passwordStr + "'";
       
       Statement stmt = connection.createStatement();
       Result rs = stmt.executeQuery(sqlStr);
       //..... vertify query result
   }catch(){
      //deal exception
  }
}

可以看到此处的用户信息校验,未对输入数据做任何校验,并且直接将输入数据拼接成sql语句,直接到数据库执行。 恶意语句输入示例: userName = admin' or '1' = '1' ,此时只要有admin用户,就可以绕过密码的验证,直接成功。

(2)正确示例:使用预编译语句 说明:使用PrepareStatement可以调用数据库参数化(预编译)查询特征,参数化的查询将查询逻辑和数据分离,互不干扰。 正确的示例代码,如下:

public void loginVertify(String userName,char[] password){
  try{
       String passwordStr = hashPassword(password);
       String sqlStr = "SELECT * FROM t_user WHERE user_name='?' AND password = '?";
       
       PrepareStatement preStmt = connection.prepareStatement();
       preStmt.setString(1,userName);
       preStmt.setString(2,passwordStr);
       Result rs = preStmt.executeQuery(sqlStr);
       //..... vertify query result
   }catch(){
      //deal exception
  }
}

说明:PrepareStatement先提交SQL查询逻辑给数据库预编译(固化),后提供的usetname参数即使是:admin' or '1'='1'这样的字符串,也只会被当作数据,无法改变之前的查询逻辑。

3.场景2:Mybatis执行SQL

(1)错误代码示例:使用$符号传参

<slect id = 'login' paramType = "java.util.Map" resultType="java.util.Map">
    SELECT * 
    FROM t_user 
    where user_name = ${userName}
    and password = ${password}
</select>

说明:使用$传参,相当于直接使用参数拼接SQL语句,若是恶意输入,未作校验处理,则就有SQL注入的风险。

(2)正确示例:使用#传入参数

<slect id = 'login' paramType = "java.util.Map" resultType="java.util.Map">
    SELECT * 
    FROM t_user 
    where user_name = #{userName}
    and password = #{password}
</select>

说明:使用#传参才能够真正达到将SQL语义逻辑和数据分离的效果。

4.场景3:存储过程注入

(1)调用存储过程的代码: (2)错误示例: (3)正确示例:

5.转码的防御策略

说明:直接对数据进行校验,以及对特殊字符及逆行转义。针对每种数据的转义机制的实现,可以使用现有的API工具--OWASP ESAPI 的escaping routing。 备注:了解OWASP ESAPI项目 — OWASP-CHINA

示例代码:

public void loginVertify(String userName,char[] password){
  try{
       String passwordStr = hashPassword(password);
       Codec oe = new OracleCode();
       String uName = ESAPI.encoderForSQL(oe,userName);
       String pwd = ESAPI.encoderForSQL(oe,passwordStr);
       
       String sqlStr = "SELECT * FROM t_user WHERE user_name='" + userName + "' AND password = '" + passwordStr + "'";
       
       Statement stmt = connection.createStatement();
       Result rs = stmt.executeQuery(sqlStr);
       //..... vertify query result
   }catch(){
      //deal exception
  }
}

说明:对输进行校验,会对影响SQL语句语义的特殊字符,如 单引号,双引号等做转义,这样的话,对于恶意的输入,拼接到原来的语句中就会执行不成功。

五.总结

系统的安全性是系统非功能性需求的一个重要指标,也是系统设计和开发人员在系统建设中不可忽略的重要方面。系统架构人员应该从从系统层面设计整个系统的安全策略,开发人员也要在开发中识别出可能有安全风险的场景,尽可能去规避,杜绝安全漏洞的代码。安全无小事,同样也适用于我们的系统开发中。

本文主要讲解了SQL注入攻击的一些原理和场景案例,以及日常开发中的用法。其实系统的攻击还有很多方面,比如XML注入,OS注入等,虽然没有SQL注入攻击常见,也是我们需要注意防范的。

   最后,希望你我都保持热爱,不断积累提升。日拱一卒无有尽,公不唐捐终入海。共勉。

推荐文章