一、SQL注入的含义与原理
SQL注入:利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,这是SQL注入的标准释义。
SQL注入是网站存在最多也是最简单的漏洞,是一种常见的数据库攻击手段。主要原因是程序员在开发用户和数据库交互的系统时没有对用户输入的字符串进行过滤,转义,限制或处理不严谨,导致用户可以通过输入精心构造的字符串去非法获取到数据库中的数据
SQL 注入其实就是恶意用户通过在表单中填写包含SQL关键字的数据来使数据库执行非常规代码的过程。SQL数据库的操作是通过SQL语句来执行的,而无论是执行代码还是数据项都必须写在SQL语句之中,这就导致如果我们在数据项中加入了某些SQL语句关键字(比如说SELECT、DROP等等),这些关键字就很可能在数据库写入或读取数据时得到执行。
二、SQL注入分类及判断
1、SQL注入分类
按数据类型分为数字型、字符型和搜索型;按提交方式分为GET型,POST型,Cookie型和HTTP请求头注入;按执行效果分为报错注入、联合查询注入、盲注和堆查询注入。其中盲注又可分为基于bool的和基于时间的注入。从查询语句及可看出来这里是字符型的注入同时也是GET型注入和表单注入,数字型注入查询语句为:SELECT * FROM user WHERE id=1,搜索型注入为查询语句为:SELECT * FROM user WHERE search like '%1%'。
2、SQL注入判断
在知道查询语句的情况下我们很容易辨别是否存在注入及注入类型,在不知道查询语句的情况下,我们可以这样操作作出判断:在URL或者表单中输入一个单引号或者其他特殊符号,页面出现错误说明此页面存在SQL注入,如果页面正常则显示说明有字符被过滤或者不存在注入。
如果存在注入可以进一步判断注入类型,在URL或者表单中输入0 or 1,如果可以查到数据,说明是数字型注入,如果输入0'or 1#,查到数据说明是字符型注入,方法不唯一。总之数字型注入不需要使用单引号闭合前面的单引号就可以执行SQL语句,而字符型必须闭合前面的单引号,然后才可以执行SQL语句,同时也需要把后面的单引号闭合,而注释就是很好的一种闭合后面的单引号的方法。
GET型注入很容易从URL中看出来,如网页的URL为:http://127.0.0.1/DVWA/vulnerabilities/sqli/?id=1,浏览器通常使用?来表示GET方法传递参数,而使用POST传递参数是不会显示到URL中的,因此URL中含有?说明就是使用GET方法传递参数。
三、如何有效防止SQL注入
注入问题都是因为执行了数据项中的SQL关键字,那这样只要检查数据项中是否存在SQL关键字不就可以了么?
这种操作的确实可行的,很多数据库管理系统采取了这种简单粗暴过滤手法,但并不能从根本上解决问题,譬如有人名字就叫做“Drop Table”呢?对于这种情况都每种数据库会有自己的的解决办法,例如Node mySql可以以字符串的形式提交,like this:
insert into student (name) values('drop table')
合理的防护办法有很多:
1、数据校验
第一,尽量避免使用常见的数据库名和数据库结构。在上面的案例中,如果表单名字并不是students,则注入代码将会在执行过程中报错,也就不会发生数据丢失的情况——SQL注入并不像大家想象得那么简单,它需要攻击者本身对于数据库的结构有足够的了解才能成功,因而在构建数据库时尽量使用较为复杂的结构和命名方式将会极大地减少被成功攻击的概率。
第二,使用正则表达式等字符串过滤手段限制数据项的格式、字符数目等也是一种很好的防护措施。理论上,只要避免数据项中存在引号、分号等特殊字符就能很大程度上避免SQL注入的发生。
使用正则表达式过滤传入的参数:
要引入的包:import java.util.regex.*;
正则表达式:private String CHECKSQL = “^(.+)sands(.+)|(.+)sor(.+)s$”;
判断是否匹配:Pattern.matches(CHECKSQL,targerStr);
下面是具体的正则表达式:
检测SQL meta-characters的正则表达式 :/(%27)|(’)|(--)|(%23)|(#)/ix
修正检测SQL meta-characters的正则表达式 :/((%3D)|(=))[^]*((%27)|(’)|(--)|(%3B)|(:))/i
典型的SQL 注入攻击的正则表达式:/w*((%27)|(’))((%6F)|o|(%4F))((%72)|r|(%52))/ix
检测SQL注入,UNION查询关键字的正则表达式:/((%27)|(’))union/ix(%27)|(’)
检测MS SQL Server SQL注入攻击的正则表达式:/exec(s|+)+(s|x)pw+/ix
等等…
字符串过滤:
(||之间的参数可以根据自己程序的需要添加)
public static boolean sql_inj(String str){
String inj_str = "'|and|exec|insert|select|delete|update|
count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,";
String inj_stra[] = split(inj_str,"|");
for (int i=0 ; i < inj_stra.length ; i++ ){
if (str.indexOf(inj_stra[i])>=0){
return true;
}
}
return false;
}
第三,就是使用各类程序文档所推荐的数据库操作方式来执行数据项的查询与写入操作,比如在上述的案例中,如果我们稍加修改,首先使用execute()方法来保证每次执行仅能执行一条语句,然后将数据项以参数的方式与SQL执行语句分离开来,就可以完全避免SQL注入的问题。或者进行数据转义也是可以避免SQL注入。
第四,PreparedStatement。采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。
使用好处:(1)代码的可读性和可维护性;(2)PreparedStatement尽最大可能提高性能;(3)最重要的一点是极大地提高了安全性。
原理:sql注入只对sql语句的准备(编译)过程有破坏作用,而PreparedStatement已经准备好了,执行阶段只是把输入串作为数据处理,而不再对sql语句进行解析,准备,因此也就避免了sql注入问题。
2、权限限制
普通用户与系统管理员用户的权限要有严格的区分。严格限制Web应用的数据库的操作权限,给此用户提供仅仅能够满足其工作的最低权限,从而最大限度的减少注入攻击对数据库的危害。请记住永远不要使用超级用户或所有者帐号去连接数据库!当数据库被攻击时将损伤限制在当前表的范围是比较明智的选择。通过权限限制可以防止攻击者获取数据库其它信息,甚至利用数据库执行Shell命令等操作。
3、日志处理
当数据库操作失败的时候,尽量不要将原始错误日志返回,把代码里的SQL语句暴露出来,以防止攻击者利用这些错误信息进行SQL注入。除此之外,在允许的情况下,使用代码或数据库系统保存查询日志也不失为一个好办法。显然,日志并不能防止任何攻击,但定期审计数据库执行日志可以跟踪是否存在应用程序正常逻辑之外的SQL语句执行。日志本身没用,要查阅其中包含的信息才行。毕竟,更多的信息总比没有要好。
4、强迫使用参数化语句
如果在编写SQL语句的时候,用户输入的变量不是直接嵌入到SQL语句,而是通过参数来传递这个变量的话,就可以有效的防治SQL注入式攻击。也就是说,用户的输入绝对不能够直接被嵌入到SQL语句中。与此相反,用户的输入的内容必须进行过滤,或者使用参数化的语句来传递用户输入的变量。参数化的语句使用参数而不是将用户输入变量嵌入到SQL语句中。采用这种措施,可以杜绝大部分的SQL注入式攻击。不过可惜的是,现在支持参数化语句的数据库引擎并不多。不过数据库工程师在开发产品的时候要尽量采用参数化语句。
5、备份与加密
最后,最重要是还是做好数据库的备份,同时对敏感内容进行加密。某些安全性问题可能永远不会有完美的解决方案,只有我们做好最基本的防护措施,才能在发生问题的时候亡羊补牢,降低损失到最小程度。
SQL注入一篇文章是讲不完的,相信大家通过本篇已掌握SQL注入的含义原理及如何防止SQL注入的几个方法。