ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

SSRFScanner:从设计到实战,构建高效SSRF检测扫描器 接到一个授权测试目标业务方给的范围是一个文档预览服务。这类服务十有八九会把http://开头的参数直接透传给后端去抓取所以我穷举了所有可能接收 URL 的字段找出三个入口替换成内网探测用的地址结果其中一个入口直接把内网的响应内容原样吐了回来。这一趟之后我彻底意识到一个事实通用漏洞扫描器解决不了 SSRF 的检测问题。它们知道“请求发出去了”但不知道“请求从哪里回来”“请求返回的内容意味着什么”“这条链路上还能不能继续打通”。这篇文章把我在 SSRFScanner 这个项目上的设计思路、实现细节、以及实战中总结的经验完整梳理一遍写给正在做安全测试和扫描工具开发的朋友。1. SSRF 扫描器要解决的真实痛点1.1 SSRF 漏洞到底寄生在哪些场景里SSRFServer-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/8172.16.0.0/12192.168.0.0/16169.254.0.0/16云元数据地址段fc00::/7 和 fe80::/10第二层是域名语义规则。探测向量库里内置了一批解析结果指向内网地址的测试域名请求引擎会把它们解析出的 IP 和响应结果都记录在案。第三层是响应指纹规则。这一步最接近人工判断拿到响应体后判断是否包含内网设备的特征内容比如路由器管理页面标题、打印机状态页、数据库管理面板等。如果一看到H3C、Cisco、MikroTik这类指纹基本可以断定请求已经到达内网设备了。3.2 容易被忽略的 URL 解析差异服务端拿 URL 参数后通常会做一次合法性校验。很多开发者以为把127.0.0.1和localhost过滤掉就安全了实际上 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?toxxx可以跳转到任意外部地址。如果这个跳转被某些内部服务当作合法跳转来跟随攻击者就能用这个跳转作为 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.1和localhost。实际上我统计过 SSRFScanner 跑出的有效内网地址里10.x、172.x、192.168.x这些内网段的占比远高于本机回环地址云元数据地址也不算罕见。开发者做 URL 合法性校验时至少应该做到 IP 段级别过滤而不是只过滤两个最常见的名称。7.2 先解析再判断而不是先判断再解析正确的防护顺序应该是拿到来访 URL 后先通过 DNS 解析出真正的 A 记录拿到解析后的 IP 再做过滤判断。只看 URL 字符串、然后做黑名单匹配的做法遇到重定向、DNS 重绑定、IP 变体格式时基本形同虚设。7.3 协议白名单比黑名单好用从扫描器的角度回看协议白名单是性价比最高的防护手段。只允许http://和https://禁止跳转到其他协议限制重定向的次数对内部服务域名做单独隔离。这套策略虽然不能覆盖所有场景但能把绝大多数自动化探测挡在门外。最后说一点个人体会写 SSRFScanner 的过程中最大的收获不是代码本身而是对“请求生命周期”的理解——一个 URL 参数从进入业务系统到最终发起网络请求中间每一步都可能被误判、被绕过、被拦截。把这个流程想透了做扫描器和做防护其实是同一件事。建议你在自己的测试项目里先把一个手工 SSRF case 从入口到回显完整走一遍再回头来看这个工具的配置和报告会顺手很多。
返回列表