Mailcow es un servidor de correo electrónico de código abierto y autoalojado muy utilizado que incluye todo lo necesario para gestionar los buzones de correo por cuenta propia. Para evaluar su seguridad, hemos configurado una instancia local y hemos llevado a cabo nuestras pentesting de IA . Encontramos tres vulnerabilidades XSS, incluida una vulnerabilidad crítica que permitía a atacantes no autenticados hacerse con el control de cuentas de administrador mientras consultaban sus registros en la interfaz de usuario.
El acceso a un buzón de correo electrónico puede tener graves consecuencias para la seguridad. Los datos confidenciales que contienen los correos electrónicos pueden ser interceptados, pero el acceso también permite a los atacantes utilizar la función de restablecimiento de contraseña para comprometer otras cuentas vinculadas de la víctima. Esto es precisamente lo que habría ocurrido si alguien hubiera aprovechado estas vulnerabilidades.
Todas las vulnerabilidades se comunicaron de forma responsable a Mailcow y se han solucionado a partir de la versión 2026-03b (publicada el 31 de marzo de 2026). Queremos dar las gracias al responsable del mantenimiento, FreddleSpl0it, por la fluidez del proceso y la rápida solución.
Registros de Autodiscover sin escapar
Esta vulnerabilidad, notificada en GitHub con el identificador GHSA-f9xf-vc72-rcgm, permitía a atacantes no autenticados enviar una solicitud de Autodiscover que contuviera una dirección de correo electrónico maliciosa, la cual aparecería en los registros. Cuando un administrador consultaba posteriormente dichos registros, el campo de la dirección de correo electrónico se mostraba sin escapar, lo que permitía la inyección de HTML y ataques XSS.
Empecemos por el fregadero. Mailcow permite consultar los registros de Autodiscover en una tabla del panel de administración, que se muestra mediante DataTables en dashboard.js. Esta biblioteca adolece del error habitual de interpretar cualquier valor de datos como HTML de forma predeterminada. Por lo tanto, es necesario escapar todos los valores adecuadamente 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 escape los datos introducidos escape en cada fila. En el caso de los registros de Autodiscover, item.ua (Agente de usuario) 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, hay una columna, «item.name», que no está escapada. Se trata de la dirección de correo electrónico enviada en la solicitud de Autodiscover.
¿Cuál es el mayor peligro? Las solicitudes de Autodiscover no requieren autenticación por diseño y, en este caso, la dirección de correo electrónico no se valida. La solicitud acabará en los registros y, cuando un administrador los consulte, se procesará a través de estas funciones vulnerables para mostrarse como código HTML arbitrario.
A continuación se muestra un ejemplo de solicitud que inserta <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><img src=x onerror=alert(origin)></EMailAddress>
<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
</Request>
</Autodiscover>
Ahora, cuando un administrador consulta los registros de Autodiscover (que se encuentran en Panel de control -> Registros -> Autodiscover), el código JavaScript se ejecuta y muestra una ventana emergente de alerta, lo que demuestra la existencia de una vulnerabilidad XSS en el origen de Mailcow.

Dado que esto afecta únicamente a los administradores, los atacantes pueden acceder al buzón de correo de cualquier usuario y reconfigurar la instancia. Esto hizo que la vulnerabilidad se clasificara como crítica.
Este número se ha solucionado añadiendo un escapeHtml() recorrer item.user mientras se procesan las filas.
Inserción de los nombres de los archivos adjuntos de cuarentena
Registrada con el número GHSA-2xjc-rg88-jvpp, esta vulnerabilidad se encuentra en la función «Cuarentena» de Mailcow, donde los administradores pueden examinar los archivos adjuntos marcados. Los nombres de archivo de dichos archivos adjuntos se mostraban sin codificación HTML, lo que daba pie a ataques XSS contra cualquier administrador que los visualizara. Para aprovechar esta vulnerabilidad no era necesaria ninguna autenticación por parte del atacante, aunque sí requería que la función «Cuarentena» estuviera habilitada en la instancia de destino.
Esta vez el problema está claro. Dentro de «quarantine.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 elementos es controlable por el usuario, podría dar lugar de nuevo a una inyección de HTML. Estos valores son , respectivamente , el nombre del archivo, el tipo MIME, el tamaño del archivo y el hash SHA-256 de VirusTotal. De entre ellos, el nombre del archivo parece ser el que tiene más probabilidades de contener datos introducidos por el usuario, ya que el atacante puede enviar un correo electrónico con un archivo adjunto cuyo nombre contenga 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 siguiente genera un archivo adjunto con la cadena EICAR como contenido y una carga útil XSS como nombre de archivo (consulte el aviso para ver el 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)
Una vez enviado el correo electrónico a Mailcow y tras haber sido puesto en cuarentena, el administrador puede abrir la página de Cuarentena para verlo. Al hacer clic en «Mostrar elemento», se muestra nuestro nombre de archivo y se activa el XSS:

