0x01 故事背景
故事的主角是某知名流媒体平台的 AutoDJ 模块(资产域:manager.[redacted].com)。在针对该模块进行常规的安全审计时,我将其作为一个黑盒去审视,试图寻找那些可能与外部或内部服务器进行交互的功能点。
众所周知,流媒体服务通常需要从不同的源拉取音频流,这种“服务器代为发起请求”的业务场景,往往是 SSRF(Server-Side Request Forgery,服务器端请求伪造)漏洞的重灾区。然而,现在的厂商安全意识都很强,常规的 127.0.0.1 探针往往会在 WAF 或 API 网关处被无情拦截。
这次也不例外,但这恰恰激起了我的胜负欲。
0x02 蛛丝马迹:嗅探攻击面
在对资产进行抓包分析时,一个特定的 EndPoint 引起了我的极度舒适(划掉,极度关注):
PUT /v3/api/proxypass_2/1/radios/{pack_id}/stations/{station_id}/streams/{stream_id}
这个接口用于更新流媒体 (stream) 的配置。在抓取的原始请求体中,我敏锐地捕捉到了一个参数:relay_server。
作为一名渗透测试人员的直觉告诉我:“只要看到类似于 server, url, host 的参数,就一定要去摸一摸!”
我熟练地构造了一个包含本地回环地址的 Payload,试图让服务器“自己打自己”:
{
"is_active_relay": true,
"relay_server": "127.0.0.1",
"relay_port": 80
}
毫不意外,响应包返回了冷冰冰的 400 Bad Request:
“code”: “validation_failed”, “description”: “Validation failed”, “attribute”: “relay_server”

规则正确拦截了点分十进制格式的本地回环地址。显然,开发小哥在这里写了一个基于字符串匹配的黑名单,禁止了 127.0.0.1 和 localhost。
本以为到此为止,但看着屏幕上的报错,我的脑海里突然闪过大二计网课本上的那句话:“IP 地址本质上就是一个 32 位的无符号整数。”
前端的 API 验证器和后端的流媒体引擎(这里是 Icecast),它们对 IP 格式的理解是一致的吗?
0x03 漏洞利用
如果字符串匹配是拦截的唯一手段,那我只要换一种写法不就行了?这就涉及到了经典的 Parsing Differential(解析差异) 攻击。
我决定使用点分十进制的变体——十进制纯数字 IP。
简单的数学换算:127.0.0.1 转换成十进制就是 (127 * 2^24) + (0 * 2^16) + (0 * 2^8) + 1 = 2130706433。
带着一丝期待,我将 Payload 修改后重新发送了 PUT 请求:
PUT /v3/api/proxypass_2/1/radios/18107/stations/14181/streams/24277 HTTP/1.1
Host: manager.[redacted].com
Accept: application/json, text/plain, */*
Cookie: SASESSION=kgmYziYb...[脱敏]...; MANAGER-XSRF-TOKEN=eyJpdG...[脱敏]...
{
"is_active_relay": true,
"relay_server": "2130706433",
"relay_port": 80,
"relay_mountpoint": "/yiyi-autodj-128.aac",
"is_enabled": true
}
响应状态码变为了 200 OK,返回了 “result”: “success”。更关键的是,返回数据中的 updated_at 时间戳发生了改变,这意味着我注入的十进制 IP 被数据库成功写入了!


API 的正则/黑名单把 2130706433 当成了一串普通的字符串放行了,验证规则被完美绕过。
但这还不是结束,后端真的执行了吗?
我继续观察。当向 2130706433(即 127.0.0.1)发起连接时,后端的 Icecast 引擎极其“聪明”地将这个十进制数字自动解析回了本地回环地址,并尝试建立连接。
由于 Icecast 尝试从自身非目标端口拉流,产生了自环错误并回退到 AutoDJ 本地源模式。在此过程中,我观察到 BANDWIDTH(带宽)参数出现了非精确整数的波动值。这个微小的带宽高低变化,像是在黑暗中传来的摩斯密码,确凿地证明了:Icecast 确实尝试向 127.0.0.1 发起了真实的网络连接!

0x04 漏洞危害
不要小看这个绕过,一旦撕开了 SSRF 的口子,后果是极其危险的。
- 端到端 API 绕过导致数据可读
十进制 IP 成功绕过 API 的 localhost 黑名单,后端的 Icecast 将其成功解析为 127.0.0.1 并连接了 localhost:8000。原本隔离在内网的数据,现在成了我砧板上的肉。 - 基于带宽特征的盲打端口扫描
利用前面提到的“Icecast relay 尝试连接时导致的带宽值波动特征”,我将其转化为了一个探测器。如果端口开放,带宽波动的表现与端口关闭截然不同。通过自动化发送不同的 relay_port,我成功扫出了服务器内部开放的三个关键端口:80 (Nginx)、8000 (Icecast) 和 8080 (HTTP代理)。服务器的内部服务架构就这样被完全泄露了。 - IPv6 幽灵绕过
在此过程中,我还顺手测试了 IPv6 的回环地址 ::1。结果依然是 API 验证 200 OK 绕过。虽然因为这台机器上的 Icecast 没有监听 IPv6 导致 HLS 返回 400,未能端到端利用,但这也进一步证实了 API 防御规则的极度残缺。
0x05 防御与反思
作为一名不仅要挖洞、还要懂安全的当代大学生,我不能只停留在“把系统搞崩”的阶段。我们需要深究 Root Cause(根本原因)。
这个漏洞的核心在于:API 验证层和后端执行层对 IP 格式的处理逻辑不一致。
验证规则只检查了字符串黑名单(如 127.0.0.1、localhost、0177.0.0.1),却未对输入进行**标准化(Normalization)**处理。
给开发小哥们的修复建议如下(我已经随报告提交给厂商啦):
- 绝对前置的 IP 地址标准化验证
在验证 relay_server 时,必须先将输入标准化为规范的 IP 格式,然后再进行黑名单检查。- 必须拦截并转换十进制格式(如 2130706433 -> 127.0.0.1)
- 必须拦截并转换八进制格式(如 0177.0.0.1 -> 127.0.0.1)
- 必须拦截并转换十六进制格式(如 0x7f000001 -> 127.0.0.1)
- 兼顾 IPv6 格式标准化(如 ::1)
- 彻底扩展黑名单的广度
单防 127.0.0.1 是不够的,你需要拦截整个网段:- 所有 loopback 地址(127.0.0.0/8, ::1)
- 所有 RFC1918 私有地址(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- link-local 地址(169.254.0.0/16, fe80::/10)
- 特殊地址如 0.0.0.0 及组播地址 224.0.0.0/4
- 引入 DNS 解析后验证(防重绑定)
如果输入的是域名,必须在后端发起请求前对 relay_server 进行真实 DNS 解析,并验证解析后的真实 IP 是否在黑名单内,以防 DNS Rebinding 攻击。