news 2026/9/14 5:21:41

SSRFScanner:从设计到实战,构建高效SSRF检测扫描器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSRFScanner:从设计到实战,构建高效SSRF检测扫描器

接到一个授权测试目标,业务方给的范围是一个文档预览服务。这类服务十有八九会把http://开头的参数直接透传给后端去抓取,所以我穷举了所有可能接收 URL 的字段,找出三个入口,替换成内网探测用的地址,结果其中一个入口直接把内网的响应内容原样吐了回来。

这一趟之后我彻底意识到一个事实:通用漏洞扫描器解决不了 SSRF 的检测问题。它们知道“请求发出去了”,但不知道“请求从哪里回来”“请求返回的内容意味着什么”“这条链路上还能不能继续打通”。这篇文章把我在 SSRFScanner 这个项目上的设计思路、实现细节、以及实战中总结的经验完整梳理一遍,写给正在做安全测试和扫描工具开发的朋友。

1. SSRF 扫描器要解决的真实痛点

1.1 SSRF 漏洞到底寄生在哪些场景里

SSRF(Server-Side Request Forgery,服务器端请求伪造)检测难,首先难在它不是一个“输入框加后端 SQL 拼接”式的漏洞。只要业务逻辑里存在“由服务器去请求一个由用户控制的地址”的动作,就可能存在 SSRF。

我在实际项目里见过的高频寄主场景有这么几类:社交软件里的 URL 预览、链接卡片;在线文档或图片处理工具的“导入 URL”功能;Webhook 配置面板;PDF 生成器(把网页转成 PDF 的服务);还有各类代理抓取型 API。

这类场景在自动化扫描器眼里往往表现为一个正常的 200 响应,没有明显错误信息,很难判断后端是否真正发起了网络请求。这也是为什么很多通用扫描器对 SSRF 基本无能为力——不是引擎不行,而是检测模型从一开始就不是为“请求方在服务端”设计的。

1.2 通用扫描器在 SSRF 检测上的三个盲区

用通用扫描器扫 SSRF,通常会遇到三个绕不开的盲区:

盲区一是不验证带外通道。很多扫描器只拿到一个“响应成功或失败”的状态就完事了,根本不会去验证一个域名是否真的被服务端请求过。SSRF 的核心证据恰恰在请求链路本身,而不在响应码里。

盲区二是不跟踪跳转链路。开放重定向、302 跳转、多级转发,这类场景在 SSRF 里太常见了,但通用扫描器往往是单请求单响应模型,跳转链路一深就跟丢了。

盲区三是不进行多步闭环验证。就算扫描器确认服务端能请求某个地址,它也不会继续判断“这个地址是不是内网 IP”“内网里跑的是什么服务”“这个服务是否存在可被利用的高危组件”。而这恰恰是客户最关心的。

我给两张工作模式做过一个直观对比,放在下面:

检测维度通用扫描器SSRFScanner
请求是否真正发出看状态码猜结合带外响应与响应体判断
能否访问内网基本不验证内置多规则内网地址探测
URL 跳转链跟随有限次数就断完整记录跳转链并标记目标属性
攻击链闭环自动探测端口、识别服务指纹
结果可信度需要人工二次确认输出证据链,可直接进报告

通用扫描器适合挖“一锤子买卖”的漏洞,比如 SQL 注入、XSS,但 SSRF 是一个需要多步验证的漏洞类型,工具的逻辑必须跟着这个特点走。

2. SSRFScanner 的模块拆解与部署

2.1 六大核心模块的职责划分

SSRFScanner 整体是模块化设计的,这样做的原因很实际:SSRF 检测链路太长,如果所有逻辑混在一个文件里,后面加一个新协议或新指纹类型,就得动主流程,容易改崩。拆成模块之后,每一段职责清晰,测完一块再动下一块。

第一块是目标归集模块。它负责从目标列表、爬虫结果或 Burp 导出的数据里,把可能接收 URL 的参数位全部提取出来。这个模块我做了两层清洗:先把明显不是 URL 参数的字段过滤掉,再把重复参数去重,免得后面请求引擎做重复功。

第二块是请求引擎。基于 asyncio 实现的异步请求层,负责实际发包、超时控制、重试、跟随跳转,以及记录每条请求的完整链路信息。

