1.1 漏洞描述
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。
根据相关技术原理,SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤,从而执行了非法的数据查询。基于此,SQL注入的产生原因通常表现在以下几方面:①不当的类型处理;②不安全的数据库配置;③不合理的查询集处理;④不当的错误处理;⑤转义字符处理不合适;⑥多个提交处理不当。
1.2 漏洞危害
数据库信息泄漏:数据库中存放的用户的隐私信息的泄露。
网页篡改:通过操作数据库对特定网页进行篡改。
网站被挂马,传播恶意软件:修改数据库一些字段的值,嵌入网马链接,进行挂马攻击。
数据库被恶意操作:数据库服务器被攻击,数据库的系统管理员帐户被窜改。
服务器被远程控制,被安装后门(WEBSHELL)。经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统。
破坏硬盘数据,瘫痪全系统。
1.3 漏洞演示
注入攻击的本质就是把用户输入的数据当做代码来执行,这里有两个关键条件:第一个是用户能够控制输入,第二个是原本程序要执行的代码,拼接了用户输入的数据。
比如这个sql:"select id from users where username = '"+username +"' and password = '" + password +"'" 。如果用户按着设计者的要求输入正常的用户名和密码,这个没有任何问题,可以正常登陆。但是这个sql因为应用了sql拼接,用户是可以控制输入的,所以如果用户拼接了可执行的数据,例如:username = "",password = "' or '1' = '1";那么sql就变成了"select id from users where username = '' and password = '' or '1' = '1'",它等价于select id from users where '1' = '1'。这个SQL是恒真的,所以它就绕过了用户名和密码,登录成功了!
注入步骤:
1)寻找注入点(URL地址栏、登陆界面、留言板、搜索框等)
2)用户自己构造SQL语句(如:’ or 1 = 1)
3)将SQL语句发送给数据库管理系统(DBMS)
4)DBMS接收请求,并将该请求解释成机器代码指令,执行必要的存取操作
5)DBMS接收返回的结果,并处理,返回给用户
因为用户构造了特殊的SQL语句,必定返回特殊的结果(只要你的SQL语句够灵活的话)。
1.4 修复方案
1)基本上大家都知道采用SQL语句预编译和绑定变量,是防御SQL注入的最佳方法。但是其中的深层次原因就不见得都理解了。
String sql = "select id, no from user where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.executeQuery();
如上所示,就是典型的采用 SQL语句预编译和绑定变量 。为什么这样就可以防止SQL注入呢?
其原因就是:采用了PreparedStatement,就会将SQL语句:"select id, no from user where id=?" 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析SQL命令,比如 select ,from ,where ,and, or ,order by 等等。所以即使你后面输入了这些SQL命令,也不会被当成SQL命令来执行了,因为这些SQL命令的执行, 必须先的通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。所以SQL语句预编译可以防御SQL注入。
2)但是不是所有场景都能够采用SQL语句预编译,有一些场景必须的采用字符串拼接的方式,此时,我们严格检查参数的数据类型,还有可以使用一些安全函数来防止SQL注入。
比如 String sql = "select id,no from user where id=" + id;
在接收到用户输入的参数时,我们就严格检查id,只能是int型。复杂情况可以使用正则表达式来判断。这样也是可以防止SQL注入的。
安全函数的使用,比如:
MySQLCodec codec = new MySQLCodec(Mode.STANDARD);
name = ESAPI.encoder().encodeForSQL(codec, name);
String sql = "select id,no from user where name=" + name;
ESAPI.encoder().encodeForSQL(codec, name)
该函数会将name中包含的一些特殊字符进行编码,这样SQL引擎就不会将name中的字符串当成SQL命令来进行语法分析了。
注:实际项目中,一般我们都是采用各种的框架,比如ibatis,hibernate,mybatis等等。他们一般也默认就是SQL预编译的。对于ibatis/mybatis,如果使用的是#{name}形式的,那么就是SQL预编译,使用${name}就不是SQL预编译的。
3)使用正则表达式过滤:
private String CHECKSQL = “^(.+)\\sand\\s(.+)|(.+)\\sor(.+)\\s$”;
//判断是否匹配:
Pattern.matches(CHECKSQL,targerStr);
//下面是具体的正则表达式:
//检测SQL meta-characters的正则表达式 :
/(\%27)|(\’)|(\-\-)|(\%23)|(#)/ix
//修正检测SQL meta-characters的正则表达式 :
/((\%3D)|(=))[^\n]*((\%27)|(\’)|(\-\-)|(\%3B)|(:))/i
//典型的SQL注入攻击的正则表达式 :
/\w*((\%27)|(\’))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix
//检测SQL注入,UNION查询关键字的正则表达式 :
/((\%27)|(\’))union/ix(\%27)|(\’)
//检测MSSQL Server SQL注入攻击的正则表达式:
/exec(\s|\+)+(s|x)p\w+/ix
//其实可以简单的使用replace方法也可以实现上诉功能:
public static String TransactSQLInjection(String str){
return str.replaceAll(".*([';]+|(--)+).*", " ");
}
总的来说有以下几点:
a)永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。
b)永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
c)永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
d)不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。
e)应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装,把异常信息存放在独立的表中。
1.5 相关参考
http://www.cnblogs.com/smilewxt/p/4229810.html
http://blog.csdn.net/quiet_girl/article/details/50586809