SSRF(Server-Side Request Forgery,服务端请求伪造)是当前网站安全中最容易被忽视却危害极大的漏洞类型之一。它的核心原理是攻击者利用服务器本身作为跳板,向内网或其他不可直接访问的目标发起请求,从而探测内网拓扑、读取敏感文件、甚至攻击内网其他服务。防护SSRF的关键在于两个层面:一是限制服务器能访问的协议和目标地址范围,二是在应用层做好输入校验和请求过滤。下面我会从原理、攻击手法、具体防护方案三个维度,把这个问题讲透。
SSRF漏洞的本质是什么
简单说,SSRF就是"借刀杀人"。正常情况下,你的Web服务器只能对外提供服务,但如果代码中存在用户可控的URL参数(比如图片加载、网页预览、文件导入等功能),攻击者就可以把这个参数改成内网地址,让服务器替他去访问内网资源。比如一个图片预览功能,用户传入的URL是https://example.com/image.jpg,但攻击者改成http://192.168.1.1/admin,服务器就会乖乖去请求内网的管理页面并把结果返回给攻击者。这就是SSRF的本质——服务器端被欺骗,替攻击者发了不该发的请求。
SSRF内网探测的常见手法
攻击者拿到SSRF漏洞后,通常会做以下几件事。第一,扫描内网端口。通过构造不同的IP和端口组合,比如http://192.168.1.1:80、http://192.168.1.1:3306、http://192.168.1.1:6379,判断哪些服务在运行。第二,读取本地敏感文件。利用file://协议读取服务器上的/etc/passwd、/etc/hosts等文件。第三,攻击内网Redis、MySQL等中间件。如果内网Redis没有密码,攻击者可以通过SSRF直接写入SSH公钥实现远程控制。第四,利用云元数据服务。在云环境中,访问http://169.254.169.254/latest/meta-data/可以获取实例的IAM凭证,这是非常高危的利用方式。
协议层面的限制是第一道防线
防护SSRF最直接有效的手段就是在协议层面做限制。很多SSRF漏洞之所以能被利用,就是因为服务器支持了不该支持的协议。你需要明确禁止或限制以下几类协议:file://协议(防止读本地文件)、dict://协议(防止利用dict服务做端口扫描)、gopher://协议(这个协议能构造任意TCP请求,危害极大)、ftp://协议(可能被用来做中转攻击)。在代码层面,可以用白名单机制只允许http和https协议。下面是一个Python的示例代码,展示如何做协议白名单校验:
import re
def validate_url(url):
# 只允许http和https协议
allowed_schemes = ['http', 'https']
parsed = re.match(r'^([a-zA-Z]+)://', url)
if not parsed:
return False
scheme = parsed.group(1).lower()
if scheme not in allowed_schemes:
return False
return True这段代码虽然简单,但在实际工程中非常实用。关键是要在所有用户输入URL的地方都加上这个校验,不能只在一个入口做。同时要注意,有些攻击者会用大小写混写(如HTTP://、HtTp://)或者用URL编码绕过,所以校验时一定要做归一化处理,把协议部分统一转成小写再判断。
IP地址和域名的黑名单与白名单策略
光限制协议还不够,还得限制目标地址。这里有两种策略:黑名单和白名单。黑名单是把已知的危险地址段列出来禁止访问,比如127.0.0.0/8(本机回环)、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16(私有地址段)、169.254.0.0/16(链路本地地址,云元数据就在这里)、0.0.0.0/8等。白名单则更严格,只允许访问预先设定好的域名列表。推荐的做法是黑名单为主、白名单为辅。下面是一个更完整的IP过滤函数示例:
import ipaddress
import re
def is_safe_ip(ip_str):
# 私有地址段黑名单
private_ranges = [
ipaddress.ip_network('127.0.0.0/8'),
ipaddress.ip_network('10.0.0.0/8'),
ipaddress.ip_network('172.16.0.0/12'),
ipaddress.ip_network('192.168.0.0/16'),
ipaddress.ip_network('169.254.0.0/16'),
ipaddress.ip_network('0.0.0.0/8'),
]
try:
ip = ipaddress.ip_address(ip_str)
for private in private_ranges:
if ip in private:
return False
return True
except ValueError:
return False
def safe_resolve(hostname):
# DNS解析后检查IP是否安全
import socket
try:
ip = socket.gethostbyname(hostname)
return is_safe_ip(ip)
except socket.gaierror:
return False这里有个关键点很多人会忽略:DNS重绑定攻击。攻击者可以把域名指向一个合法的公网IP通过白名单,然后在DNS TTL过期后把它改成内网IP。所以光在请求时检查IP不够,还要在DNS解析阶段就做限制,并且设置较短的DNS缓存时间,或者直接禁用应用层的DNS解析,改用系统级的、有防护的DNS服务。
应用层的深度防御措施
除了协议和IP层面的限制,应用层还需要做更细粒度的防护。第一,统一出口管理。所有对外请求都应该通过一个统一的HTTP客户端组件发出,这个组件内部集成协议过滤、IP黑名单、请求频率限制等逻辑,避免各个业务模块各自实现导致遗漏。第二,请求参数的严格校验。对用户传入的URL做完整的解析和校验,包括去除@符号(@后面的部分会被当作用户名密码,可能改变请求目标)、去除换行符、去除空格、限制URL长度等。第三,响应内容的过滤。即使请求发出去了,返回的内容也不应该原样透传给用户,特别是对内网服务的响应,要做脱敏处理。第四,最小权限原则。Web服务器运行的系统用户不应该有访问内网敏感服务的权限,网络层面用防火墙或安全组把Web服务器的出站流量限制到最小范围。
利用云环境元数据防护的特殊注意事项
如果你的服务部署在云服务器上,169.254.169.254这个地址必须重点防护。这是云平台的元数据服务地址,里面包含实例ID、IAM角色、临时凭证等极其敏感的信息。攻击者一旦通过SSRF拿到这些凭证,就可以操控你的云资源,后果不堪设想。防护方法包括:在服务器的iptables或安全组规则中明确禁止访问169.254.169.254;在应用层代码中把这个地址加入黑名单;如果业务不需要访问元数据服务,可以在云平台层面禁用IMDSv1,强制使用需要token验证的IMDSv2。这是目前业界公认的最佳实践。
内网探测的高级绕过手段与应对
攻击者不会坐以待毙,他们会用各种技巧绕过防护。常见的绕过手法包括:用IPv6地址绕过IPv4的黑名单(比如::1、fe80::1等);用短链接服务把恶意URL藏起来;用URL编码、Unicode编码、双重编码来混淆地址;用DNS重绑定绕过IP检查;用302跳转让服务器自己去访问恶意地址。应对这些手法,需要在防护体系中加入IPv6地址的黑名单、URL解码后再校验、禁用自动跟随重定向、限制请求的最终目标地址等措施。特别是重定向问题,很多HTTP客户端默认会跟随302跳转,这会让攻击者把你的服务器变成代理,所以一定要在HTTP客户端配置中禁用自动重定向或者对重定向目标做同样的安全检查。
监控与应急响应
防护做得再好,也不能保证百分之百不被突破。所以监控和应急响应同样重要。要对服务器的出站请求做日志记录,特别是对非预期的内网地址访问要告警。可以部署WAF规则专门检测SSRF特征,比如请求中包含内网IP、file://协议、gopher://协议等。一旦发现异常,要有快速封禁和排查的流程。定期做渗透测试,用专门的SSRF检测工具扫描自己的系统,发现问题及时修补。安全是一个持续的过程,不是一次性的工作。
总结:构建多层防御体系
SSRF防护不是靠单一手段就能解决的,需要从协议限制、IP过滤、DNS安全、应用校验、网络隔离、监控告警等多个层面构建纵深防御。核心原则就是:不信任任何用户输入的URL,不让服务器去它不该去的地方,不把内网的信息暴露出去。把这三点做到位,SSRF的风险就能降到可控范围。每个开发团队都应该把SSRF防护纳入安全开发规范,在代码审查阶段就把这类问题拦截住,而不是等到上线后被打了才补救。
