跳转到帖子

SSRF(Server-Side Request Forgery,服务端请求伪造)并不是“发现一个可控 URL”这么简单。真正有价值的分析,需要回答三个问题:请求是否真的由服务端发起、请求能否到达不应暴露的网络区域、攻击者能否读取或影响响应结果。本文以代码审计和安全验证为主线,整理一套适用于 Web 服务、Webhook、图片抓取和文档解析功能的 SSRF 分析方法。

一、典型场景与风险边界

常见的 SSRF 入口包括远程图片下载、URL 预览、Webhook 回调、PDF 或网页转码、第三方接口连通性检测等。开发者通常会接收一个 URL,然后由后端使用 HTTP 客户端访问该地址,最后将状态码、响应体或部分响应信息返回给用户。

风险并不只来自访问内网地址。即使目标服务无法直接读取响应,仍可能通过请求时间、状态码、错误信息或日志造成信息泄露。因此,审计时应分别评估网络可达性、响应可见性和请求副作用。

风险维度需要关注的问题
网络范围是否可以访问本机、容器网段、办公网或管理网段
协议能力是否仅允许 HTTP/HTTPS,是否错误地支持其他协议
响应处理是否把完整响应、错误信息或响应头返回给用户
请求副作用是否会触发内部服务的写操作、任务执行或状态变更
解析一致性域名解析、重定向和连接时使用的地址是否经过同一套校验

二、代码审计:从输入源追踪到网络出口

审计时不要只搜索“url”或“request”关键字,更有效的方法是先找外部输入,再沿数据流追踪到网络请求函数。常见入口包括 JSON 字段、表单参数、HTTP Header、数据库中的回调地址以及消息队列中的任务参数。

以下是一个具有代表性的危险写法:

def fetch_preview(user_url):
    response = requests.get(user_url, timeout=10)
    return response.text

这段代码的问题不只在于缺少域名限制,还包括没有限制协议、没有限制响应大小、默认跟随重定向,并且直接把完整响应返回给调用方。审计报告中应明确指出每个问题对应的影响,而不是笼统地写成“存在 SSRF”。

进一步分析时,可以建立如下调用链:

HTTP 参数 url
    -> URL 规范化
    -> 域名解析
    -> IP 访问控制
    -> HTTP 客户端连接
    -> 重定向处理
    -> 响应读取与返回

如果校验发生在错误的位置,例如只检查初始 URL 字符串,却没有检查重定向后的目标,或者校验时解析一次地址、连接时又重新解析一次地址,就可能形成校验与实际请求不一致的问题。

三、安全验证:使用本地实验环境完成闭环

验证 SSRF 时,建议使用本地构造的两个服务:一个作为业务服务,接收用户提供的 URL;另一个作为模拟内部服务,仅监听本机或测试网段。这样既能确认请求是否由服务端发起,也不会触碰真实生产系统。

模拟内部服务可以只返回固定内容:

from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = b"internal-test-service"
        self.send_response(200)
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

HTTPServer(("127.0.0.1", 18080), Handler).serve_forever()

验证时重点记录以下证据:

  • 业务服务的访问日志中是否出现测试请求。
  • 模拟内部服务是否收到连接,以及请求路径和请求头。
  • 业务接口返回的是完整响应、状态码、错误信息还是超时。
  • 请求是否经过代理、DNS 服务或其他中间组件。
  • 重定向、超时和大响应场景下,服务是否仍然稳定。

不要把“本机地址可访问”直接等同于高危。风险等级还取决于服务进程权限、网络分段、响应可见性以及目标接口是否具备写操作。报告应将可验证事实与推测性影响分开描述。

四、URL 校验中最容易遗漏的细节

SSRF 防护的难点在于 URL 是一个结构化对象,不能只用字符串前缀判断。例如,检查 URL 是否以某个域名开头,无法可靠区分真正的主机名和只是出现在用户名、路径或查询参数中的字符串。

建议至少执行以下步骤:

  • 只允许业务确实需要的协议,通常为 HTTP 和 HTTPS。
  • 解析 URL 后单独获取主机名、端口和路径,不使用字符串包含判断。
  • 拒绝用户名、密码和不必要的非标准端口。
  • 对主机名进行规范化,处理大小写、末尾点和国际化域名等情况。
  • 解析所有 A 和 AAAA 记录,并对每个结果执行地址范围检查。
  • 明确拒绝回环地址、链路本地地址、私有地址、组播地址、未指定地址及企业内部保留网段。
  • 禁用或严格控制自动重定向,并对每一次重定向重新执行完整校验。
  • 限制响应体大小、连接时间、读取时间和总请求时间。