Cabe señalar que tampoco es necesario autenticarse en Mailcow para aprovechar esta vulnerabilidad. Basta con enviar un correo electrónico malicioso al dominio de Mailcow y esperar a que el administrador lo revise. A partir de ahí, se podría tomar el control de toda la instancia mediante JavaScript, volver a leer los buzones de correo y configurar la instancia.
Este problema se solucionó añadiendo llamadas a escapeHtml() alrededor de los valores de los archivos adjuntos.
Ampliación de un ataque Self-XSS en una dirección IP que figura en el historial de inicio de sesión
La última vulnerabilidad XSS, identificada como GHSA-jprq-w83q-q62h, resultaba más interesante desde el punto de vista técnico a la hora de explotarla. La carga útil XSS se almacena en la propia cuenta del atacante, lo que significa que, normalmente, solo el atacante podría activarla (un «Self-XSS» sin impacto real). El ataque se vuelve peligroso cuando se combina con un CSRF de inicio de sesión, que obliga al navegador de la víctima a iniciar sesión en la cuenta del atacante, exponiendo la carga útil almacenada ante la persona equivocada.
La vulnerabilidad comienza con otro mecanismo de concatenación HTML sencillo 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) aparece en la tabla de inicios de sesión recientes sin que se aplique ningún escape. Normalmente, esto no debería suponer ningún problema, ya que las direcciones IP siguen un formato muy estricto que no deja margen para cargas de XSS.
Sin embargo, Mailcow no valida las direcciones IP, que provienen directamente del X-Real-IP: encabezado por defecto. Algunos proxies establecen este encabezado con la IP remota del usuario, pero si no existe ningún proxy de este tipo, se aceptará cualquier valor que se le asigne. Por lo tanto, puedes iniciar sesión mientras configuras X-Real-IP: "><img src onerror=alert(origin)> lo que guarda el valor en el historial de inicios de sesión. Al consultarlo (a través de /usuario), la carga útil XSS se muestra 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
Pero esto no es todo. Porque una víctima no va a iniciar sesión con ese encabezado de IP falso, ni va a acceder a la cuenta del atacante sin más. Por ahora, se trata de un Self-XSS.
Hay dos aspectos de esta vulnerabilidad que permiten la escalada de privilegios. En primer lugar, la carga útil se almacena de forma persistente en la cuenta del atacante. En segundo lugar, la solicitud de inicio de sesión carece de protección CSRF, lo que significa que puede activarse de forma cross-site. Un atacante puede alojar un formulario en su propio dominio que envíe automáticamente sus credenciales, iniciando sesión en nombre de la víctima sin que esta tenga que hacer nada más que 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>
En cuanto la víctima abre este formulario alojado en el sitio web malicioso, se inicia sesión automáticamente en la cuenta del atacante. A continuación, podemos redirigirla a /usuario para activar el XSS que habíamos almacenado anteriormente. Pero un momento, la víctima ahora ha iniciado sesión en la cuenta del atacante, así que, ¿cómo puede el atacante robar de la víctima ¿datos?
En este caso, el atacante puede recurrir a un ingenioso truco: mantener abierto el buzón de correo de la víctima hasta después del ataque CSRF y XSS de inicio de sesión, y luego leer su contenido mediante el acceso de mismo origen.
El nuevo flujo tendrá este aspecto:
- La víctima accede a nuestra página maliciosa (pestaña 1)
- La pestaña 1 abre una segunda página maliciosa (pestaña 2) que contiene el formulario CSRF de inicio de sesión, con un ligero retraso antes de que se ejecute
- La pestaña 1 redirige al buzón de correo de la víctima y carga los datos que el atacante quiere robar
- La pestaña 2 envía el formulario CSRF de inicio de sesión, abriendo una nueva pestaña 3 que inicia sesión en la cuenta del atacante en nombre de la víctima
- La pestaña 2 redirige a
/usuario, donde se activa el XSS almacenado - El XSS de la pestaña 2 utiliza una referencia a introducción para leer y extraer
document.body.innerHTMLen la pestaña 1, donde el buzón de la víctima sigue abierto

Esto permite leer los correos electrónicos de la cuenta de la víctima, ya que se descargaron mientras aún estaba conectada a su cuenta.
Consulte el aviso para obtener todos los detalles sobre cómo funciona el flujo de la vulnerabilidad en la práctica. Varios archivos HTML con un control preciso sobre el flujo permiten a un atacante robar correos electrónicos con solo dos clics (necesarios para abrir las dos pestañas adicionales).
Este problema se solucionó añadiendo llamadas a escapeHtml() en torno a la visualización de la dirección IP.
Remediación
Actualiza Mailcow a la versión 2026-03b o posterior, publicada el 31 de marzo de 2026. En esta versión se han corregido las tres vulnerabilidades XSS. Si utilizas Aikido, las instancias vulnerables de Mailcow se marcan automáticamente en tu feed de monitorización de superficie como un hallazgo crítico.

¿Aún no estás en Aikido? Crea una cuenta gratuita para empezar; no hace falta tarjeta de crédito.
Conclusión
Una tendencia que hemos observado durante estas revelaciones es que concatenar cadenas HTML sigue siendo una mala idea. Los motores de plantillas HTML modernos han mejorado mucho en la mayoría de las aplicaciones, pero las aplicaciones más antiguas que carecen de marcos seguros no pueden permitirse ese lujo. A veces recurren a concatenar cadenas en JavaScript, lo que, como se ve aquí, lleva rápidamente a olvidarse de escape los datos escape .
Las aplicaciones de correo electrónico contienen información altamente confidencial, por lo que deben tratarse con mucho cuidado. Estas aplicaciones deben someterse a auditorías rigurosas, ya que la vulneración de una de ellas puede afectar a los mecanismos de restablecimiento de contraseñas, lo que a su vez provocaría la vulneración de muchas otras cuentas vinculadas.
Aikido Attack (pentesting de IA) detecta este tipo de vulnerabilidades en las aplicaciones de forma totalmente automatizada. Si al ver estos resultados te has preguntado si tus propias aplicaciones podrían tener vulnerabilidades XSS, ¡ regístrate o ponte en contacto con nosotros!

