Entra ID User Sign-in Brute Force Attempted

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.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2024/09/06"
  3integration = ["azure"]
  4maturity = "production"
  5updated_date = "2026/09/18"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Identifies potential brute-force attacks targeting user accounts by analyzing failed sign-in patterns in Microsoft Entra
 11ID Sign-In Logs. This detection focuses on a high volume of failed interactive or non-interactive authentication
 12attempts within a short time window, often indicative of password spraying, credential stuffing, or password guessing.
 13Adversaries may use these techniques to gain unauthorized access to applications integrated with Entra ID or to
 14compromise valid user accounts.
 15"""
 16false_positives = [
 17    """
 18    Automated processes that attempt to authenticate using expired credentials or have misconfigured authentication
 19    settings may lead to false positives.
 20    """,
 21]
 22from = "now-60m"
 23interval = "15m"
 24language = "esql"
 25license = "Elastic License v2"
 26name = "Entra ID User Sign-in Brute Force Attempted"
 27note = """## Triage and analysis
 28
 29### Investigating Entra ID User Sign-in Brute Force Attempted
 30
 31This 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.
 32
 33### Possible investigation steps
 34
 35- Review `bf_type`: Determines the brute-force technique being used (`password_spraying`, `credential_stuffing`, or `password_guessing`).
 36- Examine `user_id_list`: Identify if high-value accounts (e.g., administrators, service principals, federated identities) are being targeted.
 37- Review `login_errors`: Repetitive error types like `"Invalid Grant"` or `"User Not Found"` suggest automated attacks.
 38- Check `ip_list` and `source_orgs`: Investigate if the activity originates from suspicious infrastructure (VPNs, hosting providers, etc.).
 39- Validate `unique_ips` and `countries`: Geographic diversity and IP volume may indicate distributed or botnet-based attacks.
 40- Compare `total_attempts` vs `duration_seconds`: High rate of failures in a short time period implies automation.
 41- Analyze `user_agent.original` and `device_detail_browser`: User agents like `curl`, `Python`, or generic libraries may indicate scripting tools.
 42- Investigate `client_app_display_name` and `incoming_token_type`: Detect potential abuse of legacy or unattended login mechanisms.
 43- Inspect `target_resource_display_name`: Understand what application or resource the attacker is trying to access.
 44- Pivot using `session_id` and `device_detail_device_id`: Determine if a device is targeting multiple accounts.
 45- Review `conditional_access_status`: If not enforced, ensure Conditional Access policies are scoped correctly.
 46
 47### False positive analysis
 48
 49- Legitimate automation (e.g., misconfigured scripts, sync processes) can trigger repeated failures.
 50- Internal red team activity or penetration tests may mimic brute-force behaviors.
 51- Certain service accounts or mobile clients may generate repetitive sign-in noise if not properly configured.
 52
 53### Response and remediation
 54
 55- Notify your identity security team for further analysis.
 56- Investigate and lock or reset impacted accounts if compromise is suspected.
 57- Block offending IPs or ASNs at the firewall, proxy, or using Conditional Access.
 58- Confirm MFA and Conditional Access are enforced for all user types.
 59- Audit targeted accounts for credential reuse across services.
 60- Implement account lockout or throttling for failed sign-in attempts where possible.
 61"""
 62references = [
 63    "https://www.proofpoint.com/us/blog/threat-insight/attackers-unleash-teamfiltration-account-takeover-campaign",
 64    "https://www.microsoft.com/en-us/security/blog/2025/05/27/new-russia-affiliated-actor-void-blizzard-targets-critical-sectors-for-espionage/",
 65    "https://cloud.hacktricks.xyz/pentesting-cloud/azure-security/az-unauthenticated-enum-and-initial-entry/az-password-spraying",
 66    "https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-password-spray",
 67    "https://learn.microsoft.com/en-us/purview/audit-log-detailed-properties",
 68    "https://securityscorecard.com/research/massive-botnet-targets-m365-with-stealthy-password-spraying-attacks/",
 69    "https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes",
 70    "https://github.com/0xZDH/Omnispray",
 71    "https://github.com/0xZDH/o365spray",
 72]
 73risk_score = 47
 74rule_id = "cca64114-fb8b-11ef-86e2-f661ea17fbce"
 75severity = "medium"
 76tags = [
 77    "Domain: Cloud",
 78    "Domain: Identity",
 79    "Data Source: Azure",
 80    "Data Source: Entra ID",
 81    "Data Source: Entra ID Sign-In Logs",
 82    "Use Case: Identity and Access Audit",
 83    "Use Case: Threat Detection",
 84    "Tactic: Credential Access",
 85    "Resources: Investigation Guide",
 86    "Noise: Medium",
 87    "Performance: Normal",
 88    "Profile: Recommended",
 89    "Threat: Brute Force",
 90    "Rule Type: ES|QL",
 91    "Platform: Entra ID",
 92]
 93timestamp_override = "event.ingested"
 94type = "esql"
 95
 96query = '''
 97from logs-azure.signinlogs-*
 98
 99// Define a time window for grouping and maintain the original event timestamp
100| eval Esql.time_window_date_trunc = date_trunc(15 minutes, @timestamp)
101
102// Filter relevant failed authentication events with specific error codes
103| where data_stream.dataset == "azure.signinlogs"
104    and event.category == "authentication"
105    and azure.signinlogs.category in ("NonInteractiveUserSignInLogs", "SignInLogs")
106    and event.outcome == "failure"
107    and azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication"
108    and azure.signinlogs.properties.status.error_code in (
109        50034,  // UserAccountNotFound
110        50126,  // InvalidUsernameOrPassword
111        50055,  // PasswordExpired
112        50056,  // InvalidPassword
113        50057,  // UserDisabled
114        50064,  // CredentialValidationFailure
115        50076,  // MFARequiredButNotPassed
116        50079,  // MFARegistrationRequired
117        50105,  // EntitlementGrantsNotFound
118        70000,  // InvalidGrant
119        70008,  // ExpiredOrRevokedRefreshToken
120        70043,  // BadTokenDueToSignInFrequency
121        80002,  // OnPremisePasswordValidatorRequestTimedOut
122        80005,  // OnPremisePasswordValidatorUnpredictableWebException
123        50144,  // InvalidPasswordExpiredOnPremPassword
124        50135,  // PasswordChangeCompromisedPassword
125        50142,  // PasswordChangeRequiredConditionalAccess
126        120000, // PasswordChangeIncorrectCurrentPassword
127        120002, // PasswordChangeInvalidNewPasswordWeak
128        120020  // PasswordChangeFailure
129    )
130    and azure.signinlogs.properties.user_principal_name is not null and azure.signinlogs.properties.user_principal_name != ""
131    and user_agent.original != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0"
132    and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
133
134| stats
135    Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement),
136    Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id),
137    Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name),
138    Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id),
139    Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),
140    Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status),
141    Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser),
142    Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id),
143    Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system),
144    Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type),
145    Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state),
146    Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id),
147    Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id),
148    Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name),
149    Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description),
150    Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature),
151    Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type),
152
153    Esql.azure_signinlogs_properties_user_id_count_distinct = count_distinct(azure.signinlogs.properties.user_id),
154    Esql.azure_signinlogs_properties_user_id_list = values(azure.signinlogs.properties.user_id),
155    Esql.azure_signinlogs_result_description_values_all = values(azure.signinlogs.result_description),
156    Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description),
157    Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code),
158    Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code),
159    Esql.azure_signinlogs_properties_incoming_token_type_values_all = values(azure.signinlogs.properties.incoming_token_type),
160    Esql.azure_signinlogs_properties_app_display_name_values_all = values(azure.signinlogs.properties.app_display_name),
161    Esql.source_ip_values = values(source.ip),
162    Esql.source_ip_count_distinct = count_distinct(source.ip),
163    Esql.source_as_organization_name_values = values(source.`as`.organization.name),
164    Esql.source_geo_country_name_values = values(source.geo.country_name),
165    Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name),
166    Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name),
167    Esql.timestamp_first_seen = min(@timestamp),
168    Esql.timestamp_last_seen = max(@timestamp),
169    Esql.event_count = count()
170by Esql.time_window_date_trunc
171
172| eval
173    Esql.duration_seconds = date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen),
174    Esql.brute_force_type = case(
175        Esql.azure_signinlogs_properties_user_id_count_distinct >= 10 and Esql.event_count >= 30 and Esql.azure_signinlogs_result_description_count_distinct <= 3
176            and Esql.source_ip_count_distinct >= 5
177            and Esql.duration_seconds <= 600
178            and Esql.azure_signinlogs_properties_user_id_count_distinct > Esql.source_ip_count_distinct,
179        "credential_stuffing",
180
181        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,
182        "password_spraying",
183
184        (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)
185            or (Esql.azure_signinlogs_properties_user_id_count_distinct <= 3 and Esql.source_ip_count_distinct > 30 and Esql.event_count >= 100),
186        "password_guessing",
187
188        "other"
189    )
190
191| keep
192    Esql.time_window_date_trunc,
193    Esql.brute_force_type,
194    Esql.duration_seconds,
195    Esql.event_count,
196    Esql.timestamp_first_seen,
197    Esql.timestamp_last_seen,
198    Esql.azure_signinlogs_properties_user_id_count_distinct,
199    Esql.azure_signinlogs_properties_user_id_list,
200    Esql.azure_signinlogs_result_description_values_all,
201    Esql.azure_signinlogs_result_description_count_distinct,
202    Esql.azure_signinlogs_properties_status_error_code_count_distinct,
203    Esql.azure_signinlogs_properties_status_error_code_values,
204    Esql.azure_signinlogs_properties_incoming_token_type_values_all,
205    Esql.azure_signinlogs_properties_app_display_name_values_all,
206    Esql.source_ip_values,
207    Esql.source_ip_count_distinct,
208    Esql.source_as_organization_name_values,
209    Esql.source_geo_country_name_values,
210    Esql.source_geo_country_name_count_distinct,
211    Esql.source_as_organization_name_count_distinct,
212    Esql.azure_signinlogs_properties_authentication_requirement_values,
213    Esql.azure_signinlogs_properties_app_id_values,
214    Esql.azure_signinlogs_properties_app_display_name_values,
215    Esql.azure_signinlogs_properties_resource_id_values,
216    Esql.azure_signinlogs_properties_resource_display_name_values,
217    Esql.azure_signinlogs_properties_conditional_access_status_values,
218    Esql.azure_signinlogs_properties_device_detail_browser_values,
219    Esql.azure_signinlogs_properties_device_detail_device_id_values,
220    Esql.azure_signinlogs_properties_device_detail_operating_system_values,
221    Esql.azure_signinlogs_properties_incoming_token_type_values,
222    Esql.azure_signinlogs_properties_risk_state_values,
223    Esql.azure_signinlogs_properties_session_id_values,
224    Esql.azure_signinlogs_properties_user_id_values,
225    Esql_priv.azure_signinlogs_properties_user_principal_name_values,
226    Esql.azure_signinlogs_result_description_values,
227    Esql.azure_signinlogs_result_signature_values,
228    Esql.azure_signinlogs_result_type_values
229
230| where Esql.brute_force_type != "other"
231'''
232
233
234[[rule.threat]]
235framework = "MITRE ATT&CK"
236[[rule.threat.technique]]
237id = "T1110"
238name = "Brute Force"
239reference = "https://attack.mitre.org/techniques/T1110/"
240[[rule.threat.technique.subtechnique]]
241id = "T1110.001"
242name = "Password Guessing"
243reference = "https://attack.mitre.org/techniques/T1110/001/"
244
245[[rule.threat.technique.subtechnique]]
246id = "T1110.003"
247name = "Password Spraying"
248reference = "https://attack.mitre.org/techniques/T1110/003/"
249
250[[rule.threat.technique.subtechnique]]
251id = "T1110.004"
252name = "Credential Stuffing"
253reference = "https://attack.mitre.org/techniques/T1110/004/"
254
255
256
257[rule.threat.tactic]
258id = "TA0006"
259name = "Credential Access"
260reference = "https://attack.mitre.org/tactics/TA0006/"

Triage and analysis

Investigating Entra ID User Sign-in Brute Force Attempted

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.

Possible investigation steps

  • Review bf_type: Determines the brute-force technique being used (password_spraying, credential_stuffing, or password_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_list and source_orgs: Investigate if the activity originates from suspicious infrastructure (VPNs, hosting providers, etc.).
  • Validate unique_ips and countries: Geographic diversity and IP volume may indicate distributed or botnet-based attacks.
  • Compare total_attempts vs duration_seconds: High rate of failures in a short time period implies automation.
  • Analyze user_agent.original and device_detail_browser: User agents like curl, Python, or generic libraries may indicate scripting tools.
  • Investigate client_app_display_name and incoming_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_id and device_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.

False positive analysis

  • 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.

Response and remediation

  • 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.

References

Related rules

to-top