Microsoft Entra ID Sign-In Brute Force Activity
Identifies potential brute-force attacks targeting user accounts by analyzing failed sign-in patterns in Microsoft Entra ID Sign-In Logs. This detection focuses on a high volume of failed interactive or non-interactive authentication attempts within a short time window, often indicative of password spraying, credential stuffing, or password guessing. Adversaries may use these techniques to gain unauthorized access to applications integrated with Entra ID or to compromise valid user accounts.
Rule type: esql
Rule indices:
Rule Severity: medium
Risk Score: 47
Runs every: 15m
Searches indices from: now-60m
Maximum alerts per execution: ?
References:
- https://www.proofpoint.com/us/blog/threat-insight/attackers-unleash-teamfiltration-account-takeover-campaign
- https://www.microsoft.com/en-us/security/blog/2025/05/27/new-russia-affiliated-actor-void-blizzard-targets-critical-sectors-for-espionage/
- https://cloud.hacktricks.xyz/pentesting-cloud/azure-security/az-unauthenticated-enum-and-initial-entry/az-password-spraying
- https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-password-spray
- https://learn.microsoft.com/en-us/purview/audit-log-detailed-properties
- https://securityscorecard.com/research/massive-botnet-targets-m365-with-stealthy-password-spraying-attacks/
- https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes
- https://github.com/0xZDH/Omnispray
- https://github.com/0xZDH/o365spray
Tags:
- Domain: Cloud
- Domain: Identity
- Data Source: Azure
- Data Source: Entra ID
- Data Source: Entra ID Sign-in Logs
- Use Case: Identity and Access Audit
- Use Case: Threat Detection
- Tactic: Credential Access
- Resources: Investigation Guide
Version: ?
Rule authors:
- Elastic
Rule license: Elastic License v2
This rule detects brute-force authentication activity in Entra ID sign-in logs. It classifies failed sign-in attempts into behavior types such as password spraying, credential stuffing, or password guessing. The classification (bf_type) helps prioritize triage and incident response.
- Review
bf_type: Determines the brute-force technique being used (password_spraying,credential_stuffing, orpassword_guessing). - Examine
user_id_list: Identify if high-value accounts (e.g., administrators, service principals, federated identities) are being targeted. - Review
login_errors: Repetitive error types like"Invalid Grant"or"User Not Found"suggest automated attacks. - Check
ip_listandsource_orgs: Investigate if the activity originates from suspicious infrastructure (VPNs, hosting providers, etc.). - Validate
unique_ipsandcountries: Geographic diversity and IP volume may indicate distributed or botnet-based attacks. - Compare
total_attemptsvsduration_seconds: High rate of failures in a short time period implies automation. - Analyze
user_agent.originalanddevice_detail_browser: User agents likecurl,Python, or generic libraries may indicate scripting tools. - Investigate
client_app_display_nameandincoming_token_type: Detect potential abuse of legacy or unattended login mechanisms. - Inspect
target_resource_display_name: Understand what application or resource the attacker is trying to access. - Pivot using
session_idanddevice_detail_device_id: Determine if a device is targeting multiple accounts. - Review
conditional_access_status: If not enforced, ensure Conditional Access policies are scoped correctly.
- Legitimate automation (e.g., misconfigured scripts, sync processes) can trigger repeated failures.
- Internal red team activity or penetration tests may mimic brute-force behaviors.
- Certain service accounts or mobile clients may generate repetitive sign-in noise if not properly configured.
- Notify your identity security team for further analysis.
- Investigate and lock or reset impacted accounts if compromise is suspected.
- Block offending IPs or ASNs at the firewall, proxy, or using Conditional Access.
- Confirm MFA and Conditional Access are enforced for all user types.
- Audit targeted accounts for credential reuse across services.
- Implement account lockout or throttling for failed sign-in attempts where possible.
from logs-azure.signinlogs-*
// Define a time window for grouping and maintain the original event timestamp
| eval Esql.time_window_date_trunc = date_trunc(15 minutes, @timestamp)
// Filter relevant failed authentication events with specific error codes
| where event.dataset == "azure.signinlogs"
and event.category == "authentication"
and azure.signinlogs.category in ("NonInteractiveUserSignInLogs", "SignInLogs")
and event.outcome == "failure"
and azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication"
and azure.signinlogs.properties.status.error_code in (
50034,
50126,
50055,
50056,
50057,
50064,
50076,
50079,
50105,
70000,
70008,
70043,
80002,
80005,
50144,
50135,
50142,
120000,
120002,
120020
)
and azure.signinlogs.properties.user_principal_name is not null and azure.signinlogs.properties.user_principal_name != ""
and user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0"
and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
| stats
Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement),
Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id),
Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name),
Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id),
Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),
Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status),
Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser),
Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id),
Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system),
Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type),
Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state),
Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id),
Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id),
Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name),
Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description),
Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature),
Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type),
Esql.azure_signinlogs_properties_user_id_count_distinct = count_distinct(azure.signinlogs.properties.user_id),
Esql.azure_signinlogs_properties_user_id_list = values(azure.signinlogs.properties.user_id),
Esql.azure_signinlogs_result_description_values_all = values(azure.signinlogs.result_description),
Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description),
Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code),
Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code),
Esql.azure_signinlogs_properties_incoming_token_type_values_all = values(azure.signinlogs.properties.incoming_token_type),
Esql.azure_signinlogs_properties_app_display_name_values_all = values(azure.signinlogs.properties.app_display_name),
Esql.source_ip_values = values(source.ip),
Esql.source_ip_count_distinct = count_distinct(source.ip),
Esql.source_as_organization_name_values = values(source.`as`.organization.name),
Esql.source_geo_country_name_values = values(source.geo.country_name),
Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name),
Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name),
Esql.timestamp_first_seen = min(@timestamp),
Esql.timestamp_last_seen = max(@timestamp),
Esql.event_count = count()
by Esql.time_window_date_trunc
| eval
Esql.duration_seconds = date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen),
Esql.brute_force_type = case(
Esql.azure_signinlogs_properties_user_id_count_distinct >= 10 and Esql.event_count >= 30 and Esql.azure_signinlogs_result_description_count_distinct <= 3
and Esql.source_ip_count_distinct >= 5
and Esql.duration_seconds <= 600
and Esql.azure_signinlogs_properties_user_id_count_distinct > Esql.source_ip_count_distinct,
"credential_stuffing",
Esql.azure_signinlogs_properties_user_id_count_distinct >= 15 and Esql.azure_signinlogs_result_description_count_distinct == 1 and Esql.event_count >= 15 and Esql.duration_seconds <= 1800,
"password_spraying",
(Esql.azure_signinlogs_properties_user_id_count_distinct == 1 and Esql.azure_signinlogs_result_description_count_distinct == 1 and Esql.event_count >= 30 and Esql.duration_seconds <= 300)
or (Esql.azure_signinlogs_properties_user_id_count_distinct <= 3 and Esql.source_ip_count_distinct > 30 and Esql.event_count >= 100),
"password_guessing",
"other"
)
| keep
Esql.time_window_date_trunc,
Esql.brute_force_type,
Esql.duration_seconds,
Esql.event_count,
Esql.timestamp_first_seen,
Esql.timestamp_last_seen,
Esql.azure_signinlogs_properties_user_id_count_distinct,
Esql.azure_signinlogs_properties_user_id_list,
Esql.azure_signinlogs_result_description_values_all,
Esql.azure_signinlogs_result_description_count_distinct,
Esql.azure_signinlogs_properties_status_error_code_count_distinct,
Esql.azure_signinlogs_properties_status_error_code_values,
Esql.azure_signinlogs_properties_incoming_token_type_values_all,
Esql.azure_signinlogs_properties_app_display_name_values_all,
Esql.source_ip_values,
Esql.source_ip_count_distinct,
Esql.source_as_organization_name_values,
Esql.source_geo_country_name_values,
Esql.source_geo_country_name_count_distinct,
Esql.source_as_organization_name_count_distinct,
Esql.azure_signinlogs_properties_authentication_requirement_values,
Esql.azure_signinlogs_properties_app_id_values,
Esql.azure_signinlogs_properties_app_display_name_values,
Esql.azure_signinlogs_properties_resource_id_values,
Esql.azure_signinlogs_properties_resource_display_name_values,
Esql.azure_signinlogs_properties_conditional_access_status_values,
Esql.azure_signinlogs_properties_device_detail_browser_values,
Esql.azure_signinlogs_properties_device_detail_device_id_values,
Esql.azure_signinlogs_properties_device_detail_operating_system_values,
Esql.azure_signinlogs_properties_incoming_token_type_values,
Esql.azure_signinlogs_properties_risk_state_values,
Esql.azure_signinlogs_properties_session_id_values,
Esql.azure_signinlogs_properties_user_id_values,
Esql_priv.azure_signinlogs_properties_user_principal_name_values,
Esql.azure_signinlogs_result_description_values,
Esql.azure_signinlogs_result_signature_values,
Esql.azure_signinlogs_result_type_values
| where Esql.brute_force_type != "other"
- UserAccountNotFound
- InvalidUsernameOrPassword
- PasswordExpired
- InvalidPassword
- UserDisabled
- CredentialValidationFailure
- MFARequiredButNotPassed
- MFARegistrationRequired
- EntitlementGrantsNotFound
- InvalidGrant
- ExpiredOrRevokedRefreshToken
- BadTokenDueToSignInFrequency
- OnPremisePasswordValidatorRequestTimedOut
- OnPremisePasswordValidatorUnpredictableWebException
- InvalidPasswordExpiredOnPremPassword
- PasswordChangeCompromisedPassword
- PasswordChangeRequiredConditionalAccess
- PasswordChangeIncorrectCurrentPassword
- PasswordChangeInvalidNewPasswordWeak
- PasswordChangeFailure
Framework: MITRE ATT&CK
Tactic:
- Name: Credential Access
- Id: TA0006
- Reference URL: https://attack.mitre.org/tactics/TA0006/
Technique:
- Name: Brute Force
- Id: T1110
- Reference URL: https://attack.mitre.org/techniques/T1110/
Sub Technique:
- Name: Password Guessing
- Id: T1110.001
- Reference URL: https://attack.mitre.org/techniques/T1110/001/
Sub Technique:
- Name: Password Spraying
- Id: T1110.003
- Reference URL: https://attack.mitre.org/techniques/T1110/003/
Sub Technique:
- Name: Credential Stuffing
- Id: T1110.004
- Reference URL: https://attack.mitre.org/techniques/T1110/004/