IPv4 和 IPv6 必须同时考虑。很多实现只判断常见的 IPv4 私网段,却遗漏 IPv6 回环地址或链路本地地址。还要注意 DNS 解析结果可能有多个地址,不能只检查第一个结果。

五、一个更稳妥的防护实现思路

下面示例展示防护逻辑的结构,重点是分层校验,不代表可以直接替代经过充分测试的生产级 HTTP 客户端。

from urllib.parse import urlparse
import ipaddress
import socket

ALLOWED_SCHEMES = {"http", "https"}

class UnsafeTarget(Exception):
    pass

def validate_target(raw_url):
    parsed = urlparse(raw_url)

    if parsed.scheme.lower() not in ALLOWED_SCHEMES:
        raise UnsafeTarget("unsupported scheme")
    if not parsed.hostname or parsed.username or parsed.password:
        raise UnsafeTarget("invalid host information")
    if parsed.port not in (None, 80, 443):
        raise UnsafeTarget("unsupported port")

    host = parsed.hostname.rstrip(".").lower()
    addresses = socket.getaddrinfo(host, parsed.port or 443, type=socket.SOCK_STREAM)
    if not addresses:
        raise UnsafeTarget("no address")

    for item in addresses:
        ip = ipaddress.ip_address(item[4][0])
        if (ip.is_private or ip.is_loopback or ip.is_link_local
                or ip.is_multicast or ip.is_unspecified):
            raise UnsafeTarget("blocked address")

    return parsed

实际实现还应避免解析与连接之间的地址漂移问题。较稳妥的方式是由受控的网络层或代理统一执行出口策略,并在连接阶段绑定经过校验的地址;如果使用域名直接交给底层客户端,应确认底层解析、连接和重定向行为符合预期。

六、重定向、DNS 与代理带来的差异

重定向是审计中经常被忽略的路径。初始地址可能属于允许访问的站点,但响应中的 Location 可能指向另一个网络区域。因此,禁用自动重定向通常是更容易审计的选择;如果业务必须支持重定向,则每一跳都应重新解析、校验并限制最大跳数。

DNS 相关问题也不能只靠一次解析解决。域名可能返回多个地址,解析结果可能变化,代理还可能在另一侧重新解析域名。防护策略应明确请求是由应用直连还是由出站代理代发,并在网络层设置默认拒绝的出口规则,避免仅依赖应用代码。

代码校验解决“应用认为目标是否可信”,网络出口策略解决“即使代码出现遗漏,服务实际上还能访问什么”。两者应同时存在。

七、响应处理和资源消耗

即使目标地址校验正确,远程抓取功能仍可能被滥用于资源消耗。常见问题包括无限读取响应、压缩包或转码任务过大、连接池耗尽,以及对慢速响应长期等待。

  • 设置连接超时、读取超时和总超时,不要只设置一个超时参数。
  • 使用流式读取,并限制最大响应字节数。
  • 限制并发任务数量,对同一用户或同一目标做速率控制。
  • 禁止自动解压不可信内容,或对解压后的文件数量和总大小设上限。
  • 不要把远端响应头原样转发给浏览器,避免引入缓存、跳转和内容类型问题。
  • 日志中记录目标域名、解析结果、最终状态、耗时和拒绝原因,但避免记录敏感响应正文。

八、审计报告应该怎样落地

一份可执行的 SSRF 报告至少应包含:受影响接口、输入参数、调用链、验证环境、请求是否成功到达测试服务、响应是否可见、可访问的网络范围、影响分析和修复建议。

修复建议不要只写“增加 URL 白名单”。应根据业务模式给出具体方案:固定第三方服务时使用精确域名白名单;只需要回调时采用预注册的目标标识而不是直接接受完整 URL;必须抓取任意公网资源时,使用独立出站代理、默认拒绝的网络策略和严格的资源限制。

九、总结

SSRF 分析的核心不是寻找某个神奇的绕过写法,而是建立从输入、解析、DNS、连接、重定向到响应处理的完整链路。审计时应同时关注协议、地址范围、解析一致性、网络出口和资源消耗;验证时使用本地模拟服务保留可复现证据;修复时采用应用层校验与网络层隔离的纵深防御。

对于长期维护的系统,建议将 SSRF 防护纳入代码评审清单和回归测试,并对 HTTP 客户端、代理配置及依赖库升级进行专项复核。这样才能避免一次修复后,因新增抓取功能或更换网络组件再次引入同类问题。

服务器与云环境安全示意图
服务器、云资产与权限边界梳理示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…