Aikido

Múltiples vulnerabilidades de cross-site scripting (XSS) en Mailcow

Escrito por
Jorian Woltjer

Mailcow es un servidor de correo electrónico autoalojado y de código abierto ampliamente utilizado que aloja todo lo necesario para gestionar buzones de correo por sí mismo. Para evaluar su seguridad, configuramos una instancia local y ejecutamos nuestros agentes de pentesting de IA contra ella. Encontramos tres vulnerabilidades XSS, incluida una vulnerabilidad crítica que permitía a atacantes no autenticados tomar el control de cuentas de administrador mientras consultaban sus registros en la UI.

Obtener acceso a un buzón de correo puede tener un grave impacto en la seguridad. Se pueden capturar datos sensibles encontrados en los correos electrónicos, pero el acceso también permite a los atacantes utilizar la funcionalidad de restablecimiento de contraseña para comprometer otras cuentas conectadas de la víctima. Esto es exactamente lo que habría sido posible si alguien hubiera explotado estas vulnerabilidades.

Todas las vulnerabilidades fueron divulgadas de forma responsable a Mailcow y han sido corregidas desde la versión 2026-03b (lanzada el 31 de marzo de 2026). Nos gustaría agradecer al mantenedor, FreddleSpl0it, por el proceso fluido y la rápida solución.

Registros de Autodiscover sin escapar

Reportada en GitHub como GHSA-f9xf-vc72-rcgm, esta vulnerabilidad permitía a atacantes no autenticados enviar una solicitud de Autodiscover que contenía una dirección de correo electrónico maliciosa, la cual aparecería en los registros. Cuando un administrador visualiza posteriormente estos registros, el campo de la dirección de correo electrónico se muestra sin escapar, lo que permite la inyección HTML y XSS.

Empecemos por el sumidero. Mailcow permite visualizar los registros de Autodiscover en una tabla en el panel de administración, que se renderiza utilizando DataTables en dashboard.js. Esta librería tiene el inconveniente común de interpretar cualquier valor de datos como HTML por defecto. Todos los valores deben ser correctamente escapados de antemano.