第三块是探测向量库。这是检测能力的核心资产,里面存的是所有绕过思路和探测 payload,包括内网 IP 变体、短格式 IP、八进制十六进制写法、协议切换、关键云元数据地址等等。

第四块是响应分析模块。拿到响应之后,不只是看状态码,还要做内容判断:响应体里有没有内网服务指纹、有没有云元数据特征、有没有重定向到内网地址的 Location 头。

第五块是攻击链验证模块。发现某个入口能访问内网后,自动对目标内网 IP 做轻量端口探测和服务指纹识别,给出“这条链路的危害程度”判断。

第六块是报告输出模块。所有结果按证据链组织输出,每一条结论都附带请求包、响应包、跳转记录和对应的时间线。

2.2 环境部署与参数配置

部署过程非常简单,依赖基本都在 Python 3.9+ 生态里。

pip install -r requirements.txt python ssrfscanner.py -f targets.txt -o report.html --verify

配置文件用 YAML 格式,几个关键参数的含义和调优经验我直接写在注释里:

http: timeout: 8 # 单请求初始超时,8秒是折中值 retries: 2 # 连接失败后的重试次数 max_redirects: 10 # 最大跟随跳转次数 user_agent: "Mozilla/5.0 ..." avoid_blocks: max_qps: 20 # 每host每秒最大请求数 random_delay_range: [0.5, 1.5] # 请求间隔随机化,防触发WAF sleep_after_detect: 3 # 发现内网服务后自动暂停3秒 callback: provider: "interactsh" # 带外回调通道 token: ""

这里特别说一下max_redirects。SSRF 检测里跳转链接太长的情况不多,但一旦发生,基本都是多层代理转发或者 SSO 统一登录逻辑。设置 10 次足够覆盖绝大多数业务场景,又不会让请求无限追下去。

2.3 为什么选 Python 异步引擎

有人问过我为什么不用 Go 写扫描器。Go 并发确实强,但 SSRF 检测的高频依赖是数据分析和协议处理,Python 生态里现成的请求库、DNS 解析库、指纹库最丰富,做原型迭代最快。asyncio 加 aiohttp 处理几千个并发探测请求完全够用。

实际开发中最需要注意的反而是一个看起来不起眼的问题:异步下的超时处理一定要细粒度。DNS 解析超时要单独设,TCP 连接超时要单独设,读取响应体超时也要单独设。我最初把所有超时统一成一个值,结果在目标服务响应很慢但连接很快的场景下,大量请求被误判为超时,漏报率明显偏高。

3. 内网地址探测:规则、绕过与判定逻辑

3.1 内网地址判定的多级规则

内网地址探测定不能做成“解析一下 IP,匹配一下是不是内网段”这么简单。因为服务端发起请求的目标地址可能是个域名,域名背后可能是 CDN、可能是内网解析、也可能是 DNS 重绑定,IP 本身还能做各种格式变体。SSRFScanner 的内网地址判定分了三层:

第一层是纯 IP 规则。目标域名解析出来的所有 IP 逐项比对,基础规则包括 IPv4 私网段和 IPv6 的特殊地址段:

  • 127.0.0.0/8 以及 ::1(本机回环)
  • 10.0.0.0/8
  • 172.16.0.0/12
  • 192.168.0.0/16
  • 169.254.0.0/16(云元数据地址段)
  • fc00::/7 和 fe80::/10

第二层是域名语义规则。探测向量库里内置了一批解析结果指向内网地址的测试域名,请求引擎会把它们解析出的 IP 和响应结果都记录在案。

第三层是响应指纹规则。这一步最接近人工判断:拿到响应体后,判断是否包含内网设备的特征内容,比如路由器管理页面标题、打印机状态页、数据库管理面板等。如果一看到H3CCiscoMikroTik这类指纹,基本可以断定请求已经到达内网设备了。

3.2 容易被忽略的 URL 解析差异

服务端拿 URL 参数后通常会做一次合法性校验。很多开发者以为把127.0.0.1localhost过滤掉就安全了,实际上 IP 地址在 URL 里的写法远不止一种。

我举几个实际用到的格式变体:

