Netwrix 1Secure ofrece visibilidad unificada de datos e identidad - gratis durante 14 días con acceso completo.Comience una prueba gratuita

Centro de recursosBlog

El código de error es la pista

El código de error es la pista

Oct 2, 2026

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

AppId

AppDisplayName

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

Retrato de darryl baker

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.