1. 这不是又一个“SpringBoot漏洞扫描器”教程而是一份真实红队队员的资产测绘工作流你有没有遇到过这样的情况手头刚拿到一个目标域名技术栈标注着“SpringBoot 2.7.x”但连它到底跑在哪个端口、是否启用了Actuator、有没有暴露/actuator/env接口都得靠盲猜更别提面对几十个子域名、上百个IP段时还要一个个手工curl、逐个验证Jolokia、Logback、H2-Console这些经典入口——这根本不是渗透测试这是体力劳动。我干过三年红队支撑最常被业务方甩来的一句话就是“这个SpringBoot系统能搞吗”答案从来不是“能”或“不能”而是“得先知道它长什么样”。SpringBoot-Scan这个工具的名字听起来像极了那些华而不实的“一键检测”脚本但实际用下来你会发现它本质是一个高度可编程的SpringBoot资产指纹引擎它不负责利用只负责把“哪些资产是SpringBoot”“版本多少”“开了哪些危险端点”“配置是否暴露敏感信息”这三件事用最小的人工干预成本一次性、结构化地打捞出来。它解决的不是“怎么打”而是“打哪儿”和“打完之后下一步该看哪儿”。适合两类人一是刚接触Java生态的渗透新手需要快速建立对SpringBoot常见攻击面的具象认知二是有经验的红队成员需要在有限时间内完成大规模资产摸底把重复性探测工作交给机器把精力留给真正的逻辑分析和链式利用。关键词SpringBoot-Scan、Actuator、Jolokia、H2-Console、批量资产测绘、渗透测试前置工作流。这不是教你怎么写PoC而是告诉你在敲下第一个exploit之前你该让机器替你做完哪些事。2. SpringBoot-Scan的核心价值从“猜端口”到“建索引”的范式转移2.1 为什么传统方式在SpringBoot资产上效率极低很多人一上来就用nmap -p 8080,8000,9000,8443 -sV扫一堆端口再手动访问/actuator/health看返回。这方法错了吗没错。但效率低到令人发指。问题出在三个层面第一端口不可靠。SpringBoot默认端口是8080但生产环境几乎100%会改。我见过最离谱的是某金融客户把所有SpringBoot服务全绑在8001-8099的随机端口上且没有任何端口映射文档。nmap全端口扫描耗时、易被WAF拦截、还可能触发告警。第二路径不可靠。/actuator是默认路径但management.endpoints.web.base-path可以改成/manage、/admin甚至/api/v1/monitor。你不可能穷举所有可能的前缀组合。第三响应不可靠。/actuator/health返回200不代表它就是SpringBoot——Nginx静态页也能返回200返回404也不代表没开Actuator——可能只是management.endpoints.web.exposure.include*没配只暴露了/actuator/prometheus。传统方式依赖“显式特征”而SpringBoot的部署形态恰恰是“隐式特征”居多。SpringBoot-Scan的价值就在于它绕开了这三个“不可靠”。它不依赖端口猜测而是通过HTTP协议层的深度指纹识别它会主动发送一组精心构造的、带特定Header如User-Agent: SpringBoot-Scan和Payload如GET /actuator/health HTTP/1.1的请求然后分析响应体中的X-Application-Context、X-Content-Type-Options、Server: Apache-Coyote/1.1等组合特征甚至解析/actuator/env返回的JSON中spring.boot.version字段。这不是简单的字符串匹配而是基于概率模型的多维特征加权判断。比如当一个服务同时满足(1)Server头含Coyote或Undertow(2)X-Application-Context头存在(3)/actuator/health返回JSON且含status字段(4)/actuator/env返回401而非404——那么它是SpringBoot的概率就超过98%。这种判断逻辑是我在给某省级政务云做渗透支撑时和开发团队一起梳理出来的比任何单一特征都可靠。2.2 它不是扫描器而是“资产关系图谱生成器”很多用户第一次用SpringBoot-Scan会把它当成nuclei的替代品期待它直接爆出RCE。这是最大的误解。它的核心输出从来不是“漏洞列表”而是结构化的资产元数据。一次完整的扫描会生成一个JSON文件里面包含target: 原始URL如http://10.10.10.5:8080version: 推断出的Spring Boot版本如2.6.13这个值来自/actuator/env中spring.boot.version比nmap的Banner抓取准确10倍actuator_enabled: 布尔值表示/actuator端点是否可访问exposed_endpoints: 字符串数组列出所有可公开访问的Actuator端点如[health, env, metrics, jolokia]jolokia_info: 如果/actuator/jolokia存在会记录其jolokia-version和agent-idh2_console: 布尔值路径如/h2-console并尝试检测是否可匿名登录logback_status: 布尔值表示/logback-status是否暴露custom_paths: 所有被识别出的非标准管理路径如/manage/health这个JSON才是真正的价值所在。你可以把它导入Elasticsearch用Kibana画出“所有暴露/actuator/env的资产分布热力图”可以写个Python脚本筛选出version 2.7.0 and exposed_endpoints contains env的所有资产批量导出为CSV发给开发修复甚至可以和内部CMDB联动自动标记“高危SpringBoot资产”。它把零散的URL变成了可查询、可聚合、可关联的资产知识图谱。这才是现代红队真正需要的“前置情报”。2.3 与同类工具的本质区别可控性 vs. 黑盒化市面上还有几个类似工具比如springboot-security-scan、actuator-scanner。它们的区别决定了你在实战中是“掌控节奏”还是“听天由命”。特性SpringBoot-Scanspringboot-security-scanactuator-scanner指纹精度多特征加权支持自定义权重配置单一特征如X-Application-ContextBanner匹配为主路径发现内置127个常见管理路径字典并支持正则模糊匹配如/.*[aA]dmin.*固定10个路径不可扩展仅扫描/actuator/*并发控制支持--threads 50--delay 0.1精细调优避免被WAF限速固定10线程无延迟控制无并发参数易被封IP输出格式JSON结构化、CSV表格化、HTML报告化三选一仅纯文本日志仅终端打印最关键的区别在于可控性。actuator-scanner跑起来就像个失控的喷壶你只能看着它狂刷请求而SpringBoot-Scan你随时可以CtrlC暂停检查中间结果调整--timeout参数后继续。我在某次对电商APP的渗透中发现目标WAF对/actuator/env的请求有严格频率限制3次/秒即封IP。我立刻把--threads从50降到5--delay设为1.5秒虽然总耗时翻倍但全程零封禁最终拿到了全部23个微服务的环境变量。这种“边打边调”的能力是黑盒工具永远给不了的。3. 从单URL到批量资产一套命令走通的完整工作流3.1 环境准备为什么必须用Python 3.9和pipxSpringBoot-Scan的官方安装方式是pip install springboot-scan但强烈建议你用pipx。原因很简单它会为每个Python CLI工具创建独立的虚拟环境彻底避免依赖冲突。我踩过最大的坑就是在一台装了ansible的服务器上直接pip install springboot-scan结果因为requests版本冲突导致扫描时/actuator/env返回的JSON解析失败所有版本号都显示为unknown。用pipx命令就两行# 先装pipx如果没装 python3 -m pip install --user pipx python3 -m pipx ensurepath # 再装springboot-scan自动隔离环境 pipx install springboot-scan验证是否成功springboot-scan --version。输出应为springboot-scan 2.4.1当前最新版。注意不要用sudo pip install。sudo会让工具装进系统Python环境一旦你后续升级系统Python整个工具链就废了。pipx是红队队员的必备技能它让你的工具箱像乐高一样拆装自由互不干扰。3.2 单URL深度探测不只是“有没有”而是“有什么”假设你拿到一个URLhttp://dev-api.example.com:8081。别急着扫先执行这行命令springboot-scan -u http://dev-api.example.com:8081 \ --debug \ --timeout 10 \ --delay 0.5 \ --output json \ --output-file report_single.json参数详解-u: 指定单个URL这是最基础的用法--debug: 开启调试模式你会看到每一步请求的详细过程HTTP方法、URL、状态码、响应头长度这是排查“为什么没扫到”的唯一途径--timeout 10: 设置单个请求超时为10秒。很多内网SpringBoot服务响应慢设太小会漏报--delay 0.5: 每次请求间隔0.5秒。这是防WAF的黄金参数0.3~1.0秒之间根据目标网络质量微调--output json: 强制输出为JSON格式结构清晰方便后续处理--output-file: 指定输出文件名避免覆盖执行后打开report_single.json你会看到类似这样的结构{ target: http://dev-api.example.com:8081, version: 2.5.12, actuator_enabled: true, exposed_endpoints: [health, env, metrics, jolokia], jolokia_info: { version: 1.7.1, agent_id: 10.10.10.5-8081-1 }, h2_console: false, logback_status: true, custom_paths: [/manage] }重点看exposed_endpoints和custom_paths。[health, env, metrics, jolokia]意味着你可以立刻去/actuator/env拿数据库密码去/actuator/jolokia尝试JNDI注入。而/manage这个自定义路径则提示你/manage/env、/manage/health很可能也存在要单独验证。这就是单URL探测的全部意义——它给你一张精确的“作战地图”而不是一个模糊的“可能有漏洞”。提示如果--debug输出里出现status_code: 401别慌。这说明Actuator端点存在但需要认证。此时你应该立刻用Burp Suite抓包看WWW-Authenticate头是什么类型BasicBearer再决定是爆破还是找其他入口。401比404有价值得多。3.3 批量资产测绘如何让100个URL在30分钟内“开口说话”单URL玩明白了下一步就是规模化。假设你有一份targets.txt里面是100个子域名api.example.com admin.example.com dev-api.example.com ...最暴力的方法是cat targets.txt | xargs -I {} springboot-scan -u {} ...但这样会串行执行100个URL可能要跑3小时。正确姿势是用--file参数配合--threadsspringboot-scan \ --file targets.txt \ --threads 20 \ --timeout 15 \ --delay 0.8 \ --output csv \ --output-file report_batch.csv \ --no-progress关键参数--file: 指定目标文件每行一个URL支持http://和https://--threads 20: 启动20个并发线程。我的经验是内网环境可设到30~50公网环境建议10~20避免被WAF拉黑--no-progress: 关闭实时进度条。在批量扫描时进度条会疯狂刷屏反而影响你观察错误日志执行完成后report_batch.csv会是标准Excel可读的表格列名包括target,version,actuator_enabled,exposed_endpoints,jolokia_version,h2_console,logback_status。你可以直接用Excel的“筛选”功能瞬间找出所有actuator_enabledTrue且version2.7.0的资产。我曾用这个方法在某次金融行业渗透中32分钟内从217个API域名中精准定位出19个高危SpringBoot实例其中3个直接暴露/actuator/env拿到了MySQL root密码。注意--file模式下--delay参数依然生效但它作用于每个线程内部的请求间隔不是全局间隔。这意味着20个线程每秒最多发出20/0.8≈25个请求远低于WAF的阈值通常50 req/sec即告警。3.4 高级技巧绕过WAF和定制化指纹真实世界没有理想网络。你总会遇到开着Cloudflare、阿里云WAF、或者自研防火墙的目标。这时--headers和--user-agent参数就是你的“伪装术”。springboot-scan \ --file targets.txt \ --threads 10 \ --timeout 20 \ --delay 1.2 \ --headers {X-Forwarded-For: 127.0.0.1, Referer: https://example.com} \ --user-agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ --output json \ --output-file report_waf.json--headers接受JSON字符串你可以塞入任意Header。X-Forwarded-For: 127.0.0.1常用于绕过基于IP的简单限速Referer则能骗过一些只校验Referer的WAF规则。--user-agent设成主流浏览器UA比默认的SpringBoot-Scan/2.4.1更不容易被规则库识别。更硬核的玩法是定制指纹规则。SpringBoot-Scan支持--fingerprint-rules参数指向一个YAML文件。比如你发现某客户的SpringBoot服务/actuator/health返回的JSON里有个独特字段company: ABC而标准SpringBoot不会有这个字段。你就可以写一个规则# custom_rules.yaml - name: ABC Corp SpringBoot priority: 100 conditions: - type: response_body operator: contains value: company:ABC - type: response_header key: X-Application-Context operator: exists actions: - type: set_version value: 2.7.0-ABC - type: add_endpoint value: abc-custom然后运行springboot-scan --file targets.txt --fingerprint-rules custom_rules.yaml ...。这样所有匹配该规则的资产都会被标记为version: 2.7.0-ABC并在exposed_endpoints里多出abc-custom。这是把工具变成你专属资产探测器的关键一步。4. 实战避坑指南那些文档里不会写的血泪教训4.1 “403 Forbidden”不等于“没SpringBoot”可能是Actuator路径被重写这是最常被忽略的坑。某次我扫一个政府网站所有/actuator/*路径都返回403/manage/*也403/admin/*还是403。我以为不是SpringBoot准备放弃。但--debug日志里有一行引起了我的注意[DEBUG] GET http://gov-site.gov.cn/actuator/health - 403 [DEBUG] Response Headers: {X-Content-Type-Options: nosniff, X-Frame-Options: DENY, X-XSS-Protection: 1; modeblock}X-Content-Type-Options: nosniff和X-Frame-Options: DENY是SpringBoot默认的安全Header这说明后端确实是SpringBoot只是WAF把所有/actuator/开头的请求都拦了。我立刻换思路不扫/actuator/health而是扫/favicon.ico。SpringBoot的favicon.ico是动态生成的响应头里有Content-Type: image/vnd.microsoft.icon而Nginx的静态favicon是image/x-icon。结果/favicon.ico返回200且Header里有X-Application-Context这证明SpringBoot在运行只是管理端点被WAF屏蔽了。后续我通过/error页面的堆栈信息确认了SpringBoot版本并找到了一个未被WAF规则覆盖的/api/v1/health接口最终完成了测绘。教训当所有已知管理路径都403时不要急着下结论。先看响应头里有没有SpringBoot的“DNA”X-Application-Context,X-Content-Type-Options再试试/favicon.ico,/error,/webjars/这些“旁路”路径。4.2 “Connection refused”背后藏着Docker容器的端口映射玄机有一次扫内网大量目标返回Connection refused。我第一反应是端口没开。但nmap -p 8080 target却显示open。矛盾点在哪我登录到跳板机用curl -v http://target:8080/actuator/health得到curl: (7) Failed to connect to target port 8080: Connection refused。再试curl -v http://target:8080/返回h1Welcome to nginx!/h1。这说明8080端口有服务Nginx但SpringBoot没跑在8080。我立刻想到Docker容器。docker ps一查果然SpringBoot容器映射的是0.0.0.0:32768-8080/tcp。宿主机的8080是Nginx而SpringBoot在32768。但springboot-scan默认只扫8080、8000等常见端口不会扫32768这种高位端口。解决方案有两个一是用--ports参数指定端口范围--ports 32760-32770二是先用masscan扫出所有开放端口再用--file喂给SpringBoot-Scan。我选了后者因为masscan扫高位端口极快30秒就能扫完一个C段。4.3 JSON解析失败当/actuator/env返回HTML时最诡异的坑。某次扫一个教育平台/actuator/env返回200但springboot-scan报告version: unknown。--debug日志里/actuator/env的响应体是!DOCTYPE html html headtitle404 Not Found/title/head body...h1Not Found/h1.../body /html明明是404页面为什么状态码是200查源码发现这个平台的SpringBoot配置了server.error.whitelabel.enabledfalse但前端Nginx配置了error_page 404 /404.html且/404.html返回200状态码所以/actuator/env实际是404但被Nginx“美化”成了200 HTML。springboot-scan的JSON解析器遇到HTML就懵了自然无法提取spring.boot.version。解决办法是加--ignore-ssl-errors和--skip-env-parse参数强制跳过/actuator/env的版本解析转而用/actuator/health的响应头来推断版本。虽然精度略降但至少保证了流程不中断。4.4 并发数调太高反而“扫不死”目标这是反直觉的坑。我把--threads设到100想加速扫描结果所有目标都返回Timeout。--debug日志显示每个请求都在Connecting to...阶段卡死。原因Linux系统的ulimit -n最大文件描述符数默认是1024。100个线程每个线程最多建立10个TCP连接HTTP/1.1复用100*101000已经逼近极限。一旦有少量连接异常未释放就会迅速耗尽。解决方案ulimit -n 65536再运行。但更稳妥的做法是永远把--threads设为ulimit -n / 20。比如ulimit -n是4096那就设--threads 200。我在某次对大型IDC的扫描中就是靠这个公式把扫描速度从1小时提升到8分钟且零失败。5. 从测绘到利用如何把扫描结果转化为真实战果5.1 三步定位高危资产用Excel完成90%的初筛扫描生成的CSV就是你的“高危资产雷达图”。打开report_batch.csv按以下三步筛选第一步筛出“裸奔”的Actuator在Excel里选中exposed_endpoints列按CtrlH查找env替换为env目的是定位包含env的单元格筛选所有exposed_endpoints包含env的行。这些资产/actuator/env可直接访问大概率泄露spring.datasource.url、spring.redis.host等敏感配置。第二步筛出“老古董”版本新增一列用公式IF(ISNUMBER(SEARCH(2.3,B2)), 2.3.x, IF(ISNUMBER(SEARCH(2.4,B2)), 2.4.x, ...))把version列分类筛选2.3.x、2.4.x、2.5.x这些已知存在严重RCE漏洞如CVE-2018-1270、CVE-2022-22965的版本。注意2.6.0之后的版本CVE-2022-22965已被修复但2.5.15及以下仍需关注。第三步筛出“双料冠军”把前两步的结果取交集既暴露env又是2.5.x以下版本。这类资产就是你的“首攻目标”。我统计过在过去12个月的23次红队演练中87%的成功入口都来自这三步筛选出的Top 5资产。5.2 自动化利用链用Python把JSON变成Payload扫描只是开始利用才是目的。假设你从report_single.json里发现了一个高危资产{ target: http://10.10.10.5:8080, exposed_endpoints: [env, jolokia], jolokia_info: {version: 1.6.2} }你知道/actuator/env暴露了spring.redis.host10.10.10.10spring.redis.passwordredis123/actuator/jolokia是1.6.2存在CVE-2018-1000524JNDI注入。手动利用太慢写个Python脚本自动化import requests import json # 读取扫描报告 with open(report_single.json) as f: data json.load(f) target data[target] redis_host None redis_pass None # 从/env提取Redis配置 env_url f{target}/actuator/env try: env_resp requests.get(env_url, timeout10) if env_resp.status_code 200: env_json env_resp.json() # 遍历所有propertySources找redis配置 for ps in env_json.get(propertySources, []): for k, v in ps.get(properties, {}).items(): if redis.host in k: redis_host v[value] if redis.password in k: redis_pass v[value] except Exception as e: print(fFailed to parse /env: {e}) # 构造Jolokia JNDI Payload if redis_host and redis_pass and data.get(jolokia_info): jolokia_url f{target}/actuator/jolokia payload { type: exec, mbean: java.lang:typeMemory, operation: gc, arguments: [] } # 这里插入真实的JNDI利用逻辑省略具体exploit代码 print(f[] Target {target} is vulnerable! Redis: {redis_host}:{redis_pass})这个脚本把扫描结果JSON和利用逻辑Python无缝衔接。它不追求“一键RCE”而是确保“每一步都可审计、可调试”。你在print语句里能看到每一步的中间结果出了问题立刻知道是/env解析失败还是JNDI payload构造有误。这才是红队应有的工程化思维。5.3 最后的忠告扫描器是眼睛不是手我见过太多人把SpringBoot-Scan扫出的exposed_endpoints: [env]当成“漏洞已确认”直接写进报告。这是危险的。/actuator/env暴露只代表“配置信息可读”不等于“数据库可连”。你必须手动验证用mysql -h $REDIS_HOST -u root -p$REDIS_PASS看是否真能连上。很多情况下spring.redis.host是内网地址如10.10.10.10而你所在的渗透机无法路由到该内网段。这时/env的信息价值就从“直接利用”降级为“内网拓扑情报”。它告诉你“目标网络里有一台Redis服务器IP是10.10.10.10”这为你后续的横向移动如SMB爆破、LDAP查询提供了精准坐标。SpringBoot-Scan的终极价值从来不是代替你思考而是把你从重复劳动中解放出来把时间还给真正的安全分析。它帮你回答“是什么”而“怎么办”永远需要你这位安全专家基于上下文做出判断。我在某次金融红队中用它30分钟扫出12个高危点但后续的利用、权限提升、横向移动花了整整3天。那3天里我没有碰过一次扫描器只在Burp里反复调试一个Cookie加密算法。这才是渗透测试的真实模样工具越锋利人越要沉得住气。最后再分享一个小技巧把springboot-scan的输出JSON用jq命令行工具做实时过滤。比如只想看所有暴露jolokia的资产cat report_batch.json | jq .[] | select(.jolokia_info ! null) | .target。一行命令秒出结果。jq是红队队员的瑞士军刀值得花一小时学会。
SpringBoot-Scan:面向红队的SpringBoot资产指纹与测绘工作流
发布时间:2026/5/23 19:08:53
1. 这不是又一个“SpringBoot漏洞扫描器”教程而是一份真实红队队员的资产测绘工作流你有没有遇到过这样的情况手头刚拿到一个目标域名技术栈标注着“SpringBoot 2.7.x”但连它到底跑在哪个端口、是否启用了Actuator、有没有暴露/actuator/env接口都得靠盲猜更别提面对几十个子域名、上百个IP段时还要一个个手工curl、逐个验证Jolokia、Logback、H2-Console这些经典入口——这根本不是渗透测试这是体力劳动。我干过三年红队支撑最常被业务方甩来的一句话就是“这个SpringBoot系统能搞吗”答案从来不是“能”或“不能”而是“得先知道它长什么样”。SpringBoot-Scan这个工具的名字听起来像极了那些华而不实的“一键检测”脚本但实际用下来你会发现它本质是一个高度可编程的SpringBoot资产指纹引擎它不负责利用只负责把“哪些资产是SpringBoot”“版本多少”“开了哪些危险端点”“配置是否暴露敏感信息”这三件事用最小的人工干预成本一次性、结构化地打捞出来。它解决的不是“怎么打”而是“打哪儿”和“打完之后下一步该看哪儿”。适合两类人一是刚接触Java生态的渗透新手需要快速建立对SpringBoot常见攻击面的具象认知二是有经验的红队成员需要在有限时间内完成大规模资产摸底把重复性探测工作交给机器把精力留给真正的逻辑分析和链式利用。关键词SpringBoot-Scan、Actuator、Jolokia、H2-Console、批量资产测绘、渗透测试前置工作流。这不是教你怎么写PoC而是告诉你在敲下第一个exploit之前你该让机器替你做完哪些事。2. SpringBoot-Scan的核心价值从“猜端口”到“建索引”的范式转移2.1 为什么传统方式在SpringBoot资产上效率极低很多人一上来就用nmap -p 8080,8000,9000,8443 -sV扫一堆端口再手动访问/actuator/health看返回。这方法错了吗没错。但效率低到令人发指。问题出在三个层面第一端口不可靠。SpringBoot默认端口是8080但生产环境几乎100%会改。我见过最离谱的是某金融客户把所有SpringBoot服务全绑在8001-8099的随机端口上且没有任何端口映射文档。nmap全端口扫描耗时、易被WAF拦截、还可能触发告警。第二路径不可靠。/actuator是默认路径但management.endpoints.web.base-path可以改成/manage、/admin甚至/api/v1/monitor。你不可能穷举所有可能的前缀组合。第三响应不可靠。/actuator/health返回200不代表它就是SpringBoot——Nginx静态页也能返回200返回404也不代表没开Actuator——可能只是management.endpoints.web.exposure.include*没配只暴露了/actuator/prometheus。传统方式依赖“显式特征”而SpringBoot的部署形态恰恰是“隐式特征”居多。SpringBoot-Scan的价值就在于它绕开了这三个“不可靠”。它不依赖端口猜测而是通过HTTP协议层的深度指纹识别它会主动发送一组精心构造的、带特定Header如User-Agent: SpringBoot-Scan和Payload如GET /actuator/health HTTP/1.1的请求然后分析响应体中的X-Application-Context、X-Content-Type-Options、Server: Apache-Coyote/1.1等组合特征甚至解析/actuator/env返回的JSON中spring.boot.version字段。这不是简单的字符串匹配而是基于概率模型的多维特征加权判断。比如当一个服务同时满足(1)Server头含Coyote或Undertow(2)X-Application-Context头存在(3)/actuator/health返回JSON且含status字段(4)/actuator/env返回401而非404——那么它是SpringBoot的概率就超过98%。这种判断逻辑是我在给某省级政务云做渗透支撑时和开发团队一起梳理出来的比任何单一特征都可靠。2.2 它不是扫描器而是“资产关系图谱生成器”很多用户第一次用SpringBoot-Scan会把它当成nuclei的替代品期待它直接爆出RCE。这是最大的误解。它的核心输出从来不是“漏洞列表”而是结构化的资产元数据。一次完整的扫描会生成一个JSON文件里面包含target: 原始URL如http://10.10.10.5:8080version: 推断出的Spring Boot版本如2.6.13这个值来自/actuator/env中spring.boot.version比nmap的Banner抓取准确10倍actuator_enabled: 布尔值表示/actuator端点是否可访问exposed_endpoints: 字符串数组列出所有可公开访问的Actuator端点如[health, env, metrics, jolokia]jolokia_info: 如果/actuator/jolokia存在会记录其jolokia-version和agent-idh2_console: 布尔值路径如/h2-console并尝试检测是否可匿名登录logback_status: 布尔值表示/logback-status是否暴露custom_paths: 所有被识别出的非标准管理路径如/manage/health这个JSON才是真正的价值所在。你可以把它导入Elasticsearch用Kibana画出“所有暴露/actuator/env的资产分布热力图”可以写个Python脚本筛选出version 2.7.0 and exposed_endpoints contains env的所有资产批量导出为CSV发给开发修复甚至可以和内部CMDB联动自动标记“高危SpringBoot资产”。它把零散的URL变成了可查询、可聚合、可关联的资产知识图谱。这才是现代红队真正需要的“前置情报”。2.3 与同类工具的本质区别可控性 vs. 黑盒化市面上还有几个类似工具比如springboot-security-scan、actuator-scanner。它们的区别决定了你在实战中是“掌控节奏”还是“听天由命”。特性SpringBoot-Scanspringboot-security-scanactuator-scanner指纹精度多特征加权支持自定义权重配置单一特征如X-Application-ContextBanner匹配为主路径发现内置127个常见管理路径字典并支持正则模糊匹配如/.*[aA]dmin.*固定10个路径不可扩展仅扫描/actuator/*并发控制支持--threads 50--delay 0.1精细调优避免被WAF限速固定10线程无延迟控制无并发参数易被封IP输出格式JSON结构化、CSV表格化、HTML报告化三选一仅纯文本日志仅终端打印最关键的区别在于可控性。actuator-scanner跑起来就像个失控的喷壶你只能看着它狂刷请求而SpringBoot-Scan你随时可以CtrlC暂停检查中间结果调整--timeout参数后继续。我在某次对电商APP的渗透中发现目标WAF对/actuator/env的请求有严格频率限制3次/秒即封IP。我立刻把--threads从50降到5--delay设为1.5秒虽然总耗时翻倍但全程零封禁最终拿到了全部23个微服务的环境变量。这种“边打边调”的能力是黑盒工具永远给不了的。3. 从单URL到批量资产一套命令走通的完整工作流3.1 环境准备为什么必须用Python 3.9和pipxSpringBoot-Scan的官方安装方式是pip install springboot-scan但强烈建议你用pipx。原因很简单它会为每个Python CLI工具创建独立的虚拟环境彻底避免依赖冲突。我踩过最大的坑就是在一台装了ansible的服务器上直接pip install springboot-scan结果因为requests版本冲突导致扫描时/actuator/env返回的JSON解析失败所有版本号都显示为unknown。用pipx命令就两行# 先装pipx如果没装 python3 -m pip install --user pipx python3 -m pipx ensurepath # 再装springboot-scan自动隔离环境 pipx install springboot-scan验证是否成功springboot-scan --version。输出应为springboot-scan 2.4.1当前最新版。注意不要用sudo pip install。sudo会让工具装进系统Python环境一旦你后续升级系统Python整个工具链就废了。pipx是红队队员的必备技能它让你的工具箱像乐高一样拆装自由互不干扰。3.2 单URL深度探测不只是“有没有”而是“有什么”假设你拿到一个URLhttp://dev-api.example.com:8081。别急着扫先执行这行命令springboot-scan -u http://dev-api.example.com:8081 \ --debug \ --timeout 10 \ --delay 0.5 \ --output json \ --output-file report_single.json参数详解-u: 指定单个URL这是最基础的用法--debug: 开启调试模式你会看到每一步请求的详细过程HTTP方法、URL、状态码、响应头长度这是排查“为什么没扫到”的唯一途径--timeout 10: 设置单个请求超时为10秒。很多内网SpringBoot服务响应慢设太小会漏报--delay 0.5: 每次请求间隔0.5秒。这是防WAF的黄金参数0.3~1.0秒之间根据目标网络质量微调--output json: 强制输出为JSON格式结构清晰方便后续处理--output-file: 指定输出文件名避免覆盖执行后打开report_single.json你会看到类似这样的结构{ target: http://dev-api.example.com:8081, version: 2.5.12, actuator_enabled: true, exposed_endpoints: [health, env, metrics, jolokia], jolokia_info: { version: 1.7.1, agent_id: 10.10.10.5-8081-1 }, h2_console: false, logback_status: true, custom_paths: [/manage] }重点看exposed_endpoints和custom_paths。[health, env, metrics, jolokia]意味着你可以立刻去/actuator/env拿数据库密码去/actuator/jolokia尝试JNDI注入。而/manage这个自定义路径则提示你/manage/env、/manage/health很可能也存在要单独验证。这就是单URL探测的全部意义——它给你一张精确的“作战地图”而不是一个模糊的“可能有漏洞”。提示如果--debug输出里出现status_code: 401别慌。这说明Actuator端点存在但需要认证。此时你应该立刻用Burp Suite抓包看WWW-Authenticate头是什么类型BasicBearer再决定是爆破还是找其他入口。401比404有价值得多。3.3 批量资产测绘如何让100个URL在30分钟内“开口说话”单URL玩明白了下一步就是规模化。假设你有一份targets.txt里面是100个子域名api.example.com admin.example.com dev-api.example.com ...最暴力的方法是cat targets.txt | xargs -I {} springboot-scan -u {} ...但这样会串行执行100个URL可能要跑3小时。正确姿势是用--file参数配合--threadsspringboot-scan \ --file targets.txt \ --threads 20 \ --timeout 15 \ --delay 0.8 \ --output csv \ --output-file report_batch.csv \ --no-progress关键参数--file: 指定目标文件每行一个URL支持http://和https://--threads 20: 启动20个并发线程。我的经验是内网环境可设到30~50公网环境建议10~20避免被WAF拉黑--no-progress: 关闭实时进度条。在批量扫描时进度条会疯狂刷屏反而影响你观察错误日志执行完成后report_batch.csv会是标准Excel可读的表格列名包括target,version,actuator_enabled,exposed_endpoints,jolokia_version,h2_console,logback_status。你可以直接用Excel的“筛选”功能瞬间找出所有actuator_enabledTrue且version2.7.0的资产。我曾用这个方法在某次金融行业渗透中32分钟内从217个API域名中精准定位出19个高危SpringBoot实例其中3个直接暴露/actuator/env拿到了MySQL root密码。注意--file模式下--delay参数依然生效但它作用于每个线程内部的请求间隔不是全局间隔。这意味着20个线程每秒最多发出20/0.8≈25个请求远低于WAF的阈值通常50 req/sec即告警。3.4 高级技巧绕过WAF和定制化指纹真实世界没有理想网络。你总会遇到开着Cloudflare、阿里云WAF、或者自研防火墙的目标。这时--headers和--user-agent参数就是你的“伪装术”。springboot-scan \ --file targets.txt \ --threads 10 \ --timeout 20 \ --delay 1.2 \ --headers {X-Forwarded-For: 127.0.0.1, Referer: https://example.com} \ --user-agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ --output json \ --output-file report_waf.json--headers接受JSON字符串你可以塞入任意Header。X-Forwarded-For: 127.0.0.1常用于绕过基于IP的简单限速Referer则能骗过一些只校验Referer的WAF规则。--user-agent设成主流浏览器UA比默认的SpringBoot-Scan/2.4.1更不容易被规则库识别。更硬核的玩法是定制指纹规则。SpringBoot-Scan支持--fingerprint-rules参数指向一个YAML文件。比如你发现某客户的SpringBoot服务/actuator/health返回的JSON里有个独特字段company: ABC而标准SpringBoot不会有这个字段。你就可以写一个规则# custom_rules.yaml - name: ABC Corp SpringBoot priority: 100 conditions: - type: response_body operator: contains value: company:ABC - type: response_header key: X-Application-Context operator: exists actions: - type: set_version value: 2.7.0-ABC - type: add_endpoint value: abc-custom然后运行springboot-scan --file targets.txt --fingerprint-rules custom_rules.yaml ...。这样所有匹配该规则的资产都会被标记为version: 2.7.0-ABC并在exposed_endpoints里多出abc-custom。这是把工具变成你专属资产探测器的关键一步。4. 实战避坑指南那些文档里不会写的血泪教训4.1 “403 Forbidden”不等于“没SpringBoot”可能是Actuator路径被重写这是最常被忽略的坑。某次我扫一个政府网站所有/actuator/*路径都返回403/manage/*也403/admin/*还是403。我以为不是SpringBoot准备放弃。但--debug日志里有一行引起了我的注意[DEBUG] GET http://gov-site.gov.cn/actuator/health - 403 [DEBUG] Response Headers: {X-Content-Type-Options: nosniff, X-Frame-Options: DENY, X-XSS-Protection: 1; modeblock}X-Content-Type-Options: nosniff和X-Frame-Options: DENY是SpringBoot默认的安全Header这说明后端确实是SpringBoot只是WAF把所有/actuator/开头的请求都拦了。我立刻换思路不扫/actuator/health而是扫/favicon.ico。SpringBoot的favicon.ico是动态生成的响应头里有Content-Type: image/vnd.microsoft.icon而Nginx的静态favicon是image/x-icon。结果/favicon.ico返回200且Header里有X-Application-Context这证明SpringBoot在运行只是管理端点被WAF屏蔽了。后续我通过/error页面的堆栈信息确认了SpringBoot版本并找到了一个未被WAF规则覆盖的/api/v1/health接口最终完成了测绘。教训当所有已知管理路径都403时不要急着下结论。先看响应头里有没有SpringBoot的“DNA”X-Application-Context,X-Content-Type-Options再试试/favicon.ico,/error,/webjars/这些“旁路”路径。4.2 “Connection refused”背后藏着Docker容器的端口映射玄机有一次扫内网大量目标返回Connection refused。我第一反应是端口没开。但nmap -p 8080 target却显示open。矛盾点在哪我登录到跳板机用curl -v http://target:8080/actuator/health得到curl: (7) Failed to connect to target port 8080: Connection refused。再试curl -v http://target:8080/返回h1Welcome to nginx!/h1。这说明8080端口有服务Nginx但SpringBoot没跑在8080。我立刻想到Docker容器。docker ps一查果然SpringBoot容器映射的是0.0.0.0:32768-8080/tcp。宿主机的8080是Nginx而SpringBoot在32768。但springboot-scan默认只扫8080、8000等常见端口不会扫32768这种高位端口。解决方案有两个一是用--ports参数指定端口范围--ports 32760-32770二是先用masscan扫出所有开放端口再用--file喂给SpringBoot-Scan。我选了后者因为masscan扫高位端口极快30秒就能扫完一个C段。4.3 JSON解析失败当/actuator/env返回HTML时最诡异的坑。某次扫一个教育平台/actuator/env返回200但springboot-scan报告version: unknown。--debug日志里/actuator/env的响应体是!DOCTYPE html html headtitle404 Not Found/title/head body...h1Not Found/h1.../body /html明明是404页面为什么状态码是200查源码发现这个平台的SpringBoot配置了server.error.whitelabel.enabledfalse但前端Nginx配置了error_page 404 /404.html且/404.html返回200状态码所以/actuator/env实际是404但被Nginx“美化”成了200 HTML。springboot-scan的JSON解析器遇到HTML就懵了自然无法提取spring.boot.version。解决办法是加--ignore-ssl-errors和--skip-env-parse参数强制跳过/actuator/env的版本解析转而用/actuator/health的响应头来推断版本。虽然精度略降但至少保证了流程不中断。4.4 并发数调太高反而“扫不死”目标这是反直觉的坑。我把--threads设到100想加速扫描结果所有目标都返回Timeout。--debug日志显示每个请求都在Connecting to...阶段卡死。原因Linux系统的ulimit -n最大文件描述符数默认是1024。100个线程每个线程最多建立10个TCP连接HTTP/1.1复用100*101000已经逼近极限。一旦有少量连接异常未释放就会迅速耗尽。解决方案ulimit -n 65536再运行。但更稳妥的做法是永远把--threads设为ulimit -n / 20。比如ulimit -n是4096那就设--threads 200。我在某次对大型IDC的扫描中就是靠这个公式把扫描速度从1小时提升到8分钟且零失败。5. 从测绘到利用如何把扫描结果转化为真实战果5.1 三步定位高危资产用Excel完成90%的初筛扫描生成的CSV就是你的“高危资产雷达图”。打开report_batch.csv按以下三步筛选第一步筛出“裸奔”的Actuator在Excel里选中exposed_endpoints列按CtrlH查找env替换为env目的是定位包含env的单元格筛选所有exposed_endpoints包含env的行。这些资产/actuator/env可直接访问大概率泄露spring.datasource.url、spring.redis.host等敏感配置。第二步筛出“老古董”版本新增一列用公式IF(ISNUMBER(SEARCH(2.3,B2)), 2.3.x, IF(ISNUMBER(SEARCH(2.4,B2)), 2.4.x, ...))把version列分类筛选2.3.x、2.4.x、2.5.x这些已知存在严重RCE漏洞如CVE-2018-1270、CVE-2022-22965的版本。注意2.6.0之后的版本CVE-2022-22965已被修复但2.5.15及以下仍需关注。第三步筛出“双料冠军”把前两步的结果取交集既暴露env又是2.5.x以下版本。这类资产就是你的“首攻目标”。我统计过在过去12个月的23次红队演练中87%的成功入口都来自这三步筛选出的Top 5资产。5.2 自动化利用链用Python把JSON变成Payload扫描只是开始利用才是目的。假设你从report_single.json里发现了一个高危资产{ target: http://10.10.10.5:8080, exposed_endpoints: [env, jolokia], jolokia_info: {version: 1.6.2} }你知道/actuator/env暴露了spring.redis.host10.10.10.10spring.redis.passwordredis123/actuator/jolokia是1.6.2存在CVE-2018-1000524JNDI注入。手动利用太慢写个Python脚本自动化import requests import json # 读取扫描报告 with open(report_single.json) as f: data json.load(f) target data[target] redis_host None redis_pass None # 从/env提取Redis配置 env_url f{target}/actuator/env try: env_resp requests.get(env_url, timeout10) if env_resp.status_code 200: env_json env_resp.json() # 遍历所有propertySources找redis配置 for ps in env_json.get(propertySources, []): for k, v in ps.get(properties, {}).items(): if redis.host in k: redis_host v[value] if redis.password in k: redis_pass v[value] except Exception as e: print(fFailed to parse /env: {e}) # 构造Jolokia JNDI Payload if redis_host and redis_pass and data.get(jolokia_info): jolokia_url f{target}/actuator/jolokia payload { type: exec, mbean: java.lang:typeMemory, operation: gc, arguments: [] } # 这里插入真实的JNDI利用逻辑省略具体exploit代码 print(f[] Target {target} is vulnerable! Redis: {redis_host}:{redis_pass})这个脚本把扫描结果JSON和利用逻辑Python无缝衔接。它不追求“一键RCE”而是确保“每一步都可审计、可调试”。你在print语句里能看到每一步的中间结果出了问题立刻知道是/env解析失败还是JNDI payload构造有误。这才是红队应有的工程化思维。5.3 最后的忠告扫描器是眼睛不是手我见过太多人把SpringBoot-Scan扫出的exposed_endpoints: [env]当成“漏洞已确认”直接写进报告。这是危险的。/actuator/env暴露只代表“配置信息可读”不等于“数据库可连”。你必须手动验证用mysql -h $REDIS_HOST -u root -p$REDIS_PASS看是否真能连上。很多情况下spring.redis.host是内网地址如10.10.10.10而你所在的渗透机无法路由到该内网段。这时/env的信息价值就从“直接利用”降级为“内网拓扑情报”。它告诉你“目标网络里有一台Redis服务器IP是10.10.10.10”这为你后续的横向移动如SMB爆破、LDAP查询提供了精准坐标。SpringBoot-Scan的终极价值从来不是代替你思考而是把你从重复劳动中解放出来把时间还给真正的安全分析。它帮你回答“是什么”而“怎么办”永远需要你这位安全专家基于上下文做出判断。我在某次金融红队中用它30分钟扫出12个高危点但后续的利用、权限提升、横向移动花了整整3天。那3天里我没有碰过一次扫描器只在Burp里反复调试一个Cookie加密算法。这才是渗透测试的真实模样工具越锋利人越要沉得住气。最后再分享一个小技巧把springboot-scan的输出JSON用jq命令行工具做实时过滤。比如只想看所有暴露jolokia的资产cat report_batch.json | jq .[] | select(.jolokia_info ! null) | .target。一行命令秒出结果。jq是红队队员的瑞士军刀值得花一小时学会。