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