
Si tienes prisa: el control que para esto no es el filtro de spam ni el MFA, es DMARC en p=quarantine, y está al final. Lo que hay en medio es por qué los otros dos no bastan, y eso es lo que vale la pena leer, porque esta clase de ataque va a seguir llegando.
El mismo correo, dos veredictos
Empieza con una observación que no cuadra. Dos correos de phishing, idénticos, palabra por palabra, salidos de la misma dirección IP contra la misma víctima. Con siete horas de diferencia. Uno acabó en cuarentena. El otro se entregó en la bandeja de entrada.
Nada de la infraestructura de correo cambió entre uno y otro. Mismo filtro, mismas reglas, mismas licencias. Lo único que cambió fue un enlace dentro del cuerpo del mensaje. Y ese detalle, un solo enlace, es toda la historia.
Esto lo vi revisando el tenant de Microsoft 365 de una organización mediana. He quitado los nombres, los dominios y las direcciones internas: lo que queda es la mecánica, que es lo que se repite de un sitio a otro.
El correo era un clásico de nómina. El asunto, reconstruido y sin el nombre real, era: «lMPORTANT: [Empleado] Payroll – Document Require Your Signature».
Fíjate en la primera palabra. No pone «IMPORTANT» con I mayúscula. Pone «lMPORTANT» con L minúscula. En la mayoría de las tipografías de un cliente de correo, una «l» minúscula y una «I» mayúscula se dibujan casi igual. Es un homoglifo, y es deliberado: ayuda a esquivar reglas que busquen la palabra exacta y, de paso, aprovecha que el ojo lee la forma, no las letras.
Pero el homoglifo no es lo interesante. Lo interesante son las cabeceras de autenticación, porque las dos copias del correo las traían idénticas:
Cabeceras de autenticación, idénticas en las dos copias:
spf=fail (sender IP is 45.xx.xx.xx) smtp.mailfrom=[dominio];
dkim=none (message not signed);
dmarc=fail action=none header.from=[dominio];
Traducido: el correo decía venir del propio dominio de la empresa (un empleado escribiéndose a sí mismo), pero salía de una IP que el dominio no autoriza, sin firma, y fallaba la comprobación de DMARC. Las dos copias. Y aun así, una entró.
Lo que decidió cuál se colaba

Aquí están las dos, lado a lado, tal como las etiquetó el propio Microsoft en la cabecera X-Forefront-Antispam-Report:
| Hora (UTC) | Enlace en el cuerpo | Veredicto | Resultado |
|---|---|---|---|
| 16:48 | [...].web.app/[...] (Firebase, crudo) | SCL:9 SFV:SPM CAT:HPHISH | cuarentena |
| 23:35 | links.notification.intuit.com/ss/c/[...] | SCL:1 SFV:NSPM CAT:NONE | entregado |
SCL es el Spam Confidence Level, de 0 a 9. Un 9 es «esto es phishing de alta confianza, apártalo». Un 1 es «esto está limpio». El mismo correo, con el mismo remitente falsificado, con el mismo fallo de autenticación, pasó de un 9 a un 1 por cambiar dónde apuntaba el botón.
En la primera copia, el botón «Review & Sign Document» llevaba a una URL cruda alojada en Firebase. Los dominios web.app gratuitos tienen mala reputación acumulada, y el clasificador de contenido lo penaliza. Nueve.
En la segunda, el atacante no cambió el destino final. Lo envolvió. El botón apuntaba a links.notification.intuit.com, el redirector de rastreo que usa Intuit (los de QuickBooks y TurboTax) para medir clics en sus propios correos legítimos. Es un dominio con reputación excelente. El clasificador ve «Intuit» y baja el pulgar. Uno.
Es un abuso de open redirect. El servicio de rastreo de Intuit acepta reenviar a donde le pidas y no comprueba a dónde. El atacante mete su enlace de phishing dentro del rastreador de Intuit, y hereda la reputación de Intuit durante el trayecto. El filtro no juzga la intención, juzga la reputación de la primera parada. Y la primera parada era impecable.
Un detalle que suele sorprender: este correo iba dentro de Safe Links, la protección de reescritura de URLs de Microsoft. No lo paró. Safe Links reescribe el enlace para comprobarlo en el momento del clic, pero el redirector de Intuit era, en efecto, un dominio de confianza, así que no había nada que marcar en la reescritura. La cadena maliciosa vivía al otro lado del redirector, fuera de su vista.
Lo que había detrás del enlace
Hasta aquí es un correo. Para saber qué buscaba de verdad, hay que seguir el enlace, y eso no se hace desde tu equipo ni desde tu red. Se hace en un entorno aislado. Yo uso any.run, un sandbox que carga la página dentro de una máquina desechable y te deja ver qué pasa sin que nada toque tu sistema.
El destino final, después de pasar por el redirector de Intuit, era una cadena de tres pasos.
Paso uno. Un dominio con TLD .vu (el código de país de Vanuatu, barato y poco vigilado). Antes siquiera de cargar, Cloudflare ya interponía su propia pantalla: «Suspected Phishing», con un botón de «Ignore & Proceed». Vale la pena parar en esto: incluso la infraestructura que sirve la página la había marcado. La víctima que llega hasta aquí ha tenido que hacer clic en «ignorar y continuar» sobre un aviso rojo de phishing. Lo hacen. La urgencia de la nómina puede más que un aviso que no se lee.
Paso dos. Un falso captcha. «Security Verification / Confirm Access», con un contador en cuenta atrás de 01:55. El captcha cumple dos funciones: parece un trámite de seguridad legítimo, y frena a los sistemas automáticos de análisis. Pero el contador es la pista de qué viene después. Un minuto y 55 segundos no es un tiempo de captcha. Es una cuenta atrás para otra cosa.
Paso tres. La página final. Se presentaba como buzón de voz de Microsoft Teams, «New voicemail from HR», con un mensaje de «Verify your Microsoft identity to play the recorded message». Y en el centro, en grande, un código de verificación con dos instrucciones: copiarlo, y pegarlo después de pulsar «Sign in with Microsoft».

