Potential Microsoft 365 Brute Force via Entra ID Sign-Ins

Identifies potential brute-force attacks targeting Microsoft 365 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 Microsoft 365 services such as Exchange Online, SharePoint, or Teams.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2024/09/06"
  3integration = ["azure"]
  4maturity = "production"
  5updated_date = "2025/05/20"
  6min_stack_version = "8.17.0"
  7min_stack_comments = "Elastic ES|QL values aggregation is more performant in 8.16.5 and above."
  8
  9[rule]
 10author = ["Elastic"]
 11description = """
 12Identifies potential brute-force attacks targeting Microsoft 365 user accounts by analyzing failed sign-in patterns in
 13Microsoft Entra ID Sign-In Logs. This detection focuses on a high volume of failed interactive or non-interactive
 14authentication attempts within a short time window, often indicative of password spraying, credential stuffing, or
 15password guessing. Adversaries may use these techniques to gain unauthorized access to Microsoft 365 services such as
 16Exchange Online, SharePoint, or Teams.
 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 = "10m"
 26language = "esql"
 27license = "Elastic License v2"
 28name = "Potential Microsoft 365 Brute Force via Entra ID Sign-Ins"
 29note = """## Triage and analysis
 30
 31### Investigating Potential Microsoft 365 Brute Force via Entra ID Sign-Ins
 32
 33Identifies brute-force authentication activity against Microsoft 365 services using Entra ID sign-in logs. This detection groups and classifies failed sign-in attempts based on behavior indicative of password spraying, credential stuffing, or password guessing. The classification (`bf_type`) is included for immediate triage.
 34
 35### Possible investigation steps
 36
 37- Review `bf_type`: Classifies the brute-force behavior (`password_spraying`, `credential_stuffing`, `password_guessing`).
 38- Examine `user_id_list`: Review the identities targeted. Are they admins, service accounts, or external identities?
 39- Review `login_errors`: Multiple identical errors (e.g., `"Invalid grant..."`) suggest automated abuse or tooling.
 40- Check `ip_list` and `source_orgs`: Determine if requests came from known VPNs, hosting providers, or anonymized infrastructure.
 41- Validate `unique_ips` and `countries`: Multiple countries or IPs in a short window may indicate credential stuffing or distributed spray attempts.
 42- Compare `total_attempts` vs `duration_seconds`: High volume over a short duration supports non-human interaction.
 43- Inspect `user_agent.original` via `device_detail_browser`: Clients like `Python Requests` or `curl` are highly suspicious.
 44- Investigate `client_app_display_name` and `incoming_token_type`: Identify non-browser-based logins, token abuse or commonly mimicked clients like VSCode.
 45- Review `target_resource_display_name`: Confirm the service being targeted (e.g., SharePoint, Exchange). This may be what authorization is being attempted against.
 46- Pivot using `session_id` and `device_detail_device_id`: Determine if a single device is spraying multiple accounts.
 47- Check `conditional_access_status`: If "notApplied", determine whether conditional access is properly scoped.
 48- Correlate `user_principal_name` with successful sign-ins: Investigate surrounding logs for lateral movement or privilege abuse.
 49
 50### False positive analysis
 51
 52- Developer automation (e.g., CI/CD logins) or mobile sync errors may create noisy but benign login failures.
 53- Red team exercises or pentesting can resemble brute-force patterns.
 54- Legacy protocols or misconfigured service principals may trigger repeated login failures from the same IP or session.
 55
 56### Response and remediation
 57
 58- Notify identity or security operations teams to investigate further.
 59- Lock or reset affected user accounts if compromise is suspected.
 60- Block the source IP(s) or ASN temporarily using conditional access or firewall rules.
 61- Review tenant-wide MFA and conditional access enforcement.
 62- Audit targeted accounts for password reuse across systems or tenants.
 63- Enable lockout or throttling policies for repeated failed login attempts.
 64"""
 65references = [
 66    "https://cloud.hacktricks.xyz/pentesting-cloud/azure-security/az-unauthenticated-enum-and-initial-entry/az-password-spraying",
 67    "https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-password-spray",
 68    "https://learn.microsoft.com/en-us/purview/audit-log-detailed-properties",
 69    "https://securityscorecard.com/research/massive-botnet-targets-m365-with-stealthy-password-spraying-attacks/",
 70    "https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes",
 71    "https://github.com/0xZDH/Omnispray",
 72    "https://github.com/0xZDH/o365spray",
 73]
 74risk_score = 47
 75rule_id = "35ab3cfa-6c67-11ef-ab4d-f661ea17fbcc"
 76severity = "medium"
 77tags = [
 78    "Domain: Cloud",
 79    "Domain: SaaS",
 80    "Data Source: Azure",
 81    "Data Source: Entra ID",
 82    "Data Source: Entra ID Sign-in",
 83    "Use Case: Identity and Access Audit",
 84    "Use Case: Threat Detection",
 85    "Tactic: Credential Access",
 86    "Resources: Investigation Guide",
 87]
 88timestamp_override = "event.ingested"
 89type = "esql"
 90
 91query = '''
 92FROM logs-azure.signinlogs*
 93
 94| EVAL
 95    time_window = DATE_TRUNC(5 minutes, @timestamp),
 96    user_id = TO_LOWER(azure.signinlogs.properties.user_principal_name),
 97    ip = source.ip,
 98    login_error = azure.signinlogs.result_description,
 99    error_code = azure.signinlogs.result_type,
100    request_type = TO_LOWER(azure.signinlogs.properties.incoming_token_type),
101    app_name = TO_LOWER(azure.signinlogs.properties.app_display_name),
102    asn_org = source.`as`.organization.name,
103    country = source.geo.country_name,
104    user_agent = user_agent.original,
105    event_time = @timestamp
106
107| WHERE event.dataset == "azure.signinlogs"
108  AND event.category == "authentication"
109  AND azure.signinlogs.category IN ("NonInteractiveUserSignInLogs", "SignInLogs")
110  AND azure.signinlogs.properties.resource_display_name RLIKE "(.*)365|SharePoint|Exchange|Teams|Office(.*)"
111  AND event.outcome == "failure"
112  AND NOT STARTS_WITH("Account is locked", login_error)
113  AND azure.signinlogs.result_type IN (
114    "50034", // UserAccountNotFound
115    "50126", // InvalidUserNameOrPassword
116    "50053", // IdsLocked or too many sign-in failures
117    "70000", // InvalidGrant
118    "70008", // Expired or revoked refresh token
119    "70043", // Bad token due to sign-in frequency
120    "50057", // UserDisabled
121    "50055", // Password expired
122    "50056", // Invalid or null password
123    "50064", // Credential validation failure
124    "50076", // MFA required but not passed
125    "50079", // MFA registration required
126    "50105"  // EntitlementGrantsNotFound (no access to app)
127  )
128  AND user_id IS NOT NULL AND user_id != ""
129  AND user_agent != "Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0"
130
131| STATS
132    authentication_requirement = VALUES(azure.signinlogs.properties.authentication_requirement),
133    client_app_id = VALUES(azure.signinlogs.properties.app_id),
134    client_app_display_name = VALUES(azure.signinlogs.properties.app_display_name),
135    target_resource_id = VALUES(azure.signinlogs.properties.resource_id),
136    target_resource_display_name = VALUES(azure.signinlogs.properties.resource_display_name),
137    conditional_access_status = VALUES(azure.signinlogs.properties.conditional_access_status),
138    device_detail_browser = VALUES(azure.signinlogs.properties.device_detail.browser),
139    device_detail_device_id = VALUES(azure.signinlogs.properties.device_detail.device_id),
140    incoming_token_type = VALUES(azure.signinlogs.properties.incoming_token_type),
141    risk_state = VALUES(azure.signinlogs.properties.risk_state),
142    session_id = VALUES(azure.signinlogs.properties.session_id),
143    user_id = VALUES(azure.signinlogs.properties.user_id),
144    user_principal_name = VALUES(azure.signinlogs.properties.user_principal_name),
145    result_description = VALUES(azure.signinlogs.result_description),
146    result_signature = VALUES(azure.signinlogs.result_signature),
147    result_type = VALUES(azure.signinlogs.result_type),
148
149    unique_users = COUNT_DISTINCT(user_id),
150    user_id_list = VALUES(user_id),
151    login_errors = VALUES(login_error),
152    unique_login_errors = COUNT_DISTINCT(login_error),
153    request_types = VALUES(request_type),
154    app_names = VALUES(app_name),
155    ip_list = VALUES(ip),
156    unique_ips = COUNT_DISTINCT(ip),
157    source_orgs = VALUES(asn_org),
158    countries = VALUES(country),
159    unique_country_count = COUNT_DISTINCT(country),
160    unique_asn_orgs = COUNT_DISTINCT(asn_org),
161    first_seen = MIN(event_time),
162    last_seen = MAX(event_time),
163    total_attempts = COUNT()
164  BY time_window
165
166| EVAL
167    duration_seconds = DATE_DIFF("seconds", first_seen, last_seen),
168    bf_type = CASE(
169        unique_users >= 15 AND unique_login_errors == 1 AND total_attempts >= 10 AND duration_seconds <= 1800, "password_spraying",
170        unique_users >= 8 AND total_attempts >= 15 AND unique_login_errors <= 3 AND unique_ips <= 5 AND duration_seconds <= 600, "credential_stuffing",
171        unique_users == 1 AND unique_login_errors == 1 AND total_attempts >= 30 AND duration_seconds <= 300, "password_guessing",
172        "other"
173    )
174
175| KEEP
176    time_window, bf_type, duration_seconds, total_attempts, first_seen, last_seen,
177    unique_users, user_id_list, login_errors, unique_login_errors, request_types,
178    app_names, ip_list, unique_ips, source_orgs, countries,
179    unique_country_count, unique_asn_orgs,
180
181    authentication_requirement, client_app_id, client_app_display_name,
182    target_resource_id, target_resource_display_name, conditional_access_status,
183    device_detail_browser, device_detail_device_id, incoming_token_type,
184    risk_state, session_id, user_id, user_principal_name,
185    result_description, result_signature, result_type
186
187| WHERE bf_type != "other"
188'''
189
190
191[[rule.threat]]
192framework = "MITRE ATT&CK"
193[[rule.threat.technique]]
194id = "T1110"
195name = "Brute Force"
196reference = "https://attack.mitre.org/techniques/T1110/"
197[[rule.threat.technique.subtechnique]]
198id = "T1110.001"
199name = "Password Guessing"
200reference = "https://attack.mitre.org/techniques/T1110/001/"
201
202[[rule.threat.technique.subtechnique]]
203id = "T1110.003"
204name = "Password Spraying"
205reference = "https://attack.mitre.org/techniques/T1110/003/"
206
207[[rule.threat.technique.subtechnique]]
208id = "T1110.004"
209name = "Credential Stuffing"
210reference = "https://attack.mitre.org/techniques/T1110/004/"
211
212
213
214[rule.threat.tactic]
215id = "TA0006"
216name = "Credential Access"
217reference = "https://attack.mitre.org/tactics/TA0006/"

Triage and analysis

Investigating Potential Microsoft 365 Brute Force via Entra ID Sign-Ins

Identifies brute-force authentication activity against Microsoft 365 services using Entra ID sign-in logs. This detection groups and classifies failed sign-in attempts based on behavior indicative of password spraying, credential stuffing, or password guessing. The classification (bf_type) is included for immediate triage.

Possible investigation steps

  • Review bf_type: Classifies the brute-force behavior (password_spraying, credential_stuffing, password_guessing).
  • Examine user_id_list: Review the identities targeted. Are they admins, service accounts, or external identities?
  • Review login_errors: Multiple identical errors (e.g., "Invalid grant...") suggest automated abuse or tooling.
  • Check ip_list and source_orgs: Determine if requests came from known VPNs, hosting providers, or anonymized infrastructure.
  • Validate unique_ips and countries: Multiple countries or IPs in a short window may indicate credential stuffing or distributed spray attempts.
  • Compare total_attempts vs duration_seconds: High volume over a short duration supports non-human interaction.
  • Inspect user_agent.original via device_detail_browser: Clients like Python Requests or curl are highly suspicious.
  • Investigate client_app_display_name and incoming_token_type: Identify non-browser-based logins, token abuse or commonly mimicked clients like VSCode.
  • Review target_resource_display_name: Confirm the service being targeted (e.g., SharePoint, Exchange). This may be what authorization is being attempted against.
  • Pivot using session_id and device_detail_device_id: Determine if a single device is spraying multiple accounts.
  • Check conditional_access_status: If "notApplied", determine whether conditional access is properly scoped.
  • Correlate user_principal_name with successful sign-ins: Investigate surrounding logs for lateral movement or privilege abuse.

False positive analysis

  • Developer automation (e.g., CI/CD logins) or mobile sync errors may create noisy but benign login failures.
  • Red team exercises or pentesting can resemble brute-force patterns.
  • Legacy protocols or misconfigured service principals may trigger repeated login failures from the same IP or session.

Response and remediation

  • Notify identity or security operations teams to investigate further.
  • Lock or reset affected user accounts if compromise is suspected.
  • Block the source IP(s) or ASN temporarily using conditional access or firewall rules.
  • Review tenant-wide MFA and conditional access enforcement.
  • Audit targeted accounts for password reuse across systems or tenants.
  • Enable lockout or throttling policies for repeated failed login attempts.

References

Related rules

to-top