原始地址变体写法说明
127.0.0.12130706433十进制整数格式
127.0.0.10177.0.0.1八进制表示
127.0.0.10x7f.0.0.1十六进制表示
127.0.0.1127.1短格式,省略后两段
127.0.0.1%31%32%37.0.0.1URL 编码绕过

扫描器在做内网地址探测时,必须覆盖这些变体。实现思路很简单:拿到目标地址后,先把各种格式统一规范成十进制 IP,再走内网规则匹配。

import ipaddress def normalize_host(raw): # 先尝试按普通IP解析 try: return str(ipaddress.ip_address(raw)) except ValueError: pass # 尝试十进制整数格式 try: packed = int(raw).to_bytes(4, 'big') return str(ipaddress.ip_address(packed)) except Exception: pass # 其他变体格式,交给更完整的解析库继续处理 return raw

这类解析逻辑的价值在于:它把人工测试时积累的“经验型绕过”固化成了自动化规则。工具跑一轮,相当于一个熟悉各种绕过手法的测试人员把目标入口全部试了一遍。

3.3 云元数据地址的专项验证

169.254.169.254 这段地址在 SSRF 检测里是一个必须单独处理的项目。很多企业应用跑在云上,SSRF 一旦能访问到云元数据服务,攻击面会直接放大到云平台凭证层面。

SSRFScanner 里有一个单独的云元数据检测模块,会对这个地址发起有限数量的探测请求,并从响应内容判断是否命中元数据 API 特征。比如 AWS 的元数据服务响应里会出现ami-id这类字段名,不同厂商有自己的特征。同时,响应分析模块对这类内容做了自动脱敏处理,防止敏感信息原样写入报告。

这里要强调一点:云元数据检测请求的数量和频率需要严格控制,因为这类探测一旦打到厂商的元数据服务,特殊请求模式可能被平台侧风控注意到。默认配置是每个入口只对元数据地址发 2 个验证请求,拿到特征字段就停止。

4. URL 跳转检测:从开放重定向到 SSRF 的桥接

4.1 为什么 SSRF 扫描器一定要管跳转

一种很典型的场景是:目标存在一个开放重定向漏洞,redirect?to=xxx可以跳转到任意外部地址。如果这个跳转被某些内部服务当作合法跳转来跟随,攻击者就能用这个跳转作为 SSRF 的中转站。

而且实际业务中,SSRF 的入口往往不是一个直接传 URL 的参数,而是“先跳转再请求”的多步链路。比如参数 A 跳转到参数 B,参数 B 里的地址才是真正被服务端请求的目标。如果扫描器只检测单跳,这类链路会全部漏掉。

4.2 跳转链的完整记录方式

SSRFScanner 的请求引擎默认跟随跳转,但记录的是完整链路而不是最终状态。链路里每一个节点的响应码、Location 头、解析后的 IP、IP 是否命中内网规则,全部打包成一条记录。

这样做的好处是排障快。之前碰到过一个案例,报告显示某个参数可以跳转到内网地址,但实际手动复现时怎么都不成功。查链路记录才发现中间节点有一个外网 CDN 的 302 跳转,CDN 节点解析出的 IP 里碰巧出现了内网地址。把这个中间节点标记出来,问题定位就很清楚了。

4.3 跳转检测的三种策略

根据测试场景不同,跳转检测有三种可选策略:

策略 A 是不跟随跳转,只记录 Location 头内容。适合那种只需快速确认是否存在开放重定向的场合,速度最快,但没法确认跳转后的资源是否真的被请求。

策略 B 是跟随三次以内。适合大多数常规目标,既能确认跳转目的地,又不会陷入过深的链路。

策略 C 是智能跟随,对开放重定向参数的跳转结果做二次探测。比如发现redirect参数跳转到了外部地址,再对那个外部地址做一次内网探测,判断是否形成了可利用的跳板链。

实际扫描时,我通常把策略 C 作为验证阶段的手段,而不是初扫阶段就开启,否则请求量会成倍增加。

4.4 跳转误报控制经验

