// 01
El reto y el CVE
RansomNet es un challenge de categoría Web con dificultad Very Easy. Nos presenta el sitio de un supuesto sindicato de ransomware —con su mercado de servicios criminales— y el objetivo es infiltrarlo para desenmascarar a sus responsables. La página es, por dentro, un foro vBulletin 6.0.3 sin actualizar, y toda la vulnerabilidad está ahí: en la versión del CMS, no en la lógica de negocio.
// DESCRIPCIÓN OFICIAL DEL RETO
«RansomNet's shadowy corridors pulse with whispers of ransomware operations, malware distribution, and cybercriminal services—and your mission is to infiltrate its depths and unmask the syndicate's true masterminds.»
CVE-2025-48828 es un fallo de diseño en el manejador de la API AJAX de vBulletin y en su sistema de renderizado de plantillas, presente en las versiones 5.0.0 hasta 6.0.3 (divulgado el 23 de mayo de 2025 y con PoC público). Permite a un atacante no autenticado invocar métodos protegidos de los controladores de la API, entre ellos vB_Api_Ad::replaceAdTemplate().
El detalle decisivo es la versión de PHP: el fallo se apoya en el uso indebido que hace
vBulletin de la Reflection API de PHP. A partir de
PHP 8.1, Reflection puede acceder siempre a métodos
protegidos y privados (setAccessible() pasó a ser un no-op), de modo que el
control de visibilidad en el que confiaba el CMS deja de proteger nada. Con PHP anterior
a 8.1, la misma petición no llega a ejecutar el método.
// IMPACTO
Ejecución remota de comandos sin credenciales en el contexto del usuario del servidor web. En un vBulletin expuesto a Internet, esto equivale al compromiso completo del servidor: lectura de la base de datos y de sus credenciales, escritura de webshells y pivote hacia la red interna. Los dos CVEs asociados (CVE-2025-48827 y CVE-2025-48828) fueron explotados activamente en campañas reales pocos días después de su divulgación.
La explotación encadena dos peticiones y un artefacto almacenado en el propio foro:
| PASO | PETICIÓN / ARTEFACTO | QUÉ OCURRE |
|---|---|---|
| 1 | POST /ajax/api/ad/replaceAdTemplate | Se invoca el método protegido sin autenticación y se inyecta una plantilla maliciosa |
| 2 | <vb:if condition="system('comando')"> | El código PHP del atacante queda almacenado en el sistema como plantilla de anuncio |
| 3 | POST /ajax/render/ad_<location> | Se dispara la renderización de la plantilla inyectada, también sin autenticación |
| 4 | Respuesta HTTP | El servidor ejecuta el comando y devuelve su salida en el cuerpo de la respuesta |
// NOTA TÉCNICA
El parámetro location es el que enlaza las dos peticiones: la plantilla
se guarda para una «ubicación» concreta y después se renderiza pidiendo
ajax/render/ad_ más ese mismo valor. Los PoC modernos codifican el comando
en base64 dentro de la condición
("system"("base64_decode"("..."))) para esquivar comillas y caracteres
especiales, y usan "function"(...) en lugar de eval() porque
este último es una construcción del lenguaje y no una función invocable.
// 02
Preparación del exploit
Existen varios PoC publicados para esta vulnerabilidad. Usamos el repositorio
vBulletin-CVE-2025-48828-Multi-target, que incluye un
escáner multi-objetivo (scanner.py) capaz de comprobar si el objetivo es
vulnerable y ejecutar el comando que le indiquemos.
Entramos en el directorio y instalamos la única dependencia:
requests, la librería con la que el script habla por HTTP
con el objetivo. El flag --break-system-packages es necesario
en Kali Linux porque, desde PEP 668, los paquetes Python del sistema están protegidos
frente a instalaciones de pip.
El script lee los objetivos de targets.txt, así que escribimos ahí la URL de nuestra instancia del challenge. Ojo: en HackTheBox cada instancia se levanta en un host y un puerto distintos, por lo que hay que copiar los que aparezcan en el panel del reto en lugar de reutilizar los de este writeup.
Después editamos scanner.py para indicar qué comando queremos ejecutar en
la víctima. Las dos variables que importan son COMMAND
(el comando) y EXPECTED_OUTPUT (cadena que debe aparecer en
la respuesta para dar la ejecución por buena; vacía = acepta cualquier salida).
// 03
Explotación
Con el objetivo escrito y el comando configurado, lanzamos el exploit. El script comprueba primero si la respuesta contiene la marca que inyecta (confirmación de RCE) y, si es así, vuelca la salida del comando ejecutado:
La ejecución está confirmada: el listado de la raíz del sistema aparece dentro de la
respuesta HTTP. Y ahí está el objetivo de esta fase — un
flag.txt de 29 bytes en /. El punto que sigue a los
permisos (drwxr-xr-x.) indica que el sistema usa contextos SELinux.
El script va almacenando en vuln.txt cada URL vulnerable junto con la salida obtenida, lo que permite revisar hallazgos anteriores sin repetir el escaneo:
// 04
Enumeración y captura de la flag
El exploit no da una shell interactiva: es un one-shot. Para cada comando nuevo
hay que cambiar el valor de COMMAND en scanner.py y volver a
lanzar python3 scanner.py. La enumeración sigue el orden habitual: primero
orientarse, y después buscar la flag.
| COMANDO | PROPÓSITO |
|---|---|
| whoami | Usuario en cuyo contexto se ejecuta el comando |
| id | UID, GID y grupos del usuario |
| pwd | Directorio de trabajo actual |
| ls -la / | Lista archivos y directorios de la raíz |
| ls -la /var/www | Contenido del servidor web |
| cat /etc/passwd | Usuarios del sistema |
| uname -a | Información del kernel |
| env | Variables de entorno del proceso web |
| find / -name "*.txt" 2>/dev/null | Busca archivos de texto en todo el sistema |
| ifconfig / ip a | Interfaces de red (posible pivote) |
Con el flag.txt localizado en la raíz, basta con pedir su contenido. Se
cambia el comando y se relanza el escáner:
// FLAG DEL RETO
La flag se obtiene leyendo /flag.txt (29 bytes, visto en el listado de la raíz). En esta versión publicada el valor va censurado: es la respuesta oficial del challenge y publicarla arruina el reto a quien lo esté resolviendo. La ruta completa de explotación —CVE-2025-48828 en vBulletin 6.0.3, RCE no autenticada y lectura del archivo— queda documentada paso a paso.
// LECCIÓN
Un CMS sin actualizar convierte cualquier web en una puerta de entrada:
aquí basta una petición no autenticada para ejecutar comandos como el usuario del
servidor web. La mitigación pasa por actualizar vBulletin a una versión parcheada,
mantener PHP al día, y añadir defensa en profundidad (WAF con reglas para
ajax/api, segmentación del servidor y rotación de las credenciales de la
base de datos). Los dos CVE asociados se explotaron de forma masiva en Internet días
después de su publicación, así que la ventana de exposición es real y corta.
| VULNERABILIDAD | COMPONENTE | IMPACTO |
|---|---|---|
| CVE-2025-48828 | vBulletin 6.0.3 · ajax/api/ad/replaceAdTemplate | Invocación no autenticada de métodos protegidos de la API |
| Inyección de plantilla | Motor de plantillas <vb:if> | Ejecución de PHP arbitrario desde una plantilla almacenada |
| RCE no autenticada | ajax/render/ad_<location> | Comandos arbitrarios como el usuario del servidor web |
| Software desactualizado | vBulletin 5.0.0 – 6.0.3 | Exposición a una vulnerabilidad con explotación activa en Internet |
// 05
Referencia de Comandos
- --depth 1 — clona solo el último commit
- -b <rama> — selecciona una rama concreta
- --break-system-packages — permite instalar en el Python del sistema
- -r requirements.txt — instala desde un listado
- --user — instala solo para el usuario actual
- COMMAND — comando a ejecutar en la víctima
- EXPECTED_OUTPUT — cadena esperada en la respuesta (vacío = cualquiera)
- targets.txt — listado de objetivos a probar
- vuln.txt — salida acumulada de los objetivos vulnerables
- id / whoami — contexto de ejecución del proceso web
- ls -la / — localiza archivos interesantes en la raíz
- find / -name "*.txt" 2>/dev/null — búsqueda recursiva silenciando errores
- cat /flag.txt — lectura de la flag del reto
// 06
Glosario
vB_Api_Ad::replaceAdTemplate(). Afecta a las versiones
5.0.0 – 6.0.3, fue divulgado el 23 de mayo de 2025 y cuenta con PoC
público (autor original del hallazgo: Egidio Romano, EgiX). Su gemelo
CVE-2025-48827 explota el mismo mecanismo sobre otros controladores.
vB_Api_*), capa de plantillas con su propio lenguaje
(<vb:if>, <vb:each>) y una base de datos MySQL.
Precisamente por su larga historia acumula numerosos CVEs críticos, y su API AJAX ha
sido el vector de varios de ellos: cualquier método que la API exponga sin comprobar
autenticación correctamente se convierte en una puerta abierta.
ReflectionMethod::setAccessible(true) para poder invocar un método
protegido o privado; a partir de PHP 8.1 esa llamada es un
no-op, porque Reflection accede siempre a esos miembros. Ese cambio de
comportamiento es el que convierte el descuido de vBulletin en explotable: el código
confiaba en la visibilidad del método como control de acceso, y en PHP 8.1+ esa
barrera ya no existe.
<vb:if condition="...">, que se evalúan en PHP al renderizar la
página. La vulnerabilidad aprovecha que la condición es código PHP: si un
atacante consigue almacenar una plantilla con
<vb:if condition="system('id')">, el servidor ejecutará ese comando
cada vez que renderice el anuncio. Es el patrón clásico de
inyección de plantilla del lado del servidor.
ajax/api/<controlador>/<método>
enruta la petición hacia un método de la API y es donde falta el control de acceso;
ajax/render/ad_<location> renderiza el anuncio previamente guardado
para esa ubicación y es donde se dispara el código inyectado. La variable
location es el hilo que une ambas peticiones y la que permite separar
la fase de inyección de la de ejecución.
COMMAND y guarda los resultados en vuln.txt. Como
ventaja de aprendizaje, leer el script revela exactamente qué se envía en cada petición
HTTP; como riesgo, ejecutar código de terceros contra un objetivo exige revisarlo antes.
pip install falla con error: externally-managed-environment.
El flag --break-system-packages desactiva esa protección y permite instalar
en el intérprete del sistema; la alternativa recomendada en un pentest es crear un
entorno virtual con python3 -m venv.
id, whoami), sistema operativo y kernel
(uname -a), estructura de ficheros (ls -la /), usuarios
(cat /etc/passwd) y variables de entorno (env), que suelen
revelar credenciales de base de datos o rutas internas. Cuando el exploit es
one-shot y no entrega shell, cada uno de estos comandos se ejecuta en una
iteración independiente.