Credential Access
T1110 — Brute Force
Adversaries may use brute force techniques — password guessing, cracking, spraying across many accounts, or credential stuffing from breached credential lists — to gain access to accounts, particularly where lockout policies are lenient, MFA is not enforced, or legacy authentication protocols are still reachable.
Correlate authentication failure volume and pattern (single account / many sources vs. many accounts / single or few sources) with any subsequent successful sign-in. Treat a success that follows a brute force or spray pattern as likely compromise until proven otherwise, regardless of whether the account itself looks privileged.
Platforms
Windows, Linux, macOS, Azure AD, Office 365, SaaS
Priority / status
high / complete
Evidence to collect
- Target username(s)
- Source IP(s) and ASN
- Authentication protocol used
- Failure count and time window
- Any successful sign-in following failures
- MFA challenge result, if any
- User agent / client app
D3-CAA · detect
Connection Attempt Analysis
Review failed sign-in bursts, password spray patterns across many accounts from a small set of sources, and any success immediately following a failure streak.
Tooling: Sentinel, Entra ID, Defender XDR
D3-SPP · harden
Strong Password Policy
Confirm lockout thresholds, smart lockout, and banned-password lists are enforced; tighten where legacy or service accounts are exempt.
Tooling: Entra ID, Group Policy
D3-MFA · harden
Multi-factor Authentication
Confirm MFA is enforced for the affected account and that legacy authentication protocols (which bypass MFA) are blocked.
Tooling: Entra ID Conditional Access
D3-CR · evict
Credential Revocation
Revoke sessions and refresh tokens if a spray attempt succeeded.
Tooling: Microsoft Graph, Entra ID
| ATT&CK Technique | D3FEND Technique | Practical SOC Action | Tooling |
|---|---|---|---|
| T1110 Brute Force | Connection Attempt Analysis | Review failed sign-in bursts, password spray patterns across many accounts from a small set of sources, and any success immediately following a failure streak. | Sentinel, Entra ID, Defender XDR |
| T1110 Brute Force | Strong Password Policy | Confirm lockout thresholds, smart lockout, and banned-password lists are enforced; tighten where legacy or service accounts are exempt. | Entra ID, Group Policy |
| T1110 Brute Force | Multi-factor Authentication | Confirm MFA is enforced for the affected account and that legacy authentication protocols (which bypass MFA) are blocked. | Entra ID Conditional Access |
| T1110 Brute Force | Credential Revocation | Revoke sessions and refresh tokens if a spray attempt succeeded. | Microsoft Graph, Entra ID |
T1110 Brute Force → D3FEND → SOC action
Investigation steps — Microsoft
- Review Entra ID SigninLogs for the affected account(s) and source IPs over the last 24-48 hours.
- Check Identity Protection risky sign-ins and risky users for spray/brute-force risk detections.
- Review whether legacy authentication (IMAP/POP/SMTP AUTH) was used to bypass MFA.
- Check Conditional Access sign-in diagnostics for any policy that was not applied or was misconfigured.
- Confirm smart lockout / custom lockout thresholds are active for the tenant.
Investigation steps — generic
- Count failed authentication attempts per account and per source over a rolling window.
- Identify whether the pattern is single-account/many-source (brute force) or many-account/few-source (spray).
- Check whether any attempt succeeded and, if so, what happened immediately after.
- Review whether the source IP or ASN is associated with known scanning/attack infrastructure.
- Real detection reference: "API - Account Takeover" -- 42Crunch API protection against account takeover'
Response actions
| Action | Risk | Automation safe | Approval required |
|---|---|---|---|
| Block attacking IPs in Conditional Access (Named Locations → | Medium | No | Yes |
| Reset passwords for all accounts successfully authenticated | Medium | No | Yes |
| Force MFA for targeted accounts if not already enforced | Low | No | Yes |
| Alert customer — provide list of targeted and compromised ac | Low | No | Yes |
KQL
25c86f99-0a91-4b7f-88f3-599a008e5ab8 — API - Account Takeover
let firstRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where (UriPath has "/api/register" or UriPath has "/api/reset") and Status == 403;
let recCount = iff((toscalar(firstRec | count) >= 20), 1, 0);
firstRec | take recCount d951d64d-0ecd-4675-8c79-6c870d5f72ac — API - Password Cracking
let loginRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/login?user=" and Status == 403;
let recCount = iff((toscalar(loginRec | count) > 10), 1, 0);
loginRec | take recCount 7bdc10d6-aa24-4ca9-9a93-802cd8761354 — API - Suspicious Login
let firstRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/login?user=" and Status in (200, 403);
let ipAddressCount = toscalar(firstRec | summarize by SourceIp | count);
let secondRec = firstRec | summarize arg_max(TimeGenerated, *) by SourceIp;
let recCount = iff((toscalar(secondRec | count) > 3), ipAddressCount, 0);
secondRec | take recCount Escalation criteria
- A spray or brute-force attempt succeeded.
- Privileged or break-glass account targeted.
- Source IP associated with known threat infrastructure.
- Legacy authentication protocol was used to bypass MFA.
- Attempts continue after initial containment.
False positive considerations
- Misconfigured service account with wrong password — Single account pattern — rule threshold of 5+ accounts won't trigger
# T1110 - Brute Force
## SOC Recommendation
Correlate authentication failure volume and pattern (single account / many sources vs. many accounts / single or few sources) with any subsequent successful sign-in. Treat a success that follows a brute force or spray pattern as likely compromise until proven otherwise, regardless of whether the account itself looks privileged.
## D3FEND Mappings
| D3FEND Technique | Relationship | Practical SOC Action |
|---|---|---|
| Connection Attempt Analysis | Detect | Review failed sign-in bursts, password spray patterns across many accounts from a small set of sources, and any success immediately following a failure streak. |
| Strong Password Policy | Harden | Confirm lockout thresholds, smart lockout, and banned-password lists are enforced; tighten where legacy or service accounts are exempt. |
| Multi-factor Authentication | Harden | Confirm MFA is enforced for the affected account and that legacy authentication protocols (which bypass MFA) are blocked. |
| Credential Revocation | Evict | Revoke sessions and refresh tokens if a spray attempt succeeded. |
## Investigation Steps
1. Review Entra ID SigninLogs for the affected account(s) and source IPs over the last 24-48 hours.
2. Check Identity Protection risky sign-ins and risky users for spray/brute-force risk detections.
3. Review whether legacy authentication (IMAP/POP/SMTP AUTH) was used to bypass MFA.
4. Check Conditional Access sign-in diagnostics for any policy that was not applied or was misconfigured.
5. Confirm smart lockout / custom lockout thresholds are active for the tenant.
## Evidence to Collect
- Target username(s)
- Source IP(s) and ASN
- Authentication protocol used
- Failure count and time window
- Any successful sign-in following failures
- MFA challenge result, if any
- User agent / client app
## Response Actions
| Action | Risk | Automation Safe | Approval Required |
|---|---|---|---|
| Block attacking IPs in Conditional Access (Named Locations → | Medium | No | Yes |
| Reset passwords for all accounts successfully authenticated | Medium | No | Yes |
| Force MFA for targeted accounts if not already enforced | Low | No | Yes |
| Alert customer — provide list of targeted and compromised ac | Low | No | Yes |
## KQL
```kql
let firstRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where (UriPath has "/api/register" or UriPath has "/api/reset") and Status == 403;
let recCount = iff((toscalar(firstRec | count) >= 20), 1, 0);
firstRec | take recCount
```
```kql
let loginRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/login?user=" and Status == 403;
let recCount = iff((toscalar(loginRec | count) > 10), 1, 0);
loginRec | take recCount
```
```kql
let firstRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/login?user=" and Status in (200, 403);
let ipAddressCount = toscalar(firstRec | summarize by SourceIp | count);
let secondRec = firstRec | summarize arg_max(TimeGenerated, *) by SourceIp;
let recCount = iff((toscalar(secondRec | count) > 3), ipAddressCount, 0);
secondRec | take recCount
```
## Escalation Criteria
- A spray or brute-force attempt succeeded.
- Privileged or break-glass account targeted.
- Source IP associated with known threat infrastructure.
- Legacy authentication protocol was used to bypass MFA.
- Attempts continue after initial containment.
## False Positive Considerations
- Misconfigured service account with wrong password — Single account pattern — rule threshold of 5+ accounts won't trigger
Generated by SOC Response Atlas by Basyrix.
Want to push this directly to Confluence? Upgrade to Basyrix Pro.
- /api/techniques/T1110.json
- /api/recommendations/T1110.json
- /api/d3fend/T1110.json
- /api/mappings/T1110.json
- /api/confluence/T1110.md
Example curl:
curl https://atlas.basyrix.com/api/recommendations/T1110.json Response:
{
"technique_id": "T1110",
"name": "Brute Force",
"priority": "high",
"status": "complete",
"version": "0.1.0",
"last_reviewed": "2026-07-23",
"generated_by": "SOC Response Atlas by Basyrix",
"tactics": [
"Credential Access"
],
"platforms": [
"Windows",
"Linux",
"macOS",
"Azure AD",
"Office 365",
"SaaS"
],
"summary": "Adversaries may use brute force techniques — password guessing, cracking, spraying across many accounts, or credential stuffing from breached credential lists — to gain access to accounts, particularly where lockout policies are lenient, MFA is not enforced, or legacy authentication protocols are still reachable.\n",
"soc_recommendation": "Correlate authentication failure volume and pattern (single account / many sources vs. many accounts / single or few sources) with any subsequent successful sign-in. Treat a success that follows a brute force or spray pattern as likely compromise until proven otherwise, regardless of whether the account itself looks privileged.\n",
"d3fend_mappings": [
{
"id": "D3-CAA",
"name": "Connection Attempt Analysis",
"relationship": "detect",
"practical_action": "Review failed sign-in bursts, password spray patterns across many accounts from a small set of sources, and any success immediately following a failure streak.\n",
"tooling": [
"Sentinel",
"Entra ID",
"Defender XDR"
]
},
{
"id": "D3-SPP",
"name": "Strong Password Policy",
"relationship": "harden",
"practical_action": "Confirm lockout thresholds, smart lockout, and banned-password lists are enforced; tighten where legacy or service accounts are exempt.\n",
"tooling": [
"Entra ID",
"Group Policy"
]
},
{
"id": "D3-MFA",
"name": "Multi-factor Authentication",
"relationship": "harden",
"practical_action": "Confirm MFA is enforced for the affected account and that legacy authentication protocols (which bypass MFA) are blocked.\n",
"tooling": [
"Entra ID Conditional Access"
]
},
{
"id": "D3-CR",
"name": "Credential Revocation",
"relationship": "evict",
"practical_action": "Revoke sessions and refresh tokens if a spray attempt succeeded.",
"tooling": [
"Microsoft Graph",
"Entra ID"
]
}
],
"investigation_steps": {
"microsoft": [
"Review Entra ID SigninLogs for the affected account(s) and source IPs over the last 24-48 hours.",
"Check Identity Protection risky sign-ins and risky users for spray/brute-force risk detections.",
"Review whether legacy authentication (IMAP/POP/SMTP AUTH) was used to bypass MFA.",
"Check Conditional Access sign-in diagnostics for any policy that was not applied or was misconfigured.",
"Confirm smart lockout / custom lockout thresholds are active for the tenant."
],
"generic": [
"Count failed authentication attempts per account and per source over a rolling window.",
"Identify whether the pattern is single-account/many-source (brute force) or many-account/few-source (spray).",
"Check whether any attempt succeeded and, if so, what happened immediately after.",
"Review whether the source IP or ASN is associated with known scanning/attack infrastructure.",
"Real detection reference: \"API - Account Takeover\" -- 42Crunch API protection against account takeover'"
]
},
"evidence_to_collect": [
"Target username(s)",
"Source IP(s) and ASN",
"Authentication protocol used",
"Failure count and time window",
"Any successful sign-in following failures",
"MFA challenge result, if any",
"User agent / client app"
],
"response_actions": [
{
"name": "Block attacking IPs in Conditional Access (Named Locations →",
"category": "Response",
"risk": "Medium",
"automation_safe": false,
"approval_required": true,
"tool": "See investigation guide",
"notes": "Block attacking IPs in Conditional Access (Named Locations → Block)"
},
{
"name": "Reset passwords for all accounts successfully authenticated",
"category": "Response",
"risk": "Medium",
"automation_safe": false,
"approval_required": true,
"tool": "See investigation guide",
"notes": "Reset passwords for all accounts successfully authenticated from those IPs"
},
{
"name": "Force MFA for targeted accounts if not already enforced",
"category": "Response",
"risk": "Low",
"automation_safe": false,
"approval_required": true,
"tool": "See investigation guide"
},
{
"name": "Alert customer — provide list of targeted and compromised ac",
"category": "Response",
"risk": "Low",
"automation_safe": false,
"approval_required": true,
"tool": "See investigation guide",
"notes": "Alert customer — provide list of targeted and compromised accounts"
}
],
"queries": {
"kql": [
{
"name": "25c86f99-0a91-4b7f-88f3-599a008e5ab8 — API - Account Takeover",
"description": "42Crunch API protection against account takeover' (Source: Microsoft's official Azure-Sentinel Detections (MIT licensed).)",
"query": "let firstRec = FortyTwoCrunchAPIProtection\n| where TimeGenerated >= ago(5m) \n| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid\n| where (UriPath has \"/api/register\" or UriPath has \"/api/reset\") and Status == 403;\nlet recCount = iff((toscalar(firstRec | count) >= 20), 1, 0);\nfirstRec | take recCount"
},
{
"name": "d951d64d-0ecd-4675-8c79-6c870d5f72ac — API - Password Cracking",
"description": "42Crunch API protection against password cracking' (Source: Microsoft's official Azure-Sentinel Detections (MIT licensed).)",
"query": "let loginRec = FortyTwoCrunchAPIProtection\n| where TimeGenerated >= ago(5m) \n| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid\n| where UriPath has \"/api/login?user=\" and Status == 403;\nlet recCount = iff((toscalar(loginRec | count) > 10), 1, 0);\nloginRec | take recCount"
},
{
"name": "7bdc10d6-aa24-4ca9-9a93-802cd8761354 — API - Suspicious Login",
"description": "42Crunch API protection against suspicious login' (Source: Microsoft's official Azure-Sentinel Detections (MIT licensed).)",
"query": "let firstRec = FortyTwoCrunchAPIProtection\n| where TimeGenerated >= ago(5m) \n| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid\n| where UriPath has \"/api/login?user=\" and Status in (200, 403);\nlet ipAddressCount = toscalar(firstRec | summarize by SourceIp | count);\nlet secondRec = firstRec | summarize arg_max(TimeGenerated, *) by SourceIp;\nlet recCount = iff((toscalar(secondRec | count) > 3), ipAddressCount, 0);\nsecondRec | take recCount"
}
],
"spl": [
{
"name": "Authentication failure volume by source",
"description": "Review authentication failure volume in Splunk.",
"query": "index=authentication action=failure earliest=-24h\n| stats count by src_ip, user\n| where count > 20\n"
}
],
"esql": [
{
"name": "50742e15-c5ef-49c8-9a2d-31221d45af58 — Okta Successful Login After Credential Attack",
"description": "(ESQL) Correlates Okta credential attack alerts with subsequent successful authentication for the same user account, identifying potential compromise following brute force, password spray, or credential stuffing attempts. (Source: Elastic's official detection-rules repository (Elastic License v2).)",
"query": "FROM .alerts-security.*, logs-okta.system-* METADATA _id, _version, _index\n// Filter for credential attack alerts OR successful Okta authentications\n| WHERE\n (\n // Credential attack alerts from the five correlated rules\n kibana.alert.rule.rule_id IN (\n \"94e734c0-2cda-11ef-84e1-f661ea17fbce\", // Credential Stuffing\n \"42bf698b-4738-445b-8231-c834ddefd8a0\", // Password Spraying\n \"23f18264-2d6d-11ef-9413-f661ea17fbce\", // DT Brute Force\n \"5889760c-9858-4b4b-879c-e299df493295\", // Distributed Brute Force\n \"2d3c27d5-d133-4152-8102-8d051619ec4a\" // Distributed Spray\n )\n )\n OR (\n // Successful Okta authentication events\n data_stream.dataset == \"okta.system\"\n AND (event.action LIKE \"user.authentication.*\" OR event.action == \"user.session.start\")\n AND okta.outcome.result == \"SUCCESS\"\n AND okta.actor.alternate_id IS NOT NULL\n )\n// correlation - alerts may store user/IP in different fields than raw logs\n| EVAL\n Esql.user = COALESCE(okta.actor.alternate_id, user.name, user.email),\n Esql.source_ip = COALESCE(okta.client.ip, client.ip, source.ip)\n// Must have user identity to correlate\n| WHERE Esql.user IS NOT NULL\n// Classify events and capture timestamps/IPs by event type\n| EVAL\n Esql.is_attack_alert = CASE(\n kibana.alert.rule.rule_id IN (\n \"94e734c0-2cda-11ef-84e1-f661ea17fbce\",\n \"42bf698b-4738-445b-8231-c834ddefd8a0\",\n \"23f18264-2d6d-11ef-9413-f661ea17fbce\",\n \"5889760c-9858-4b4b-879c-e299df493295\",\n \"2d3c27d5-d133-4152-8102-8d051619ec4a\"\n ), 1, 0\n ),\n Esql.is_success_login = CASE(\n data_stream.dataset == \"okta.system\"\n AND okta.outcome.result == \"SUCCESS\", 1, 0\n ),\n Esql.attack_ip = CASE(\n kibana.alert.rule.rule_id IN (\n \"94e734c0-2cda-11ef-84e1-f661ea17fbce\",\n \"42bf698b-4738-445b-8231-c834ddefd8a0\",\n \"23f18264-2d6d-11ef-9413-f661ea17fbce\",\n \"5889760c-9858-4b4b-879c-e299df493295\",\n \"2d3c27d5-d133-4152-8102-8d051619ec4a\"\n ), Esql.source_ip, null\n ),\n Esql.login_ip = CASE(\n data_stream.dataset == \"okta.system\"\n AND okta.outcome.result == \"SUCCESS\", Esql.source_ip, null\n ),\n Esql.attack_ts = CASE(\n kibana.alert.rule.rule_id IN (\n \"94e734c0-2cda-11ef-84e1-f661ea17fbce\",\n \"42bf698b-4738-445b-8231-c834ddefd8a0\",\n \"23f18264-2d6d-11ef-9413-f661ea17fbce\",\n \"5889760c-9858-4b4b-879c-e299df493295\",\n \"2d3c27d5-d133-4152-8102-8d051619ec4a\"\n ), @timestamp, null\n ),\n Esql.login_ts = CASE(\n data_stream.dataset == \"okta.system\"\n AND okta.outcome.result == \"SUCCESS\", @timestamp, null\n )\n// Aggregate by user (catches IP rotation: spray from IP A, login from IP B)\n| STATS\n Esql.attack_count = SUM(Esql.is_attack_alert),\n Esql.login_count = SUM(Esql.is_success_login),\n Esql.earliest_attack = MIN(Esql.attack_ts),\n Esql.latest_attack = MAX(Esql.attack_ts),\n Esql.earliest_login = MIN(Esql.login_ts),\n Esql.latest_login = MAX(Esql.login_ts),\n Esql.attack_source_ips = VALUES(Esql.attack_ip),\n Esql.login_source_ips = VALUES(Esql.login_ip),\n Esql.all_source_ips = VALUES(Esql.source_ip),\n Esql.alert_rule_ids = VALUES(kibana.alert.rule.rule_id),\n Esql.alert_rule_names = VALUES(kibana.alert.rule.name),\n Esql.event_action_values = VALUES(event.action),\n Esql.geo_country_values = VALUES(client.geo.country_name),\n Esql.geo_city_values = VALUES(client.geo.city_name),\n Esql.source_asn_values = VALUES(source.as.number),\n Esql.source_asn_org_values = VALUES(source.as.organization.name),\n Esql.user_agent_values = VALUES(okta.client.user_agent.raw_user_agent),\n Esql.device_values = VALUES(okta.client.device),\n Esql.is_proxy_values = VALUES(okta.security_context.is_proxy)\n BY Esql.user\n// Calculate time gap between latest attack and earliest subsequent login\n| EVAL Esql.attack_to_login_minutes = DATE_DIFF(\"minute\", Esql.latest_attack, Esql.earliest_login)\n// Correlation: attack BEFORE login + success within reasonable window (3 hours)\n| WHERE\n Esql.attack_count > 0\n AND Esql.login_count > 0\n AND Esql.latest_attack < Esql.earliest_login\n AND Esql.attack_to_login_minutes <= 180\n| SORT Esql.login_count DESC\n| KEEP Esql.*"
},
{
"name": "cca64114-fb8b-11ef-86e2-f661ea17fbce — Entra ID User Sign-in Brute Force Attempted",
"description": "(ESQL) 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... (Source: Elastic's official detection-rules repository (Elastic License v2).)",
"query": "from logs-azure.signinlogs-*\n\n// Define a time window for grouping and maintain the original event timestamp\n| eval Esql.time_window_date_trunc = date_trunc(15 minutes, @timestamp)\n\n// Filter relevant failed authentication events with specific error codes\n| where data_stream.dataset == \"azure.signinlogs\"\n and event.category == \"authentication\"\n and azure.signinlogs.category in (\"NonInteractiveUserSignInLogs\", \"SignInLogs\")\n and event.outcome == \"failure\"\n and azure.signinlogs.properties.authentication_requirement == \"singleFactorAuthentication\"\n and azure.signinlogs.properties.status.error_code in (\n 50034, // UserAccountNotFound\n 50126, // InvalidUsernameOrPassword\n 50055, // PasswordExpired\n 50056, // InvalidPassword\n 50057, // UserDisabled\n 50064, // CredentialValidationFailure\n 50076, // MFARequiredButNotPassed\n 50079, // MFARegistrationRequired\n 50105, // EntitlementGrantsNotFound\n 70000, // InvalidGrant\n 70008, // ExpiredOrRevokedRefreshToken\n 70043, // BadTokenDueToSignInFrequency\n 80002, // OnPremisePasswordValidatorRequestTimedOut\n 80005, // OnPremisePasswordValidatorUnpredictableWebException\n 50144, // InvalidPasswordExpiredOnPremPassword\n 50135, // PasswordChangeCompromisedPassword\n 50142, // PasswordChangeRequiredConditionalAccess\n 120000, // PasswordChangeIncorrectCurrentPassword\n 120002, // PasswordChangeInvalidNewPasswordWeak\n 120020 // PasswordChangeFailure\n )\n and azure.signinlogs.properties.user_principal_name is not null and azure.signinlogs.properties.user_principal_name != \"\"\n and user_agent.original != \"Mozilla/5.0 (compatible; MSAL 1.0) PKeyAuth/1.0\"\n and source.`as`.organization.name != \"MICROSOFT-CORP-MSN-as-BLOCK\"\n\n| stats\n Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement),\n Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id),\n Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name),\n Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id),\n Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),\n Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status),\n Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser),\n Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id),\n Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system),\n Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type),\n Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state),\n Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id),\n Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id),\n Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name),\n Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description),\n Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature),\n Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type),\n\n Esql.azure_signinlogs_properties_user_id_count_distinct = count_distinct(azure.signinlogs.properties.user_id),\n Esql.azure_signinlogs_properties_user_id_list = values(azure.signinlogs.properties.user_id),\n Esql.azure_signinlogs_result_description_values_all = values(azure.signinlogs.result_description),\n Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description),\n Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code),\n Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code),\n Esql.azure_signinlogs_properties_incoming_token_type_values_all = values(azure.signinlogs.properties.incoming_token_type),\n Esql.azure_signinlogs_properties_app_display_name_values_all = values(azure.signinlogs.properties.app_display_name),\n Esql.source_ip_values = values(source.ip),\n Esql.source_ip_count_distinct = count_distinct(source.ip),\n Esql.source_as_organization_name_values = values(source.`as`.organization.name),\n Esql.source_geo_country_name_values = values(source.geo.country_name),\n Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name),\n Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name),\n Esql.timestamp_first_seen = min(@timestamp),\n Esql.timestamp_last_seen = max(@timestamp),\n Esql.event_count = count()\nby Esql.time_window_date_trunc\n\n| eval\n Esql.duration_seconds = date_diff(\"seconds\", Esql.timestamp_first_seen, Esql.timestamp_last_seen),\n Esql.brute_force_type = case(\n Esql.azure_signinlogs_properties_user_id_count_distinct >= 10 and Esql.event_count >= 30 and Esql.azure_signinlogs_result_description_count_distinct <= 3\n and Esql.source_ip_count_distinct >= 5\n and Esql.duration_seconds <= 600\n and Esql.azure_signinlogs_properties_user_id_count_distinct > Esql.source_ip_count_distinct,\n \"credential_stuffing\",\n\n 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,\n \"password_spraying\",\n\n (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)\n or (Esql.azure_signinlogs_properties_user_id_count_distinct <= 3 and Esql.source_ip_count_distinct > 30 and Esql.event_count >= 100),\n \"password_guessing\",\n\n \"other\"\n )\n\n| keep\n Esql.time_window_date_trunc,\n Esql.brute_force_type,\n Esql.duration_seconds,\n Esql.event_count,\n Esql.timestamp_first_seen,\n Esql.timestamp_last_seen,\n Esql.azure_signinlogs_properties_user_id_count_distinct,\n Esql.azure_signinlogs_properties_user_id_list,\n Esql.azure_signinlogs_result_description_values_all,\n Esql.azure_signinlogs_result_description_count_distinct,\n Esql.azure_signinlogs_properties_status_error_code_count_distinct,\n Esql.azure_signinlogs_properties_status_error_code_values,\n Esql.azure_signinlogs_properties_incoming_token_type_values_all,\n Esql.azure_signinlogs_properties_app_display_name_values_all,\n Esql.source_ip_values,\n Esql.source_ip_count_distinct,\n Esql.source_as_organization_name_values,\n Esql.source_geo_country_name_values,\n Esql.source_geo_country_name_count_distinct,\n Esql.source_as_organization_name_count_distinct,\n Esql.azure_signinlogs_properties_authentication_requirement_values,\n Esql.azure_signinlogs_properties_app_id_values,\n Esql.azure_signinlogs_properties_app_display_name_values,\n Esql.azure_signinlogs_properties_resource_id_values,\n Esql.azure_signinlogs_properties_resource_display_name_values,\n Esql.azure_signinlogs_properties_conditional_access_status_values,\n Esql.azure_signinlogs_properties_device_detail_browser_values,\n Esql.azure_signinlogs_properties_device_detail_device_id_values,\n Esql.azure_signinlogs_properties_device_detail_operating_system_values,\n Esql.azure_signinlogs_properties_incoming_token_type_values,\n Esql.azure_signinlogs_properties_risk_state_values,\n Esql.azure_signinlogs_properties_session_id_values,\n Esql.azure_signinlogs_properties_user_id_values,\n Esql_priv.azure_signinlogs_properties_user_principal_name_values,\n Esql.azure_signinlogs_result_description_values,\n Esql.azure_signinlogs_result_signature_values,\n Esql.azure_signinlogs_result_type_values\n\n| where Esql.brute_force_type != \"other\""
}
]
},
"automation": {
"safe": [
"Add recommendation as Sentinel incident comment.",
"Create ServiceNow SecOps task.",
"Run enrichment queries.",
"Block known-malicious source IP on watchlist."
],
"approval_required": [
"Force password reset.",
"Tenant-wide legacy authentication block.",
"Disable account."
]
},
"escalation_criteria": [
"A spray or brute-force attempt succeeded.",
"Privileged or break-glass account targeted.",
"Source IP associated with known threat infrastructure.",
"Legacy authentication protocol was used to bypass MFA.",
"Attempts continue after initial containment."
],
"false_positive_considerations": [
"Misconfigured service account with wrong password — Single account pattern — rule threshold of 5+ accounts won't trigger"
],
"confluence": {
"title": "T1110 - Brute Force Response Guidance",
"labels": [
"mitre",
"attack",
"d3fend",
"secops",
"basyrix"
],
"sections": [
"summary",
"d3fend_mappings",
"investigation_steps",
"response_actions",
"queries",
"automation",
"escalation_criteria",
"false_positive_considerations"
]
}
}