var table = $('#autodiscover_log').DataTable({
  ...
  ajax: {
    type: "GET",
    url: "/api/v1/get/logs/autodiscover/100",
    dataSrc: function(data){
      return process_table_data(data, 'autodiscover_log');
    }
  },

El process_table_data() la función intenta escapar la entrada del usuario en cada fila. Para los registros de Autodiscover,  item.ua (User Agent) se escapa correctamente utilizando escapeHtml (dashboard.js):

} else if (table == 'autodiscover_log') { 
   $.each(data, function (i, item) { 
     if (item.ua == null) { 
       item.ua = 'unknown'; 
     } else { 
       item.ua = escapeHtml(item.ua); 
     } 
     item.ua = '<span style="font-size:small">' + item.ua + '</span>';

Sin embargo, una columna, item.name, no se escapa. Esta es la dirección de correo electrónico enviada en la solicitud de Autodiscover.

¿La parte más peligrosa? Una solicitud de Autodiscover es no autenticada por diseño y, en este caso, la dirección de correo electrónico no se valida. Terminará en los registros y, una vez que un administrador los vea, pasará por estas funciones vulnerables para ser renderizado como HTML arbitrario.

A continuación, se muestra una solicitud de ejemplo que inyecta <img src=x onerror=alert(origin)> en la tabla:

POST /Autodiscover/Autodiscover.xml HTTP/2
Host: 127.0.0.1
Content-Type: text/xml
Content-Length: 384

<?xml version='1.0' encoding='utf-8'?>
<Autodiscover xmlns='http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006'>
  <Request>
    <EMailAddress>&lt;img src=x onerror=alert(origin)&gt;</EMailAddress>
    <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
  </Request>
</Autodiscover>

Cuando un administrador visualiza ahora los registros de Autodiscover (ubicados en Dashboard -> Logs -> Autodiscover), el JavaScript se ejecuta y muestra una ventana emergente de alerta, lo que demuestra XSS en el origen de Mailcow.

En la copia de mailcow en localhost, aparece una ventana emergente que dice "localhost dice https://localhost

Dado que esto afecta solo a los administradores, los atacantes pueden acceder al buzón de cualquier usuario y reconfigurar la instancia. Esto otorgó a la vulnerabilidad una severidad crítica.

Este problema se solucionó añadiendo una escapeHtml() llamada alrededor de item.user al procesar las filas.

Inyección de nombres de archivo adjuntos en cuarentena

Reportada como GHSA-2xjc-rg88-jvpp, esta vulnerabilidad reside en la función de Cuarentena de Mailcow, donde los administradores pueden investigar archivos adjuntos marcados. Los nombres de archivo de esos adjuntos se mostraban sin escape de HTML, abriendo la puerta a XSS para cualquier administrador que los viera. Explotar esto no requería autenticación por parte del atacante, aunque sí requería que la función de Cuarentena estuviera habilitada en la instancia objetivo.

El sink está claro esta vez. Dentro de quarentine.js, hay un fragmento de código que concatena HTML con datos dinámicos:

$.each(data.attachments, function(index, value) { 
   qAtts.append( 
     '<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' + 
     ' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>' 
   ); 
 });

Si alguno de estos valores es controlado por el usuario, podría resultar nuevamente en una inyección de HTML. Estos valores son nombre de archivo, tipo MIME, tamaño de archivo y virustotal sha256, respectivamente. De estos, el nombre de archivo parece ser el más propenso a contener entrada de usuario, ya que el atacante puede enviar un correo electrónico con un adjunto que tiene un nombre de archivo especial que consiste en etiquetas HTML. 

When testing, it turns out that, as expected, no strict validation takes place on this filename. It can contain <> characters, which is all that's needed to inject an XSS payload. The remaining challenge is getting the email into the quarantine queue in the first place, so an admin will open and inspect it. The attacker solves this by attaching an EICAR antivirus test file, which is a standardized string that every antivirus engine is guaranteed to flag. This reliably routes the email to quarantine without requiring the attacker to send actual malware.

El código a continuación genera un adjunto con la cadena EICAR como contenido y un payload XSS como nombre de archivo (consulte el aviso para ver un script completo):

# Malicious filename attachment with EICAR to trigger quarantine
att = MIMEApplication(b'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*', _subtype='octet-stream')
att.add_header('Content-Disposition', 'attachment', filename='<img src=x onerror=alert(origin)>.exe')
msg.attach(att)

Después de enviar el correo electrónico a Mailcow y de que este sea puesto en cuarentena, el administrador puede abrir la página de Cuarentena para verlo. Una vez que hacen clic en Mostrar elemento, nuestro nombre de archivo se renderiza y el XSS se activa:

Tenga en cuenta que esto tampoco requiere autenticación en Mailcow para ser explotado. Todo lo que se necesita es enviar un correo electrónico maliciosamente elaborado al dominio de Mailcow y que el administrador lo investigue. Desde aquí, toda la instancia podría ser tomada usando JavaScript, leyendo buzones nuevamente y configurando la instancia.

Este problema se solucionó añadiendo llamadas a escapeHtml() alrededor de los valores de los adjuntos.

Elevación de un Self-XSS en IP listada en el Historial de Inicio de Sesión

La última vulnerabilidad XSS, reportada como GHSA-jprq-w83q-q62h, fue más interesante técnicamente de explotar. El payload XSS se almacena bajo la propia cuenta del atacante, lo que significa que, normalmente por sí solo, solo el atacante lo activaría (un "Self-XSS" sin impacto real). El ataque se vuelve peligroso cuando se combina con un CSRF de inicio de sesión, que fuerza al navegador de la víctima a iniciar sesión en la cuenta del atacante, colocando el payload almacenado frente a la persona equivocada.

La vulnerabilidad comienza con otro simple sink de concatenación de HTML en user.js:

var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.tools/prefix/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>";

El real_rip (IP Remota Real) se renderiza en la tabla de inicios de sesión recientes sin ser escapada. Normalmente, esto no debería ser un problema; las direcciones IP siguen un formato muy estricto sin espacio para payloads XSS.

Sin embargo, las direcciones IP no son validadas por Mailcow y provienen directamente del X-Real-IP: header por defecto. Algunos proxies configuran este header con la IP remota del usuario, pero si no existe tal proxy, cualquier valor que se le asigne será de confianza. Por lo tanto, puedes iniciar sesión mientras configuras X-Real-IP: "><img src onerror=alert(origin)> lo que guarda el valor en las entradas de inicio de sesión recientes. Cuando se visualiza (a través de /user), la carga útil XSS se renderiza y ejecuta la alerta.

POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
X-Real-Ip: "><img src onerror=alert(origin)>

login_user=attacker&pass_user=attacker

Sin embargo, esta no es el final de la historia. Porque una víctima no iniciará sesión con este header IP falso, ni iniciará sesión en la cuenta del atacante de la nada. Por ahora, es Self-XSS.

Dos aspectos de esta vulnerabilidad hacen posible la escalada. Primero, la carga útil se almacena de forma persistente en la cuenta del atacante. Segundo, la solicitud de inicio de sesión no tiene protección CSRF, lo que significa que puede ser activada de forma cross-site. Un atacante puede alojar un formulario en su propio dominio que envía automáticamente las credenciales del atacante, iniciando sesión para la víctima sin ninguna interacción más allá de visitar la página.

Un atacante puede alojar el siguiente formulario en su dominio malicioso:

<form action="http://mailcow.local/" method="POST">
    <input type="text" name="login_user" value="attacker">
    <input type="text" name="pass_user" value="attacker">
</form>
<script>
    document.forms[0].submit();
</script>

Una vez que una víctima abre este formulario alojado en el sitio web malicioso, se inicia sesión automáticamente en la cuenta del atacante. Luego podemos redirigirlos a /user para activar el XSS que almacenamos anteriormente. Pero espera un segundo, la víctima ahora ha iniciado sesión en la cuenta del atacante, entonces ¿cómo puede el atacante robar los datos de la víctima ?

Aquí, el atacante puede usar un truco ingenioso: mantener abierto el buzón de la víctima hasta después del Login CSRF y XSS, y luego leer de él usando acceso de mismo origen.

El nuevo flujo será el siguiente:

  1. La víctima navega a nuestra página maliciosa (pestaña 1)
  2. La pestaña 1 abre una segunda página maliciosa (pestaña 2) que contiene el formulario de CSRF de inicio de sesión, con un ligero retraso antes de que se ejecute
  3. La pestaña 1 redirige al buzón de la víctima, cargando los datos que el atacante quiere robar
  4. La pestaña 2 envía el formulario de CSRF de inicio de sesión, abriendo una nueva pestaña 3 que inicia sesión para la víctima en la cuenta del atacante
  5. La pestaña 2 se redirige a /user, donde se activa el XSS almacenado
  6. El XSS en la pestaña 2 utiliza una referencia a opener para leer y exfiltrar document.body.innerHTML en la pestaña 1, donde el buzón de la víctima sigue abierto

El diagrama muestra tres cuadros que representan pestañas del navegador. 1. Mailbox 2. XSS 3. CSRF. Las dos últimas pertenecen al atacante.

Esto permite leer correos electrónicos de la cuenta de la víctima porque se obtuvieron mientras la sesión de su cuenta seguía activa.

Consulte el aviso para obtener todos los detalles sobre cómo funciona el flujo de explotación en la práctica. Múltiples archivos HTML con control preciso sobre el flujo permiten a un atacante robar correos electrónicos en solo dos clics (necesarios para abrir las dos pestañas adicionales).

Este problema se solucionó añadiendo llamadas a escapeHtml() alrededor de la renderización de la IP.

Remediación

Actualice Mailcow a la versión 2026-03b o posterior, lanzada el 31 de marzo de 2026. Las tres vulnerabilidades XSS están parcheadas en esta versión. Si utiliza Aikido, las instancias vulnerables de Mailcow se marcan automáticamente en su feed de monitorización de superficie como un hallazgo crítico.

Panel de Aikido mostrando una puntuación de 95: Crítico para Mailcow. También incluye un resumen (TL;DR) y los pasos para solucionarlo.

¿Aún no está en Aikido? Cree una cuenta gratuita para empezar — no se requiere tarjeta de crédito.

Conclusión

Un patrón que hemos observado durante estas divulgaciones es que concatenar cadenas HTML sigue siendo una mala idea. Los motores de plantillas HTML modernos han logrado grandes mejoras en la mayoría de las aplicaciones, pero las aplicaciones más antiguas sin frameworks seguros no tienen este lujo. A veces, recurren a la concatenación de cadenas en JavaScript, lo que, como se ve aquí, lleva rápidamente a olvidar escapar la entrada del usuario.

Las aplicaciones de buzón de correo contienen información altamente sensible, por lo que deben tratarse con mucho cuidado. Dichas aplicaciones deben ser auditadas rigurosamente, ya que el compromiso de una puede conducir a mecanismos de restablecimiento de contraseñas, lo que resultaría en el compromiso de muchas otras cuentas conectadas.

Aikido Attack (pentesting de IA) encuentra este tipo de vulnerabilidades en las aplicaciones, de forma completamente automatizada. Si ver estos resultados le hace preguntarse sobre las vulnerabilidades XSS en sus propias aplicaciones, regístrese o póngase en contacto.

Compartir:

https://www.aikido.dev/blog/xss-vulnerabilities-in-mailcow

Suscríbete para recibir noticias

4.7/5
¿Cansado de los falsos positivos?

Prueba Aikido como otros 100k.
Empiece ahora
Obtenga un recorrido personalizado

Con la confianza de más de 100k equipos

Reservar ahora
Escanee su aplicación en busca de IDORs y rutas de ataque reales

Con la confianza de más de 100k equipos

Empezar a escanear
Vea cómo el pentesting de IA prueba su aplicación

Con la confianza de más de 100k equipos

Empezar a probar

Asegura tu plataforma ahora

Protege tu código, la nube y el entorno de ejecución en un único sistema central.
Encuentra y corrije vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.