From f3b8186b0314af3c46ab533fd540ea0ff5f2206e Mon Sep 17 00:00:00 2001 From: Terrance DeJesus Date: Mon, 22 Jun 2026 14:55:30 -0400 Subject: [PATCH 1/4] [New Rule] Entra ID Potential Conditional Access MFA Bypass via First-Party Microsoft Graph Access --- detection_rules/etc/non-ecs-schema.json | 3 + ...ra_id_ca_mfa_bypass_first_party_graph.toml | 172 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml diff --git a/detection_rules/etc/non-ecs-schema.json b/detection_rules/etc/non-ecs-schema.json index 3ff5808a895..61ebe8037f8 100644 --- a/detection_rules/etc/non-ecs-schema.json +++ b/detection_rules/etc/non-ecs-schema.json @@ -218,6 +218,9 @@ "azure.signinlogs.properties.original_transfer_method": "keyword", "azure.auditlogs.properties.target_resources.0.display_name": "keyword", "azure.signinlogs.properties.authentication_details.authentication_method": "keyword", + "azure.signinlogs.properties.applied_conditional_access_policies.display_name": "keyword", + "azure.signinlogs.properties.applied_conditional_access_policies.enforced_grant_controls": "keyword", + "azure.signinlogs.properties.applied_conditional_access_policies.result": "keyword", "azure.signinlogs.properties.authentication_processing_details": "keyword", "azure.signinlogs.properties.token_protection_status_details.sign_in_session_status": "keyword", "azure.signinlogs.properties.session_id": "keyword", diff --git a/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml b/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml new file mode 100644 index 00000000000..3c035b9a926 --- /dev/null +++ b/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml @@ -0,0 +1,172 @@ +[metadata] +creation_date = "2026/06/22" +integration = ["azure"] +maturity = "production" +updated_date = "2026/06/22" + +[rule] +author = ["Elastic"] +description = """ +Identifies the first observed instance of a Microsoft first-party public client application acquiring a Microsoft Graph +token using single-factor (password-only) authentication while an MFA Conditional Access grant control went unenforced, +for a given user, application, and source autonomous system (ASN). This pattern is associated with the Conditional Access +"resource exclusion" bypass: when a tenant's "all resources" Conditional Access policy contains at least one application +exclusion, Entra ID issues tokens for low-privilege baseline scopes (User.Read, openid, profile, email) to any resource, +including Microsoft Graph, without enforcing the policy's grant controls (such as MFA). An adversary holding only a +stolen password can therefore obtain a Graph token through a trusted first-party public client (for example, Microsoft +Bing Search) and enumerate directory objects, even though the tenant requires MFA. Critically, the overall +conditional_access_status is never "failure" for this technique (the sign-in is not blocked); it is reported as "success" +or "notApplied" depending on what other policies exist in the tenant, so detections that key on Conditional Access +failures will not observe it. The reliable fingerprint is in the per-policy results: a policy whose enforced grant +control is MFA reports a result of "notApplied" for this sign-in, meaning the MFA requirement was silently not enforced +while the single-factor, password-only sign-in still succeeded. +""" +from = "now-9m" +index = ["filebeat-*", "logs-azure.signinlogs-*"] +language = "kuery" +license = "Elastic License v2" +name = "Entra ID Potential Conditional Access MFA Bypass via First-Party Microsoft Graph Access" +note = """## Triage and analysis + +### Investigating Entra ID Potential Conditional Access MFA Bypass via First-Party Microsoft Graph Access + +This New Terms rule detects the first time a given user authenticates a Microsoft first-party public client to Microsoft Graph with single-factor (password-only) authentication, from a given source ASN, within the history window. Microsoft first-party applications are identified by their owner tenant (`app_owner_tenant_id` = the Microsoft first-party services tenant `f8cdef31-a31e-4b4a-93e4-5f571e91255a`) rather than an enumerated client ID list, so the detection cannot be evaded by choosing a first-party public client that is absent from a static allowlist. The credential-only form of this bypass is specific to Microsoft first-party public clients because they are pre-consented in every tenant and cannot be blocked or unconsented by the victim; a confidential or third-party client would require the attacker to already control an application in the tenant. + +This is the telemetry signature of the Conditional Access resource-exclusion bypass. When an "all resources" Conditional Access policy carries at least one application exclusion, Entra ID will issue tokens for baseline scopes (User.Read, openid, profile, email) to Microsoft Graph without applying the policy's grant controls. An attacker with only a stolen password can authenticate a trusted first-party public client (which cannot be blocked by tenants) and obtain a Graph token without satisfying MFA. The directory's default user permissions then allow enumeration of groups, service principals, directory roles, and devices even from a `User.Read`-scoped token. + +The reliable signal is per-policy, not the aggregate. The top-level `azure.signinlogs.properties.conditional_access_status` is an aggregate verdict: `success` means no applied policy was violated (it does NOT mean MFA was satisfied), and `notApplied` means no policy applied at all. For this technique it is `success` or `notApplied` (never `failure`, because the sign-in is not blocked), and which one appears depends on what other Conditional Access policies exist in the tenant - so it is not a dependable invariant on its own. The dependable evidence sits inside `azure.signinlogs.properties.applied_conditional_access_policies`: a policy whose `enforced_grant_controls` includes `Mfa` carries a `result` of `notApplied` for this sign-in, while `azure.signinlogs.properties.authentication_requirement` is `singleFactorAuthentication` and the authentication method is `Password`. That combination - an MFA grant control that did not fire on a password-only sign-in - is the fingerprint of the bypass. + +### Possible investigation steps + +- Review `azure.signinlogs.properties.user_principal_name` to identify the user and whether they hold privileged roles or are otherwise a high-value target. +- Confirm the client via `azure.signinlogs.properties.app_display_name` and `azure.signinlogs.properties.app_id`. End-user authentication of clients such as Microsoft Bing Search to Microsoft Graph is uncommon and warrants scrutiny; everyday clients (Microsoft Teams, OneDrive, Outlook Mobile) are far more likely to be benign. +- Examine `source.ip`, `source.as.number`, `source.as.organization.name`, and `source.geo.*` for hosting-provider or geographic anomalies inconsistent with the user's normal sign-in locations. +- Inspect `azure.signinlogs.properties.applied_conditional_access_policies` for the sign-in. A policy with `enforced_grant_controls` of `Mfa` and a `result` of `notApplied`, on a `singleFactorAuthentication` / `Password` sign-in, means the MFA grant control was present but not enforced. Note the aggregate `conditional_access_status` may read `success` or `notApplied` and is not by itself indicative. +- Review whether the tenant has an "all resources" Conditional Access policy with one or more application exclusions, which is the configuration that enables this bypass. +- Pivot to `logs-azure.graphactivitylogs-*` for the same `user_principal_object_id` and `app_id` to identify directory enumeration (requests to `/groups`, `/servicePrincipals`, `/directoryRoles`, `/devices`, `/users`) shortly after the sign-in. +- Correlate using `azure.signinlogs.properties.session_id` to reconstruct the full token-acquisition sequence, including any non-interactive token redemption. + +### False positive analysis + +- Everyday first-party public clients (Microsoft Teams, OneDrive, Outlook Mobile, Microsoft 365 Copilot, Microsoft To-Do, Edge, Windows Search) legitimately acquire Microsoft Graph tokens with single-factor authentication, particularly when MFA was already satisfied earlier in the session or no MFA policy applies to that application. Expect benign first-time `(user, app, ASN)` combinations, especially during onboarding or first use of a tool. +- Users signing in from a new network (travel, VPN, new ISP) will present a new ASN and may trigger the New Terms condition once. +- In tenants with broad MFA Conditional Access policies, those policies can legitimately report `notApplied` for single-factor sign-ins that are genuinely out of policy scope (for example, sign-ins from a trusted named location or an app excluded from the policy). The `applied_conditional_access_policies` condition therefore sharpens, but does not perfectly isolate, the bypass; corroborate with the application identity and source. +- The rule scopes to all Microsoft first-party applications via `app_owner_tenant_id` rather than a fixed client ID list, so it covers every first-party public client but is correspondingly broad. New Terms on `(user, app_id, ASN)` limits this to first-occurrence events; consider allowlisting expected `(user, app)` pairs, or specific high-volume everyday first-party apps, to further reduce volume. +- Tune by excluding known developer or automation identities that routinely use these clients against Microsoft Graph. + +### Response and remediation + +- Contact the user to confirm whether they initiated the sign-in and used the detected application. +- If unauthorized, revoke the user's refresh tokens and require password reset and MFA re-registration. +- Review `logs-azure.graphactivitylogs-*` for directory enumeration or data access performed with the issued token. +- Remediate the enabling configuration: review "all resources" Conditional Access policies for application exclusions, and enable strict baseline-scope enforcement so baseline scopes do not bypass grant controls. +- Block the source IP or ASN if confirmed malicious. +""" +references = [ + "https://dirkjanm.io/bypassing-conditional-access-with-resource-exclusion/", + "https://docs.microsoft.com/en-us/azure/active-directory/reports-monitoring/reference-azure-monitor-sign-ins-log-schema" +] +risk_score = 47 +rule_id = "921544bd-e2aa-44a6-9fb9-98629c342adf" +severity = "medium" +tags = [ + "Domain: Cloud", + "Domain: Identity", + "Data Source: Azure", + "Data Source: Microsoft Entra ID", + "Data Source: Microsoft Entra ID Sign-in Logs", + "Use Case: Identity and Access Audit", + "Use Case: Threat Detection", + "Tactic: Initial Access", + "Tactic: Defense Evasion", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "new_terms" + +query = ''' +data_stream.dataset: "azure.signinlogs" and +event.outcome: "success" and +azure.signinlogs.properties.user_type: "Member" and +azure.signinlogs.properties.authentication_requirement: "singleFactorAuthentication" and +azure.signinlogs.properties.conditional_access_status: ("success" or "notApplied") and +azure.signinlogs.properties.authentication_details.authentication_method: "Password" and +azure.signinlogs.properties.applied_conditional_access_policies.enforced_grant_controls: "Mfa" and +azure.signinlogs.properties.applied_conditional_access_policies.result: "notApplied" and +( + azure.signinlogs.properties.resource_id: "00000003-0000-0000-c000-000000000000" or + azure.signinlogs.properties.resource_display_name: "Microsoft Graph" +) and +azure.signinlogs.properties.app_owner_tenant_id: "f8cdef31-a31e-4b4a-93e4-5f571e91255a" +''' + + +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1078" +name = "Valid Accounts" +reference = "https://attack.mitre.org/techniques/T1078/" +[[rule.threat.technique.subtechnique]] +id = "T1078.004" +name = "Cloud Accounts" +reference = "https://attack.mitre.org/techniques/T1078/004/" + + + +[rule.threat.tactic] +id = "TA0001" +name = "Initial Access" +reference = "https://attack.mitre.org/tactics/TA0001/" +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1556" +name = "Modify Authentication Process" +reference = "https://attack.mitre.org/techniques/T1556/" +[[rule.threat.technique.subtechnique]] +id = "T1556.009" +name = "Conditional Access Policies" +reference = "https://attack.mitre.org/techniques/T1556/009/" + + + +[rule.threat.tactic] +id = "TA0005" +name = "Defense Evasion" +reference = "https://attack.mitre.org/tactics/TA0005/" + +[rule.investigation_fields] +field_names = [ + "@timestamp", + "azure.signinlogs.properties.user_principal_name", + "azure.signinlogs.properties.app_id", + "azure.signinlogs.properties.app_display_name", + "azure.signinlogs.properties.resource_id", + "azure.signinlogs.properties.resource_display_name", + "azure.signinlogs.properties.authentication_requirement", + "azure.signinlogs.properties.conditional_access_status", + "azure.signinlogs.properties.applied_conditional_access_policies.display_name", + "azure.signinlogs.properties.applied_conditional_access_policies.enforced_grant_controls", + "azure.signinlogs.properties.applied_conditional_access_policies.result", + "azure.signinlogs.properties.is_interactive", + "azure.signinlogs.properties.session_id", + "source.ip", + "source.as.number", + "source.as.organization.name", + "source.geo.country_name", + "user_agent.original", +] + +[rule.new_terms] +field = "new_terms_fields" +value = [ + "azure.signinlogs.properties.user_principal_name", + "azure.signinlogs.properties.app_id", + "source.as.number", +] +[[rule.new_terms.history_window_start]] +field = "history_window_start" +value = "now-14d" + + From bf7ec19bff3af1331ff10f017a07b7c3992e0e1b Mon Sep 17 00:00:00 2001 From: Terrance DeJesus Date: Mon, 22 Jun 2026 15:09:34 -0400 Subject: [PATCH 2/4] linting --- ...ra_id_ca_mfa_bypass_first_party_graph.toml | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml b/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml index 3c035b9a926..b2c790614af 100644 --- a/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml +++ b/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml @@ -9,17 +9,17 @@ author = ["Elastic"] description = """ Identifies the first observed instance of a Microsoft first-party public client application acquiring a Microsoft Graph token using single-factor (password-only) authentication while an MFA Conditional Access grant control went unenforced, -for a given user, application, and source autonomous system (ASN). This pattern is associated with the Conditional Access -"resource exclusion" bypass: when a tenant's "all resources" Conditional Access policy contains at least one application -exclusion, Entra ID issues tokens for low-privilege baseline scopes (User.Read, openid, profile, email) to any resource, -including Microsoft Graph, without enforcing the policy's grant controls (such as MFA). An adversary holding only a -stolen password can therefore obtain a Graph token through a trusted first-party public client (for example, Microsoft -Bing Search) and enumerate directory objects, even though the tenant requires MFA. Critically, the overall -conditional_access_status is never "failure" for this technique (the sign-in is not blocked); it is reported as "success" -or "notApplied" depending on what other policies exist in the tenant, so detections that key on Conditional Access -failures will not observe it. The reliable fingerprint is in the per-policy results: a policy whose enforced grant -control is MFA reports a result of "notApplied" for this sign-in, meaning the MFA requirement was silently not enforced -while the single-factor, password-only sign-in still succeeded. +for a given user, application, and source autonomous system (ASN). This pattern is associated with the Conditional +Access "resource exclusion" bypass: when a tenant's "all resources" Conditional Access policy contains at least one +application exclusion, Entra ID issues tokens for low-privilege baseline scopes (User.Read, openid, profile, email) to +any resource, including Microsoft Graph, without enforcing the policy's grant controls (such as MFA). An adversary +holding only a stolen password can therefore obtain a Graph token through a trusted first-party public client (for +example, Microsoft Bing Search) and enumerate directory objects, even though the tenant requires MFA. Critically, the +overall conditional_access_status is never "failure" for this technique (the sign-in is not blocked); it is reported as +"success" or "notApplied" depending on what other policies exist in the tenant, so detections that key on Conditional +Access failures will not observe it. The reliable fingerprint is in the per-policy results: a policy whose enforced +grant control is MFA reports a result of "notApplied" for this sign-in, meaning the MFA requirement was silently not +enforced while the single-factor, password-only sign-in still succeeded. """ from = "now-9m" index = ["filebeat-*", "logs-azure.signinlogs-*"] @@ -30,11 +30,11 @@ note = """## Triage and analysis ### Investigating Entra ID Potential Conditional Access MFA Bypass via First-Party Microsoft Graph Access -This New Terms rule detects the first time a given user authenticates a Microsoft first-party public client to Microsoft Graph with single-factor (password-only) authentication, from a given source ASN, within the history window. Microsoft first-party applications are identified by their owner tenant (`app_owner_tenant_id` = the Microsoft first-party services tenant `f8cdef31-a31e-4b4a-93e4-5f571e91255a`) rather than an enumerated client ID list, so the detection cannot be evaded by choosing a first-party public client that is absent from a static allowlist. The credential-only form of this bypass is specific to Microsoft first-party public clients because they are pre-consented in every tenant and cannot be blocked or unconsented by the victim; a confidential or third-party client would require the attacker to already control an application in the tenant. +This rule fires the first time a user signs in to Microsoft Graph through a Microsoft first-party app using single-factor, password-only authentication, keyed on the user, app, and source ASN within the history window. First-party apps are matched by their owner tenant (`app_owner_tenant_id` `f8cdef31-a31e-4b4a-93e4-5f571e91255a`) instead of a fixed client ID list, so switching to a different first-party client does not get around the detection. The password-only form of this bypass depends on first-party apps: they are pre-consented in every tenant and cannot be blocked, so a stolen password is enough to use one. A third-party or confidential client would mean the attacker already controls an app in the tenant, which is a different scenario. -This is the telemetry signature of the Conditional Access resource-exclusion bypass. When an "all resources" Conditional Access policy carries at least one application exclusion, Entra ID will issue tokens for baseline scopes (User.Read, openid, profile, email) to Microsoft Graph without applying the policy's grant controls. An attacker with only a stolen password can authenticate a trusted first-party public client (which cannot be blocked by tenants) and obtain a Graph token without satisfying MFA. The directory's default user permissions then allow enumeration of groups, service principals, directory roles, and devices even from a `User.Read`-scoped token. +The behavior comes from the Conditional Access resource-exclusion bypass. If an "all resources" CA policy excludes even one application, Entra ID issues tokens for baseline scopes (User.Read, openid, profile, email) to Microsoft Graph without applying the policy's grant controls, so MFA is skipped. Default directory permissions then let that `User.Read` token read groups, service principals, directory roles, and devices. -The reliable signal is per-policy, not the aggregate. The top-level `azure.signinlogs.properties.conditional_access_status` is an aggregate verdict: `success` means no applied policy was violated (it does NOT mean MFA was satisfied), and `notApplied` means no policy applied at all. For this technique it is `success` or `notApplied` (never `failure`, because the sign-in is not blocked), and which one appears depends on what other Conditional Access policies exist in the tenant - so it is not a dependable invariant on its own. The dependable evidence sits inside `azure.signinlogs.properties.applied_conditional_access_policies`: a policy whose `enforced_grant_controls` includes `Mfa` carries a `result` of `notApplied` for this sign-in, while `azure.signinlogs.properties.authentication_requirement` is `singleFactorAuthentication` and the authentication method is `Password`. That combination - an MFA grant control that did not fire on a password-only sign-in - is the fingerprint of the bypass. +Do not read too much into the top-level `azure.signinlogs.properties.conditional_access_status` here. It shows `success` or `notApplied` (never `failure`, since the sign-in is not blocked), and which one you see depends on the other policies in the tenant. The useful evidence is in `azure.signinlogs.properties.applied_conditional_access_policies`: an MFA grant control (`enforced_grant_controls` of `Mfa`) that came back `notApplied` on a sign-in that was still single-factor and password-based. That is what the query keys on. ### Possible investigation steps @@ -64,7 +64,7 @@ The reliable signal is per-policy, not the aggregate. The top-level `azure.signi """ references = [ "https://dirkjanm.io/bypassing-conditional-access-with-resource-exclusion/", - "https://docs.microsoft.com/en-us/azure/active-directory/reports-monitoring/reference-azure-monitor-sign-ins-log-schema" + "https://docs.microsoft.com/en-us/azure/active-directory/reports-monitoring/reference-azure-monitor-sign-ins-log-schema", ] risk_score = 47 rule_id = "921544bd-e2aa-44a6-9fb9-98629c342adf" From 3bca5bc08ee963504381ebbe0dd0b44815f2e6ea Mon Sep 17 00:00:00 2001 From: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Date: Tue, 23 Jun 2026 08:39:56 -0400 Subject: [PATCH 3/4] Apply suggestion from @terrancedejesus --- ...efense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml b/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml index b2c790614af..ef65763d36b 100644 --- a/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml +++ b/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml @@ -167,6 +167,6 @@ value = [ ] [[rule.new_terms.history_window_start]] field = "history_window_start" -value = "now-14d" +value = "now-7d" From 06c8fc2c877b6608ff94dd765349de0b0733a0d9 Mon Sep 17 00:00:00 2001 From: Terrance DeJesus Date: Tue, 23 Jun 2026 09:24:21 -0400 Subject: [PATCH 4/4] adjusts mitre; adds required fields for new terms --- ...ra_id_ca_mfa_bypass_first_party_graph.toml | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml b/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml index ef65763d36b..87817e27286 100644 --- a/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml +++ b/rules/integrations/azure/defense_evasion_entra_id_ca_mfa_bypass_first_party_graph.toml @@ -97,9 +97,27 @@ azure.signinlogs.properties.applied_conditional_access_policies.result: "notAppl azure.signinlogs.properties.resource_id: "00000003-0000-0000-c000-000000000000" or azure.signinlogs.properties.resource_display_name: "Microsoft Graph" ) and -azure.signinlogs.properties.app_owner_tenant_id: "f8cdef31-a31e-4b4a-93e4-5f571e91255a" +azure.signinlogs.properties.app_owner_tenant_id: "f8cdef31-a31e-4b4a-93e4-5f571e91255a" and +azure.signinlogs.properties.user_principal_name: * and +azure.signinlogs.properties.app_id: * and +source.as.number: * ''' +[[rule.threat]] +framework = "MITRE ATT&CK" +[[rule.threat.technique]] +id = "T1556" +name = "Modify Authentication Process" +reference = "https://attack.mitre.org/techniques/T1556/" +[[rule.threat.technique.subtechnique]] +id = "T1556.009" +name = "Conditional Access Policies" +reference = "https://attack.mitre.org/techniques/T1556/009/" + +[rule.threat.tactic] +id = "TA0005" +name = "Defense Evasion" +reference = "https://attack.mitre.org/tactics/TA0005/" [[rule.threat]] framework = "MITRE ATT&CK" @@ -112,29 +130,10 @@ id = "T1078.004" name = "Cloud Accounts" reference = "https://attack.mitre.org/techniques/T1078/004/" - - [rule.threat.tactic] id = "TA0001" name = "Initial Access" reference = "https://attack.mitre.org/tactics/TA0001/" -[[rule.threat]] -framework = "MITRE ATT&CK" -[[rule.threat.technique]] -id = "T1556" -name = "Modify Authentication Process" -reference = "https://attack.mitre.org/techniques/T1556/" -[[rule.threat.technique.subtechnique]] -id = "T1556.009" -name = "Conditional Access Policies" -reference = "https://attack.mitre.org/techniques/T1556/009/" - - - -[rule.threat.tactic] -id = "TA0005" -name = "Defense Evasion" -reference = "https://attack.mitre.org/tactics/TA0005/" [rule.investigation_fields] field_names = [