Ese código es la trampa entera. Y para entender por qué, hay que entender qué es.
Por qué el MFA no sirve aquí
La reacción instintiva ante cualquier phishing es «bueno, tenemos MFA, si roban la contraseña no entran». Con este ataque, el MFA no te salva. Y la razón es que el atacante no va detrás de tu contraseña.
Ese código de la pantalla es un código de dispositivo (device code) real, generado por el atacante contra el endpoint auténtico de Microsoft, segundos antes de mostrártelo.
El flujo de código de dispositivo existe para casos legítimos: iniciar sesión en aparatos que no tienen teclado cómodo, como una smart TV o una consola. El aparato te enseña un código corto y te dice «ve a microsoft.com/devicelogin en tu celular y escribe este código». Tú lo escribes, te autenticas en tu celular con normalidad, y el aparato queda autorizado. Microsoft diseñó esto a propósito para separar la pantalla donde ves el código de la pantalla donde te autenticas.

El atacante secuestra ese mecanismo. La secuencia es esta:
- El atacante pide a Microsoft un código de dispositivo. Microsoft se lo da, con su ventana de vida (los famosos ~15 minutos, aunque el contador falso decía 01:55 para meter prisa).
- El atacante te enseña ese código en su página falsa.
- Tú, creyendo que «verificas tu identidad», copias el código y pulsas «Sign in with Microsoft», que te lleva a la página real de Microsoft.
- Ahí escribes tu usuario, tu contraseña, y apruebas tu MFA. Todo legítimo. Todo en microsoft.com de verdad. Tu gestor de contraseñas no protesta, porque el dominio es el correcto. Tu app de autenticación aprueba, porque eres tú.
- Al aprobar, no estás autorizando tu smart TV. Estás autorizando la sesión del atacante. Microsoft le entrega a él el token de acceso.
El atacante entra con un token válido, emitido por Microsoft, respaldado por tu MFA. No hay contraseña que robar porque no la necesita. No hay segundo factor que superar porque se lo has aprobado tú.
Por eso el contador de 01:55. El código de dispositivo caduca a los quince minutos, y el atacante necesita que lo uses antes. El contador aprieta mucho más de lo necesario para que no te pares a pensar, pero la prisa no es teatro sin más: detrás hay una caducidad real.
Y no es un truco viejo ni un caso aislado. El 6 de abril de 2026, el equipo de investigación de Microsoft Defender publicó el análisis de una campaña de este mismo tipo, a gran escala, que Microsoft relaciona con EvilTokens, un kit de phishing que se vende como servicio. La novedad tenía que ver justo con ese contador: para esquivar la caducidad de quince minutos, la campaña no generaba el código de antemano, sino en el momento en que la víctima hacía clic. Y los correos estaban escritos con IA, a la medida del puesto de cada víctima: licitaciones, facturas, flujos de fabricación. La recomendación de Microsoft en esa misma entrada es la de la sección siguiente: bloquear el flujo de código de dispositivo donde no haga falta.
Esta es la parte incómoda para cualquiera que haya vendido el MFA como la solución. Microsoft calcula que el MFA reduce en más de un 99 % el riesgo de que te comprometan la cuenta, y es verdad para el phishing que roba credenciales. Este ataque vive en el 1 % restante, porque no roba credenciales. Te pide que uses las tuyas, correctamente, para autorizar a otro.
Cómo saber si alguien picó
Un correo entregado no es una brecha. Entre que el correo llega y que alguien pega el código median varios «ifs». La pregunta que de verdad importa, y la que puedes responder tú mismo, es: ¿se completó algún inicio de sesión por código de dispositivo?
En Microsoft 365 esto se comprueba en los registros de inicio de sesión. Si tienes acceso a Graph, la consulta directa es esta:
Connect-MgGraph -Scopes "AuditLog.Read.All","Directory.Read.All" -NoWelcome
$desde = (Get-Date).AddDays(-16).ToString('yyyy-MM-ddTHH:mm:ssZ')
$uri = "https://graph.microsoft.com/beta/auditLogs/signIns" +
"?`$filter=createdDateTime ge $desde and authenticationProtocol eq 'deviceCode'"
$r = Invoke-MgGraphRequest -Method GET -Uri $uri
@($r.value).Count
Lo que buscas es authenticationProtocol eq 'deviceCode'. En un tenant normal, donde nadie usa smart TVs corporativas, este número debería ser cero. Cualquier resultado es, como mínimo, algo que mirar con lupa: quién, desde qué IP, contra qué aplicación.
En el caso que motiva este artículo, el resultado fue cero en dieciséis días. El correo entró, pero el ataque no se completó. Nadie pegó el código. Y esa comprobación, ese cero, es lo que convierte un susto en un incidente cerrado.
Merece la pena dejarlo como control permanente. Un inicio de sesión por código de dispositivo en una empresa que no usa el flujo para nada legítimo es una señal tan limpia que casi no tiene falsos positivos. Si tu SIEM o tu tenant permite una alerta sobre ese campo, es de las más baratas y rentables que puedes poner.
Y un paso más, porque detectar cierra el incidente pero no quita la puerta: si tu organización no usa el flujo de código de dispositivo para nada, bloquéalo. Desde 2025, el Acceso Condicional de Entra ID trae una condición de flujos de autenticación que corta el device code flow de raíz (Condiciones → Flujos de autenticación → Flujo de código de dispositivo → Bloquear). Tras la campaña de STORM-2372 de febrero de 2025, Microsoft añadió a sus políticas gestionadas una que se llama, literalmente, «Block device code flow». Aparece en tu tenant en modo de solo informe y, si no la tocas, Microsoft la activa pasados al menos 30 días. Merece la pena entrar a Acceso Condicional y mirar si ya la tienes. Ojo con las excepciones: bloquearlo a lo bruto puede interferir con el registro de dispositivos y con los dispositivos de Microsoft Teams Rooms, que sí usan este flujo (Microsoft tiene una guía para excluirlos sin abrir la puerta al resto). Despliégalo primero en modo de solo informe y mira a quién tocas antes de exigirlo.
Lo que no lo arregla
Antes de llegar al control que sí para esto, tres que no, porque son justo los tres que la gente supone que bastan.
Bloquear los dominios del atacante. Es el instinto: sale el IOC, se bloquea. Sirve para el correo de ayer. El dominio .vu de hoy le cuesta al atacante unos dólares y diez minutos, y mañana usa otro. Bloquear dominios es limpiar, no defender. Necesario, pero siempre vas por detrás.
Confiar en el filtro de contenido. Ya hemos visto que se lo saltó con un solo cambio de enlace. El filtro puntúa reputación, y la reputación se puede pedir prestada. Cualquier defensa que dependa de que el atacante use infraestructura de mala fama se cae el día que el atacante usa la de Intuit, o la de Google, o la tuya.
El MFA. Por todo lo de la sección anterior. Es imprescindible, pero contra este ataque concreto no interviene.
Los tres tienen algo en común: todos actúan después de aceptar que el correo es plausible. El filtro decide si el contenido huele mal. El MFA decide si el que se autentica eres tú. Ninguno se hace la pregunta anterior: ¿este correo viene de verdad de quien dice venir?
Lo que sí: DMARC, en la capa de autenticación
Vuelve a las cabeceras del principio:
Las cabeceras, otra vez:
spf=fail; dkim=none; dmarc=fail action=none header.from=[dominio]
dmarc=fail. Los dos correos, el bloqueado y el entregado, fallaban DMARC. Un empleado no se escribe a sí mismo desde una IP extranjera sin firmar. Es una falsificación exacta del propio dominio, y DMARC está hecho precisamente para eso.
La clave está en action=none. La política DMARC del dominio estaba en p=none, que significa «detecta el fallo, pero no hagas nada, solo repórtalo». El correo fallaba, el sistema lo veía fallar, y lo dejaba pasar de todas formas, porque eso es lo que p=none ordena.
El arreglo vive en una línea del DNS, la política del registro DMARC del dominio. Pero cuidado, no es un interruptor que pasas de p=none a bloquear y ya. Es un recorrido de tres escalones, y cada uno se sube leyendo los informes que el propio DMARC te manda:
p=none. No hace nada: solo observa y te envía informes de quién manda correo en tu nombre. Es donde arranca todo el mundo, y donde se queda casi todo el mundo.p=quarantine. Lo que falla la autenticación cae a la carpeta de no deseado. Aquí ya se habría parado el correo de este caso.p=reject. Lo que falla se rechaza en la puerta, ni llega al buzón. Es el destino final, y es al que quieres llegar.
Un registro en el escalón de cuarentena, con los informes ya activados, se parece a esto:
v=DMARC1; p=quarantine; sp=quarantine; rua=mailto:[...]; ruf=mailto:[...]; fo=1
Y aquí está el porqué de que esto sí funcione cuando el filtro no: DMARC juzga la autenticación, no el contenido, y su veredicto manda sobre el del clasificador. El correo de las 23:35 se coló porque el clasificador le dio un SCL:1. Con p=quarantine, ese SCL:1 daría igual: el correo falla la alineación DMARC, y una política de cuarentena lo aparta por ese fallo, por muy limpio que el filtro crea que está el contenido. La autenticación no vota junto al filtro, lo anula.
Es la diferencia entre un portero que mira si tu cara da mala espina (el filtro) y uno que comprueba si tu documento es auténtico (DMARC). Al segundo le da igual tu cara.
Un par de avisos, porque p=quarantine no es un interruptor que se pulsa a la ligera:
- Vas a bloquear cosas tuyas que no sabías que existían. Cualquier sistema que mande correo «desde» tu dominio sin estar en tu SPF (un CRM, una impresora, un servicio de facturación, un subdominio interno que alguien montó hace años) empezará a caer en cuarentena. Antes de cambiar nada, hay que leer los informes agregados de DMARC (los
rua) durante semanas e inventariar quién manda legítimamente en tu nombre. Subir de escalón el primer día es la mejor forma de bloquear tu propia nómina: primero se leen los informes durante semanas, y solo entonces se endurece. - La señal de los informes llega con retraso. Un correo se evalúa cuando llega, no cuando se emite el informe, y los informes agregados van uno o dos días por detrás y en lotes diarios. Después de cambiar la política, no saques conclusiones el mismo día. La primera prueba real de que muerde llega días después.
Y no te quedes en p=quarantine. La cuarentena ya muerde, pero deja el correo malo en una carpeta desde donde alguien todavía puede rescatarlo por error. Cuando los informes te confirmen, semanas seguidas, que ahí no cae nada tuyo legítimo, sube al último escalón, p=reject: ese es el final del recorrido, y son los mismos informes los que te dicen cuándo estás listo para darlo.
La prueba de que funcionó no está en los informes
Cuando se puso p=quarantine en el dominio de este caso, la confirmación de que había funcionado no fue un porcentaje en un panel. Fue el comportamiento del atacante.
A los cuatro días de endurecer la política, las auto-suplantaciones de ese dominio contra la víctima se pararon en seco. Cero. Y aparecieron, casi la misma semana, correos con el mismo patrón pero suplantando un dominio hermano de la misma organización, uno que todavía estaba en p=none.
El atacante se mudó. No abandonó a la víctima, cambió de puerta. Esa mudanza es la mejor confirmación posible de que la primera puerta se había cerrado de verdad. Si el dominio viejo le siguiera sirviendo, no se habría molestado en cambiar. Un porcentaje en un informe te dice que la política se aplica; que el atacante recoja sus cosas y se vaya a otra parte te dice que le duele.
El aviso que el filtro se comió
Un último detalle, porque es el que menos se cuenta y el que más rabia da.
La gente reportó el correo. Uno de los empleados que lo recibió reenviado hizo lo correcto: lo reenvió a varios compañeros y a la dirección interna de soporte para avisar. Reportó bien, a tiempo, exactamente como pides que hagan en cada formación de concienciación.
El aviso no llegó a soporte. ¿Por qué? Porque el correo de aviso contenía, citado, el correo de phishing original. El filtro lo volvió a clasificar como phishing (que lo era) y lo mandó a cuarentena. El reporte del usuario se lo comió el mismo filtro que no había parado el original.
La lección es incómoda: si tu único canal para que la gente reporte phishing es «reenvíalo a soporte», ese canal pasa por el filtro de contenido, y el filtro no distingue «te reenvío un phishing para avisarte» de «te mando un phishing». El reporte legítimo y el ataque son, byte a byte, casi el mismo correo.
La salida es un canal que no pase por el filtro de contenido: el botón «Report» nativo de Outlook, atado a una política de envío de informes que entregue la muestra a un buzón interno además de a Microsoft. Ese camino no se reclasifica. Sin él, tus usuarios más responsables te están avisando a una dirección que nunca recibe el aviso, y ni ellos ni tú lo saben.
Lo que queda cuando esta campaña se olvide
El dominio .vu concreto ya no importa. Estará muerto en semanas. Lo que se queda es la forma del ataque, y esa se va a repetir.
Tres cosas para llevarse:
El filtro puntúa reputación, no intención, y la reputación se alquila. Cualquier defensa que dependa de que el malo use infraestructura de mala fama tiene fecha de caducidad. El salto de SCL:9 a SCL:1 con un solo cambio de enlace es toda la demostración que necesitas.
El MFA resuelve el robo de credenciales, no la cesión de sesión. El phishing de código de dispositivo, y su primo el AiTM con proxy inverso, no roban tu contraseña: consiguen que autorices al atacante con tus propias credenciales, correctamente. Contra eso, lo que ayuda es acceso condicional que ate el token a un dispositivo conocido y a una ubicación esperada, y limitar el flujo de código de dispositivo a los pocos casos que de verdad lo usan.
La única capa que se hace la pregunta correcta es la de autenticación. SPF, DKIM y DMARC no miran el contenido. Miran si el correo viene de quien dice venir. Para la falsificación exacta de tu propio dominio, DMARC en p=quarantine o p=reject no es una capa más: es la única que corta el ataque antes de que el contenido tenga siquiera un voto.
Y una cuarta, más de fondo: cuando un correo se cuela, la pregunta no es «¿cómo mejoro el filtro?». El filtro siempre irá un paso por detrás del ingenio del que envía. La pregunta es «¿qué capa habría parado esto sin depender de acertar sobre el contenido?». Casi siempre, la respuesta está en la autenticación, y casi siempre estaba ahí, apagada, esperando a que alguien la encendiera.
Escribí esto después de encontrármelo revisando un tenant de Microsoft 365. Los datos están anonimizados a propósito: el valor está en la mecánica, no en el objetivo. Y lo de p=none no es un descuido raro de nadie: es el estado por defecto en el que se queda la mayoría de los dominios, porque terminar el recorrido de DMARC da trabajo y casi nadie lo termina. Si administras Microsoft 365 y todavía tienes algún dominio ahí, ese es el que van a usar.
Qué hacer esta semana
- Mira si alguien ya picó. Lanza la consulta de Graph de arriba y déjala como alerta: en una empresa que no usa el flujo de código de dispositivo, cualquier resultado merece una mirada.
- Busca «Block device code flow» en Acceso Condicional. Si Microsoft ya te la creó, revisa a quién toca y actívala. Si no, créala en modo de solo informe, excluye lo que de verdad lo use (Teams Rooms) y actívala después.
- Comprueba en qué escalón de DMARC está cada uno de tus dominios, también los que no usas para mandar correo. Si alguno sigue en
p=none, empieza esta semana a leer sus informesrua. - Da a tus usuarios un canal de reporte que no pase por el filtro: el botón «Report» de Outlook con una política de envío de informes a un buzón interno. Y deja de pedirles que reenvíen el phishing a soporte.
Seguir leyendo en IT Rafa
- Correo transaccional: por qué tu servidor propio no entrega, y cómo alinear SPF, DKIM y DMARC sin que se te caiga el correo legítimo
- Comprueba si tu correo está en una filtración, la herramienta del blog