Proofpoint documentó dos campañas este año que validan credenciales robadas contra Entra ID sin que se produzca un inicio de sesión exitoso. La técnica es lo suficientemente simple para explicarla en una frase: envía una contraseña al endpoint de token con un client ID que no corresponde a ninguna aplicación registrada y lee el error que recibes. Estos códigos de error son importantes, pero la filtración mayor es dónde se sitúa cada código en la secuencia de autenticación.
Tres códigos, tres significados diferentes
Las solicitudes se envían al endpoint OAuth 2.0 Resource Owner Password Credentials (ROPC): un POST que lleva nombre de usuario, contraseña y client_id. Esta técnica está específicamente limitada a ROPC, y restringirla es una de las mitigaciones.
Tres respuestas importan:
- AADSTS50034 (
UserAccountNotFound): La cuenta no está en el directorio. - AADSTS50126 (
InvalidUserNameOrPassword): Documentado como un error general que cubre cualquier fallo. - AADSTS700016: El identificador de la aplicación no se encontró en el directorio.
AADSTS50126 no debería discriminar entre un nombre de usuario incorrecto y una contraseña incorrecta, pero el comportamiento observado los separa. Un intento fallido contra un nombre de usuario inexistente no se registra en el log de inicio de sesión, porque Entra solo escribe una fila en el log una vez que ha validado un nombre de usuario como válido. Entra devuelve 50034 para cuentas ausentes y 50126 para cuentas presentes con una contraseña incorrecta, lo que es una distinción de comportamiento observada que la documentación de Microsoft difumina. El alcance también importa. Estas observaciones aplican a inquilinos gestionados en el endpoint de token. Los inquilinos federados validan contraseñas localmente mediante Pass-through Authentication (PTA) o ADFS, por lo que la ruta de error es diferente.
Los registros demuestran las diferencias en el manejo:
Request | Log row written? | What the attacker learns |
|---|---|---|
|
Account not in directory (50034)
|
No |
This username doesn't exist |
|
Account exists, wrong password (50126) |
Yes, failed sign-in |
This username exists |
|
Account exists, correct password (700016) |
Yes, failed sign-in
|
This credential pair works |
AADSTS700016 indica que un registro de aplicación está roto. Visto tras una serie de intentos de autenticación con client IDs desconocidos, confirma que el atacante tiene una contraseña válida.
Las dos campañas
UNK_pyreq2323 agotó la infraestructura de AWS desde el 14 de enero de 2026 hasta principios de marzo. Generó más de 700,000 IDs de cliente falsificados al aleatorizar los últimos seis dígitos de un identificador legítimo de aplicación de primera parte de Exchange Online (por ejemplo, 00000002-0000-0ff1-ce00-000000XXXXXX). Apuntó a más de un millón de cuentas en aproximadamente 4,000 tenants y reutilizó cada ID falsificado contra no más de 12 cuentas antes de descartarlo. Un ID de aplicación de primera parte de Microsoft está pre-consentido en cada tenant, por lo que los IDs que mantienen ese prefijo parecen legítimos en lugar de ruido.
UNK_OutFlareAZ apareció en diciembre de 2025 y regresó en febrero y marzo de 2026, operando a través de la infraestructura de Cloudflare contra más de 2 millones de usuarios con 3,7 millones de IDs de aplicaciones falsificadas.
Los dos usaron métodos de generación diferentes, y la diferencia es importante para la detección. Mientras pyreq2323 mantenía un prefijo real y reutilizaba cada ID hasta 12 veces, OutFlareAZ generaba un UUIDv4 aleatorio nuevo por solicitud, un usuario por ID, por lo que cualquier heurística basada en la reutilización no lo detecta.
(Proofpoint rastrea ambas campañas bajo su designación UNK_, que marca clústeres no atribuidos en lugar de actores nombrados.)
Detección
Los defensores vieron los fallos y los bloqueos, pero no podían distinguir cuáles de esos fallos eran confirmaciones de credenciales. La señal estaba allí, junto a cada usuario que tecleó mal su contraseña esa mañana, pero era ilegible sin saber qué código de error separar. La mitad realmente invisible es la enumeración. Los nombres de usuario inexistentes no generan ninguna fila de registro, por lo que el reconocimiento que precede a todo lo demás no deja rastro en ningún tenant.
Cada una de estas solicitudes queda registrada en los registros de inicio de sesión interactivo. Esto se basa en la propia definición de Microsoft: IsInteractive cubre los inicios de sesión donde el usuario proporciona un factor de autenticación, la definición menciona explícitamente las contraseñas, y ROPC envía una contraseña.
La firma es un nombre de aplicación en blanco:
Client ID in request |
|
|
|---|---|---|
|
Real registered app |
populated |
populated |
|
Well-formed UUID, unregistered
|
populated
|
blank |
|
Malformed client ID |
blank |
blank |
La fila dos es lo que buscas. Empareja isnotempty(AppId) con isempty(AppDisplayName) para aislar el caso bien formado pero no registrado en lugar de capturar también datos mal formados.
La consulta que importa devuelve compromisos de credenciales confirmados:
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == "700016"
| where isnotempty(AppId) and isempty(AppDisplayName)
| project TimeGenerated, UserPrincipalName, AppId, IPAddress,
UserAgent, AuthenticationProtocol, ResultDescription
| order by TimeGenerated asc
Cada fila es un par de credenciales validadas por Entra, así que trate la salida como una lista de restablecimiento de contraseñas y revocación de sesiones. No es un ticket de registro de aplicación, y el error más grave aquí es redirigir los 700016 accesos a quien posea los registros de aplicaciones.
Para la detección de campañas, pivote sobre la fuente en lugar del ID del cliente:
SigninLogs
| where TimeGenerated > ago(7d)
| where isnotempty(AppId) and isempty(AppDisplayName)
| summarize
DistinctAppIds = dcount(AppId),
TargetedUsers = dcount(UserPrincipalName),
ConfirmedCreds = countif(ResultType == "700016"),
WrongPassword = countif(ResultType == "50126"),
Lockouts = countif(ResultType == "50053"),
UserAgents = make_set(UserAgent, 10)
by IPAddress, bin(TimeGenerated, 1h)
| where DistinctAppIds > 10
| order by DistinctAppIds desc
Pivotar en IPAddress es intencionado. Cualquier heurística basada en el límite de reutilización de 12 cuentas identifica pyreq2323 y pasa por alto completamente OutFlareAZ, porque este último nunca reutiliza un ID. Ambas campañas parecen iguales desde la fuente: un origen que presenta una gran cantidad de IDs de cliente que no resuelven nada. Mantener ConfirmedCreds y Lockouts en la misma fila muestra el ruido junto a las verdaderas compromisos.
Qué cubre y qué no cubre la solución
Restringir ROPC mediante Conditional Access es la decisión correcta y Proofpoint lo recomienda. Pero quiero dejar claro qué se logra con ello.
No afecta a la enumeración. Se devuelve AADSTS50034 cuando Entra resuelve el nombre de usuario, antes de cualquier validación de credenciales y dos pasos antes de la evaluación de la política. Ninguna política de Conditional Access puede interceptar una respuesta emitida antes de consultar la política, y no se registra ninguna fila en el log independientemente de la configuración del tenant. La enumeración de nombres de usuario contra tu directorio sobrevive a todos los controles del lado del tenant, incluido un bloqueo estricto de autenticación heredada.
Una política que bloquea la autenticación heredada devuelve AADSTS53003, BlockedByConditionalAccess. Según la misma lógica de orden, 53003 solo es accesible una vez que la contraseña ha sido validada, por lo que el mensaje de error del atacante se convierte en 50126 en lugar de 53003. Código diferente, misma información.
Referencias
Compartir en
Aprende más
Acerca del autor
Darryl Baker
Investigador Principal de Seguridad
Darryl G. Baker es un Investigador Senior de Seguridad en Netwrix y una autoridad reconocida en seguridad de Identity y Active Directory. Con más de una década de experiencia en sistemas de identidad, ha liderado evaluaciones de seguridad empresarial, capacitaciones en seguridad de identidad y emulaciones de amenazas centradas en Active Directory, Entra ID y entornos Azure. Darryl ha impartido capacitaciones y demostraciones muy valoradas en BlueTeamCon, BSidesCT, The Experts Conference y Wild Wild West Hackin’ Fest. Es el arquitecto detrás de numerosos laboratorios prácticos de emulación de ataques, aprovechando las herramientas actuales de red team y blue team para ayudar a los defensores a dominar desde el análisis de rutas de ataque hasta la caza de amenazas. En sus sesiones, Darryl combina un profundo conocimiento técnico con estudios de casos reales, capacitando a los profesionales de blue team para fortalecer su postura de seguridad de identidad y defenderse contra técnicas adversarias en evolución.
Aprende más sobre este tema
Netwrix Innovation Week: Introducción a AIHound
Bring Your Own Vault: por qué "rip and replace" no es la única opción
El proveedor de su bóveda de contraseñas no es su plan de respaldo
¿Alojar su gestor de contraseñas o no?
Las credenciales estáticas siguen siendo la forma más fácil para que la IA acceda