跳转类的误报通常来自两个地方。一是外网 CDN 加速节点跳转后 IP 解析成内网地址——某些 CDN 服务商在做回源时有内网转发,但这属于服务商内部行为,跟目标业务没有直接关系;二是统一登录系统的跳转,目标是一个集中认证平台,跳到哪里都不稀奇。

处理方式是在判断规则里加上下文:维护一份“可信域名/可信 CDN 段”的名单;跳转后的响应内容必须出现内网服务指纹或与目标业务相关的特征;同时检查跳转后的 Location 是否命中内网 IP 规则。三条件同时满足才判定为有效跳转,能压掉很大一部分假阳性。

5. 攻击链验证:从“可能”到“确认”

5.1 为什么普通扫描报告没有说服力

“该参数疑似存在 SSRF”是安全测试项目里最没说服力的一句话。客户看到这句话后的第一反应一定是:你说能访问内网,然后呢?能拿到数据吗?能进一步控制服务器吗?

所以 SSRFScanner 要把验证往前推一步:发现某个入口可以访问内网时,自动对可访问的内网 IP 做轻量端口探测,识别常见的数据库、缓存、管理面板等服务指纹,判断是否存在可以直接利用的高危组件。

需要明确的是,工具只做“指纹识别 + 利用条件判断”,不主动执行破坏性利用动作。这个边界在测试场景里很重要——授权测试的目的是证明漏洞存在和影响范围,而不是替攻击者把路走完。

5.2 攻击链验证的三层模型

整个验证流程按三层递进:

层级验证目标典型证据
第一层入口是否真正发起到内网的请求带外响应、响应体回显
第二层内网可达性的具体范围端口开放情况、服务指纹
第三层目标服务是否存在已知可利用条件组件版本、未授权访问特征

第一层解决“有没有”的问题,第二层解决“能到哪里”的问题,第三层解决“危害有多大”的问题。三层都过了,报告里就能理直气壮地写“确认存在 SSRF 导致的严重风险”。

5.3 一个实际验证流程的复盘

用一个小例子展示完整流程。某个在线文档导入功能,参数是document_url。SSRFScanner 初扫时发现该参数可以访问一个内网地址,响应体里出现了特定设备的 HTTP 响应头。

攻击链验证模块随后对这个内网 IP 的常见端口做了轻量扫描,发现 8080 端口开放,返回内容里的 favicon hash 和登录页标题指向一个协作平台。工具把这三层证据组织成一条链路:

document_url 参数可请求任意地址 └─> 内网 IP 可达(响应头特征确认) └─> 8080 端口开放,指纹识别为协作平台 └─> 登录页特征匹配,存在已知高危版本特征

这一步没有执行任何攻击行为,但报告已经可以明确写出风险等级和影响面。测试人员拿到这份证据后,再手动做最终确认和利用验证,整个过程高效多了。

我之前用 CTFHub 技能树的 SSRF 关卡做过回归测试。通过在线靶场跑一遍全量检测流程,能快速验证工具的检测覆盖率有没有在迭代中回退。我的做法是每更新一次探测向量库或响应指纹库,就把靶场里的 SSRF 相关题目重跑一遍,逐题对照结果,保证改动不影响已有检测能力。

6. 实战踩坑与调优记录

6.1 超时设置不是越大越好

SSRF 检测里,超时设置直接决定漏报率和扫描时长这两个维度。如果目标服务处理请求很慢,超时太短会漏报;但如果统一设成很大,目标几千个参数并发扫下来,时间成本会高到没法接受。

我目前的经验值是:初始扫描超时设 6 秒,在确认存在内网地址过滤或目标访问较慢的场景下,单独拉长到 12 秒重测一轮。第一轮保证覆盖率,第二轮保证准确性,两轮加起来的时间也远小于把所有请求全部用长超时跑一遍的时间。

6.2 别把目标站点打到熔断

用扫描器最忌讳的就是拿最大并发量对着目标狂轰一气。SSRF 探测请求比普通漏洞扫描更容易触发 WAF 和限流,因为内网探测请求在业务日志里看起来极其可疑——一个来自外网的会话,反复请求各种内网地址和特殊格式 IP。

