SSRF漏洞在内网探测场景中的破坏力,源于它能让攻击者把服务器当跳板,直接穿透边界访问内部系统。要封堵这种利用方式,不能只靠修补代码,必须建立一套从请求过滤到网络隔离的纵深防御策略。核心思路是:让服务器发起的请求必须经过严格校验,同时切断任何可能通往内网的网络路径。

基于协议和地址的白名单过滤

最有效的第一道防线是在应用层实施请求目标的白名单校验。代码中所有接受用户输入URL并发起请求的功能点,必须维护一份允许访问的域名或IP清单。解析用户提供的URL后,先提取主机名和端口,与白名单比对,不匹配的直接拒绝。白名单应尽量精确,避免使用通配符过宽的规则。对于需要访问外部API的场景,只允许HTTPS协议,并固定目标域名的解析IP,防止DNS重绑定攻击绕过检查。

私有地址和保留地址的彻底阻断

即便有白名单,也要在请求发起前增加一层黑名单过滤,专门拦截内网地址段。需要封禁的范围包括: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、0.0.0.0/8,以及IPv6的链路本地地址fe80::/10和唯一本地地址fc00::/7。过滤逻辑不能只做简单的字符串匹配,必须将URL中的主机名解析为IP地址后,再与这些网段逐一比对。注意攻击者常把IP编码成十进制整数、十六进制或使用IPv6映射的IPv4地址来绕过,解析函数需要还原为标准的IP格式再判断。

DNS解析的安全控制

很多SSRF绕过手法依赖DNS层面的操控,比如将域名先解析到合法外网IP,后续请求时再返回内网IP。防御上必须限制DNS解析的行为。应用服务器使用的DNS解析器应配置为拒绝解析指向私有地址的记录,或者直接在企业DNS服务器上设置响应策略区域,对包含内网地址的应答进行过滤。代码层面,在获取到目标IP后,要再次校验该IP是否属于内网范围,这个检查必须放在实际建立连接之前。对于使用第三方DNS的情况,可考虑自建递归解析,并禁用DNS缓存中可能被污染的记录。

禁用危险协议和限制端口

SSRF攻击不仅限于HTTP,还常利用file://、dict://、gopher://、ftp://等协议读取本地文件或攻击内网服务。应用代码中发起请求的库应限制仅允许http和https协议,其他协议一概拒绝。同时,对目标端口做严格限制,默认只允许80和443,如果业务需要访问特殊端口,必须逐一审批并记录在配置中。像22、3306、6379、11211、27017这类常被利用的端口,要在请求过滤层直接阻断,不留给后续环节判断。

网络层的出站流量隔离

应用层面的过滤可能被绕过,因此网络层的隔离是兜底策略。将Web服务器所在网段划入独立的DMZ区域,通过防火墙策略严格限制从该区域发起的出站连接。规则应默认禁止DMZ服务器访问任何内网IP段,只放行必要的对外服务端口。对于云环境,利用安全组或网络ACL实现同样的限制,确保即便攻击者成功触发了SSRF,请求也无法到达内网资源。同时,服务器上应关闭不必要的网络功能,比如禁止内核IP转发,防止被用作跳板转发流量。

请求特征和频率的实时检测

在过滤和隔离之外,还需要建立检测机制发现正在进行的探测行为。监控应用日志中所有包含内网地址、非常规端口、异常协议的请求,对短时间内大量请求不同内网IP或端口的行为触发告警。可以设置规则,当某个源IP在单位时间内发起的SSRF类请求超过阈值时,临时将其列入黑名单。对于请求的目标地址,如果出现连续端口扫描的特征,应立即阻断并通知安全团队。这些检测规则需要结合业务正常请求的基线来调优,减少误报。

代码实现示例

下面是一段在Python中实现URL校验的参考代码,演示了如何结合白名单、协议限制和内网IP过滤:

import socket
import ipaddress
from urllib.parse import urlparse

ALLOWED_DOMAINS = {'api.example.com', 'cdn.example.net'}
BLOCKED_SUBNETS = [
    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('127.0.0.0/8'),
    ipaddress.ip_network('169.254.0.0/16'),
    ipaddress.ip_network('0.0.0.0/8'),
]

def is_internal_ip(ip_str):
    try:
        ip = ipaddress.ip_address(ip_str)
    except ValueError:
        return True
    for subnet in BLOCKED_SUBNETS:
        if ip in subnet:
            return True
    return False

def validate_url(url):
    parsed = urlparse(url)
    if parsed.scheme not in ('http', 'https'):
        return False
    hostname = parsed.hostname
    if hostname not in ALLOWED_DOMAINS:
        return False
    try:
        ip = socket.gethostbyname(hostname)
    except socket.gaierror:
        return False
    if is_internal_ip(ip):
        return False
    return True

这段代码先校验协议是否为HTTP或HTTPS,再检查域名是否在白名单内,最后解析IP并判断是否属于内网地址。实际部署时,DNS解析和IP校验之间要避免时间差带来的竞态条件,最好使用支持异步解析的库并设置合理的超时。

重定向和跳转的跟随控制

攻击者经常利用服务器端请求跟随重定向的特性,先让请求指向一个合法的外部URL,该URL返回302跳转到内网地址。因此,HTTP客户端库必须禁用自动跟随重定向,或者自定义重定向逻辑,对每一次跳转的目标重新执行完整的安全校验。如果必须跟随重定向,需要限制最大跳转次数,并确保每次跳转都经过白名单和IP过滤。另外,响应体中的任何URL都不应被直接用于发起新的请求,除非经过同样的校验流程。

凭证和元数据服务的保护

云环境中,实例元数据服务是SSRF攻击的高价值目标。攻击者通过访问169.254.169.254可以获取临时凭证等敏感信息。除了在网络层禁止访问该地址外,应用服务器应关闭实例元数据服务版本1,仅使用需要令牌验证的版本2。在IMDSv2模式下,任何请求元数据服务都必须先获取一个PUT请求返回的令牌,这能有效阻止简单SSRF的直接读取。同时,分配给服务器的IAM角色应遵循最小权限原则,即使凭证泄露也无法造成过大影响。

日志记录与回溯

所有被拒绝的SSRF请求都应记录详细日志,包括时间、来源IP、请求的URL、解析后的目标IP和端口、匹配的阻断规则。这些日志不仅用于攻击溯源,也能帮助发现业务中可能存在的正常但被误拦的请求,便于调整白名单。日志格式要结构化,便于后续通过SIEM系统聚合分析。对于通过校验的合法请求,也可以选择性记录目标地址,作为基线与异常检测的输入。

纵深防御的持续维护

SSRF防御不是一次性配置就能高枕无忧。新的绕过手法不断出现,比如利用IP地址的不同字符串表示、DNS重绑定变种、HTTP请求走私等。安全团队需要持续跟踪相关漏洞情报,定期对现有防护措施进行穿透测试。白名单和黑名单规则应纳入配置管理系统,变更时走审批流程。每季度至少进行一次全面审查,移除不再需要的规则,添加新发现的危险地址段。同时,推动开发团队在需求阶段就考虑SSRF风险,避免在设计上留下必须从服务器端访问不可信URL的功能。