SSRF漏洞的可怕之处不在于它本身能直接拿到服务器权限,而在于它像一把万能钥匙,能让攻击者绕过防火墙,直接窥探并攻击你内网里那些从不对外开放的敏感服务。很多人修复SSRF时只想着过滤掉localhost和127.0.0.1,这种思路在实战中几乎等于没修,攻击者绕过的方法多得超乎想象。
SSRF内网探测的底层逻辑与常见目标SSRF攻击在内网探测阶段的本质是利用存在漏洞的服务器作为跳板,向内部网络发起请求。攻击者提交一个URL参数,服务器端应用程序接收后发起请求,由于这个请求是从服务器内部发出的,天然具备访问内网资源的权限。攻击者最常探测的目标包括内网元数据服务、数据库服务、缓存系统以及各类中间件管理后台。
云环境下的元数据服务是SSRF攻击的首要目标。在云服务器上,169.254.169.254这个地址提供了实例的元数据信息,包括临时访问凭证、用户数据脚本等极度敏感的内容。攻击者一旦通过SSRF读取到这个地址返回的信息,往往能直接获取到云账号的控制权。内网中未授权访问的Redis、Memcached、MySQL、MongoDB等服务也是重点攻击对象,这些服务通常默认无密码或弱密码,通过SSRF可以执行写操作、获取数据甚至写入计划任务反弹Shell。
SSRF绕过黑名单过滤的实战手法剖析很多开发者认为只要过滤掉内网IP段就能防御SSRF,这种认知是极其危险的。攻击者绕过IP黑名单的手法非常成熟且多样。利用URL解析差异是最常见的绕过方式,不同编程语言的URL解析库在处理特殊格式时存在差异,攻击者可以利用这些差异构造看似合法实则指向内网的URL。
短网址跳转服务是另一个高频利用点,攻击者将内网地址转换为短链接,由于短链接域名本身在白名单内,后端请求时会跟随跳转最终到达内网目标。DNS重绑定攻击则更为隐蔽,攻击者注册一个域名,第一次DNS解析返回正常外网IP通过验证,第二次解析返回内网IP,由于服务器端请求时进行了两次DNS查询,时间差使得攻击得以成功。利用IPv6地址表示法也能绕过很多只过滤了IPv4地址的防护,比如将127.0.0.1表示为[::ffff:127.0.0.1]或直接使用[::1]。
十进制、八进制、十六进制IP表示法的转换绕过同样有效。127.0.0.1可以表示为2130706433(十进制)、0177.0.0.1(八进制)、0x7f.0.0.1(十六进制)。使用URL编码对IP地址中的点号或特殊字符进行编码,或者利用302跳转从外网合法地址跳转到内网地址,都是实战中频繁出现的绕过手段。更高级的攻击者会利用URL Scheme的差异,比如使用file://、gopher://、dict://等协议读取本地文件或攻击内网服务。
白名单机制的正确落地方式防御SSRF最有效的手段不是黑名单,而是严格的白名单机制。白名单的核心原则是只允许请求访问明确指定的目标地址,其他一律拒绝。实现白名单的第一步是梳理业务需求,明确你的应用到底需要请求哪些外部资源,把这些资源的域名或IP段逐一列出。
白名单的校验必须放在DNS解析之后进行,这是很多人容易忽略的关键点。如果先做白名单校验再发起DNS解析,攻击者可以通过DNS重绑定绕过。正确的做法是先进行DNS解析得到真实的IP地址,然后拿这个IP地址去和白名单做匹配,匹配通过才允许发起请求。同时需要禁用不必要的URL协议,只允许http和https协议,禁止file、gopher、dict、ftp等协议的使用。
对于确实需要用户提交URL的业务场景,应该建立一套完整的请求链路控制体系。首先在代码层面强制指定请求的目标端口,只允许80和443等Web服务端口,禁止访问Redis的6379、MySQL的3306、Memcached的11211等常见内网服务端口。其次要禁止跟随跳转,或者严格限制跳转的目标地址同样必须在白名单内。很多HTTP客户端库默认会自动跟随302跳转,这个特性在生产环境中应该显式关闭。
代码层面的多层纵深防御实现在实际开发中,单靠一层校验远远不够,需要构建多道防线。以Java语言为例,使用HttpURLConnection或Apache HttpClient时,需要自定义连接工厂和重定向策略。下面是一个核心的防御实现思路:
// DNS解析后IP白名单校验核心逻辑
private boolean isIpInWhitelist(String hostname) {
try {
InetAddress address = InetAddress.getByName(hostname);
String ip = address.getHostAddress();
// 检查是否为内网IP
if (isInternalIp(ip)) {
return false;
}
// 检查是否在白名单网段内
for (String allowedCidr : whitelistCidrs) {
if (isIpInCidr(ip, allowedCidr)) {
return true;
}
}
return false;
} catch (UnknownHostException e) {
return false;
}
}
private boolean isInternalIp(String ip) {
// 严格拦截所有内网地址段
if (ip.startsWith("10.") || ip.startsWith("172.16.")
|| ip.startsWith("192.168.") || ip.equals("127.0.0.1")
|| ip.equals("0.0.0.0") || ip.startsWith("169.254.")) {
return true;
}
return false;
}
这段代码的核心思想是在DNS解析完成后,先判断目标IP是否为内网地址,如果是则直接拒绝,然后再与白名单网段进行匹配。需要注意的是,判断内网IP时要覆盖10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.0/8、169.254.0.0/16等所有RFC 1918定义的私有地址段,不能遗漏任何一个。
在Python开发中,使用requests库时同样需要自定义适配器来控制请求行为。关键是要在HTTPAdapter中重写连接逻辑,在建立连接前对目标地址进行校验。同时设置timeout参数防止请求被恶意挂起消耗连接池资源,设置max_retries为0或者严格控制重试次数,避免被利用进行慢速攻击。
网络架构层面的隔离与监控代码层面的防护再完善,也应该配合网络架构的隔离来构建纵深防御。将发起外网请求的服务独立部署在DMZ区域,通过防火墙策略严格限制该服务器对内网的访问权限。这台服务器只应该能访问白名单内的外网地址,对内网任何地址的访问都应该在网络层就被拦截。
出口流量的监控同样关键。在出口防火墙上部署IDS/IPS规则,对请求流量进行深度包检测,识别并阻断异常的协议请求。比如正常业务只需要HTTP协议,那么所有非HTTP协议的出站请求都应该触发告警。同时建立请求日志的集中分析平台,记录每一次SSRF请求的源IP、目标URL、请求时间和响应状态,通过行为分析及时发现异常探测行为。
对于使用了云服务的架构,安全组的配置要遵循最小权限原则。只开放业务必需的出站端口和协议,禁止服务器主动访问云元数据服务的169.254.169.254地址。部分云平台支持在虚拟机层面禁用元数据服务或者启用IMDSv2来增强安全性,这些功能应该被充分利用。
绕过案例复盘与防御加固建议回顾近几年的重大安全事件,SSRF漏洞始终是攻击链中的关键一环。攻击者通过SSRF读取云元数据获取临时凭证,进而横向移动到整个云基础设施的案例屡见不鲜。这些案例的共同特点是防御方只做了浅层次的过滤,没有从架构层面进行隔离,也没有建立有效的监控体系。
防御加固应该从以下几个方面持续推进。定期对应用进行SSRF专项安全测试,使用Burp Suite的Collaborator功能或者自建DNS日志平台来检测是否存在盲SSRF。对第三方依赖和开源组件保持关注,及时修复已知的SSRF相关漏洞。建立安全编码规范,明确禁止在未校验的情况下根据用户输入构造请求URL。对运维人员来说,内网所有服务都应该开启认证机制,不能因为在内网就默认信任所有请求来源。
SSRF的防御不是一蹴而就的事情,它需要开发、运维、安全三个团队的持续协作。白名单机制是核心防线,DNS解析后校验是关键控制点,网络隔离是兜底策略,流量监控是发现手段。四者缺一不可,只有把这四道防线都做实了,才能真正把SSRF内网探测的风险降到可控范围内。