SSRFScanner 内置了 per-host 的 QPS 限制,默认 20 QPS,单个参数默认间隔 0.8 秒,一旦检测到内网服务响应,自动暂停后续探测 3 秒。这个机制是在一次真实测试里被教育出来的:当时没加限速,目标网关的 WAF 直接把整个业务 IP 封了半小时,测试从技术问题变成了事故。

6.3 DNS 解析结果要分层缓存

这个坑我踩得比较深。扫描刚开始时,把目标域名解析成 IP 缓存起来直接复用,能大幅度提升速度。但 SSRF 检测里,域名解析结果的变化恰恰是判断 DNS 重绑定和 CDN 调度的关键信号。

如果所有域名都走同一个长缓存,DNS 重绑定场景根本测不出来;如果所有域名都不缓存,几千个请求全走完整 DNS 解析,性能又回不去。折中方案是把 DNS 缓存分两层:业务域名走长期缓存,探测专用的特殊域名走短期缓存、逐次解析。这样既保证性能,又不会错过解析层的变化。

6.4 在云上部署扫描器的误报处理

很多人会在云服务器上跑扫描器,这时候很容易发现扫出来的结果一片红。原因在于云厂商网络里,历史遗留的“脏 IP”会被广播到同一段,某些现象会导致 DNS 解析结果命中云厂商的内网保留段,产生大量误报。

处理方式是在配置里加入“云厂商元数据地址黑名单”,结合 DNS 解析结果做二次确认:只有命中内网 IP 规则、同时响应体出现明确内网服务指纹的才判定为有效结果。单靠 IP 段匹配,云上环境跑出来的报告根本没法看。

7. 用久了之后,我对服务端 SSRF 防护的几个观察

7.1 只封 127.0.0.1 等于没封

服务端最常见的错误是只过滤了127.0.0.1localhost。实际上,我统计过 SSRFScanner 跑出的有效内网地址里,10.x172.x192.168.x这些内网段的占比远高于本机回环地址,云元数据地址也不算罕见。开发者做 URL 合法性校验时,至少应该做到 IP 段级别过滤,而不是只过滤两个最常见的名称。

7.2 先解析再判断,而不是先判断再解析

正确的防护顺序应该是:拿到来访 URL 后,先通过 DNS 解析出真正的 A 记录,拿到解析后的 IP 再做过滤判断。只看 URL 字符串、然后做黑名单匹配的做法,遇到重定向、DNS 重绑定、IP 变体格式时基本形同虚设。

7.3 协议白名单比黑名单好用

从扫描器的角度回看,协议白名单是性价比最高的防护手段。只允许http://https://,禁止跳转到其他协议;限制重定向的次数;对内部服务域名做单独隔离。这套策略虽然不能覆盖所有场景,但能把绝大多数自动化探测挡在门外。

最后说一点个人体会:写 SSRFScanner 的过程中,最大的收获不是代码本身,而是对“请求生命周期”的理解——一个 URL 参数从进入业务系统到最终发起网络请求,中间每一步都可能被误判、被绕过、被拦截。把这个流程想透了,做扫描器和做防护其实是同一件事。建议你在自己的测试项目里,先把一个手工 SSRF case 从入口到回显完整走一遍,再回头来看这个工具的配置和报告,会顺手很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 5:19:34

HiFox vs Jira:AI智能体如何夺取开发工作流主权

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 5:18:47

解析ThreeUI开源组件库:Open Core模式与社区贡献实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 5:18:42

NPU加速CV任务:Ops-CV算子库优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 5:15:50

基于Spring Boot+Vue的养老院管理系统毕设设计与实现

简介:基于JavaSpringBootVueMySQL的养老院管理系统完整源码包,主要面向计算机相关专业学生的毕业设计、课程设计与期末大作业,解决养老院日常运营中的数字化管理问题,系统功能完善、操作便捷。系统覆盖老人信息管理、员工管理、财…

作者头像 李华
网站建设 2026/9/14 5:12:45

Windows文件句柄枚举:C++实现与命令行工具

简介:面向Windows中高级开发者的商业编程源码包,围绕“获取系统中打开的文件清单”这一系统级功能,提供完整编码实现与可运行演示。核心逻辑利用CreateToolhelp32Snapshot、NtQueryObject等系统API枚举进程、遍历句柄并映射到具体文件路径&am…

作者头像 李华