Entra ID Federated Identity Credential Modified

Detects when a federated identity credential is added to an Entra ID application, or when the issuer or subject of an existing credential is changed. Adversaries with application-management rights can federate the application to an attacker-controlled identity provider, or re-scope an existing trust on a shared issuer such as GitHub Actions to an identity they control, and authenticate as its service principal without creating a client secret.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2025/07/14"
  3integration = ["azure"]
  4maturity = "production"
  5min_stack_comments = "Requires ES|QL JSON_EXTRACT to read federated credential Id, Issuer, and Subject values from the audit JSON."
  6min_stack_version = "9.4.0"
  7updated_date = "2026/10/01"
  8
  9[rule]
 10author = ["Elastic"]
 11description = """
 12Detects when a federated identity credential is added to an Entra ID application, or when the issuer or subject of an
 13existing credential is changed. Adversaries with application-management rights can federate the application to an
 14attacker-controlled identity provider, or re-scope an existing trust on a shared issuer such as GitHub Actions to an
 15identity they control, and authenticate as its service principal without creating a client secret.
 16"""
 17from = "now-9m"
 18language = "esql"
 19license = "Elastic License v2"
 20name = "Entra ID Federated Identity Credential Modified"
 21note = """## Triage and analysis
 22
 23### Investigating Entra ID Federated Identity Credential Modified
 24
 25This rule detects changes to the federated identity credentials on an Entra ID application. `Esql.fic_change` says which change was observed:
 26
 27- `fic_created`: the credential collection was an empty array and now holds a credential.
 28- `fic_added`: a credential `Id` is present in the new value and absent from the old value, and the credential count increased.
 29- `fic_issuer_changed`: a credential `Id` is in both values, but its issuer URL changed.
 30- `fic_subject_changed`: a credential `Id` is in both values with the same issuer, but its subject identifier changed. On shared public issuers such as GitHub Actions or GitLab the issuer is identical for every tenant, so the subject is the only value scoping the trust to a specific repository, branch, or environment.
 31
 32The query expands the new credential array into one row per credential and matches each one to the old array by `Id`, so a change to the audience of an existing credential does not alert, and neither does removing a credential.
 33
 34### Possible investigation steps
 35
 36- Review `azure.auditlogs.properties.initiated_by.user.userPrincipalName` and `ipAddress` to identify who made the change and from where.
 37- Use `Esql.fic_change` to decide whether this is a new trust, an existing trust pointed at a different issuer, or an existing trust re-scoped to a different subject.
 38- For an issuer change, compare `Esql.fic_old_issuer` and `Esql.fic_new_issuer` for the credential in `Esql.fic_id`.
 39- For a subject change, compare `Esql.fic_old_subject` and `Esql.fic_new_subject`. Confirm the new repository, organization, branch, or environment belongs to the organization and that the change was expected by the pipeline owner. A subject that is now null may indicate a switch to a claims matching expression; review the new credential object in `azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`.
 40- For an added credential, review `Esql.fic_id`, `Esql.fic_new_issuer`, and `Esql.fic_new_subject`.
 41- Check whether the issuer domain is controlled by the organization.
 42- Review the application's assigned roles and permissions to understand the scope of access.
 43- Use `azure.correlation_id` to pivot to related changes in the same session.
 44- Check for subsequent federated sign-ins as the application's service principal.
 45
 46### False positive analysis
 47
 48- Onboarding a workload to workload identity federation adds the first credential.
 49- DevOps teams add further credentials for additional repositories, branches, or environments.
 50- Migrations from one identity provider to another change the issuer on an existing credential id.
 51- Renaming a branch, transferring a repository to a new organization, or moving a pipeline from a branch-scoped to an environment-scoped subject changes the subject on an existing credential id.
 52- Validate the change with the application owner before taking action.
 53
 54### Response and remediation
 55
 56- If the change is unauthorized, immediately remove or revert the federated identity credential.
 57- Rotate any secrets or certificates associated with the application.
 58- Review Azure sign-in logs and audit logs for any unauthorized activity using the application's identity.
 59- Disable the application or service principal if compromise is confirmed.
 60- Investigate how the unauthorized change occurred (e.g., compromised admin account, over-privileged service principal).
 61- Implement conditional access policies and PIM (Privileged Identity Management) to protect application management operations.
 62"""
 63references = [
 64    "https://dirkjanm.io/persisting-with-federated-credentials-entra-apps-managed-identities/",
 65    "https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation",
 66]
 67risk_score = 73
 68rule_id = "498e4094-60e7-11f0-8847-f661ea17fbcd"
 69setup = """### Microsoft Entra ID Audit Logs
 70This rule requires the Azure integration with Microsoft Entra ID Audit Logs data stream ingesting in your Elastic Stack deployment. For more information, refer to the [Microsoft Entra ID Audit Logs integration documentation](https://www.elastic.co/docs/reference/integrations/azure/adlogs).
 71"""
 72severity = "high"
 73tags = [
 74    "Domain: Cloud",
 75    "Domain: Identity",
 76    "Data Source: Azure",
 77    "Data Source: Microsoft Entra ID Audit Logs",
 78    "Use Case: Identity and Access Audit",
 79    "Tactic: Persistence",
 80    "Tactic: Privilege Escalation",
 81    "Tactic: Defense Evasion",
 82    "Resources: Investigation Guide",
 83    "Profile: Beta",
 84    "Rule Type: ES|QL",
 85    "Platform: Entra ID",
 86]
 87timestamp_override = "event.ingested"
 88type = "esql"
 89
 90query = '''
 91from logs-azure.auditlogs-* metadata _id, _version, _index
 92| where event.action == "Update application"
 93| where `azure.auditlogs.properties.target_resources.0.modified_properties.0.display_name` == "FederatedIdentityCredentials"
 94| eval Esql.fic_old_value = `azure.auditlogs.properties.target_resources.0.modified_properties.0.old_value`
 95| eval Esql.fic_new_value = `azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`
 96// each credential is one JSON object, so the number of "{" is the credential count
 97| eval Esql.fic_old_count = mv_count(split(Esql.fic_old_value, "{")) - 1
 98| eval Esql.fic_new_count = mv_count(split(Esql.fic_new_value, "{")) - 1
 99// removals never alert
100| where Esql.fic_new_count > 0 and Esql.fic_new_count >= Esql.fic_old_count
101// strip the outer brackets and expand the new array into one row per credential object
102| eval Esql.fic_object = split(replace(substring(Esql.fic_new_value, 2, length(Esql.fic_new_value) - 2), "\\},\\{", "}\n{"), "\n")
103| mv_expand Esql.fic_object
104| eval Esql.fic_id = json_extract(Esql.fic_object, "Id"),
105       Esql.fic_new_issuer = json_extract(Esql.fic_object, "Issuer"),
106       Esql.fic_new_subject = json_extract(Esql.fic_object, "Subject")
107// locate the same credential Id in the old array and rebuild that object to read its previous issuer and subject
108| eval Esql.fic_old_id_pos = locate(Esql.fic_old_value, concat("\"Id\":\"", Esql.fic_id, "\""))
109| eval Esql.fic_old_object = case(
110    Esql.fic_old_id_pos > 0,
111    concat("{", mv_last(split(substring(Esql.fic_old_value, 1, Esql.fic_old_id_pos - 1), "{")), mv_first(split(substring(Esql.fic_old_value, Esql.fic_old_id_pos), "}")), "}"),
112    null
113  )
114| eval Esql.fic_old_issuer = json_extract(Esql.fic_old_object, "Issuer"),
115       Esql.fic_old_subject = json_extract(Esql.fic_old_object, "Subject")
116// subject is null on flexible credentials that use a claims matching expression, so coalesce before comparing
117| eval Esql.fic_change = case(
118    Esql.fic_old_count == 0, "fic_created",
119    Esql.fic_old_id_pos == 0 and Esql.fic_new_count > Esql.fic_old_count, "fic_added",
120    Esql.fic_old_issuer != Esql.fic_new_issuer, "fic_issuer_changed",
121    coalesce(Esql.fic_old_subject, "") != coalesce(Esql.fic_new_subject, ""), "fic_subject_changed",
122    "none"
123  )
124| where Esql.fic_change != "none"
125// the raw old/new arrays stay on the alert under azure.*; drop the working copies and intermediates
126| drop Esql.fic_old_value, Esql.fic_new_value, Esql.fic_object, Esql.fic_old_object, Esql.fic_old_id_pos
127| keep @timestamp, Esql.*, azure.*, event.*, cloud.*, related.*, tags, source.*, agent.*, client.*, _id, _version, _index, data_stream.namespace
128'''
129
130
131[[rule.threat]]
132framework = "MITRE ATT&CK"
133[[rule.threat.technique]]
134id = "T1098"
135name = "Account Manipulation"
136reference = "https://attack.mitre.org/techniques/T1098/"
137[[rule.threat.technique.subtechnique]]
138id = "T1098.001"
139name = "Additional Cloud Credentials"
140reference = "https://attack.mitre.org/techniques/T1098/001/"
141
142
143
144[rule.threat.tactic]
145id = "TA0003"
146name = "Persistence"
147reference = "https://attack.mitre.org/tactics/TA0003/"
148[[rule.threat]]
149framework = "MITRE ATT&CK"
150[[rule.threat.technique]]
151id = "T1098"
152name = "Account Manipulation"
153reference = "https://attack.mitre.org/techniques/T1098/"
154[[rule.threat.technique.subtechnique]]
155id = "T1098.001"
156name = "Additional Cloud Credentials"
157reference = "https://attack.mitre.org/techniques/T1098/001/"
158[[rule.threat.technique]]
159id = "T1484"
160name = "Domain or Tenant Policy Modification"
161reference = "https://attack.mitre.org/techniques/T1484/"
162[[rule.threat.technique.subtechnique]]
163id = "T1484.002"
164name = "Trust Modification"
165reference = "https://attack.mitre.org/techniques/T1484/002/"
166
167
168
169[rule.threat.tactic]
170id = "TA0004"
171name = "Privilege Escalation"
172reference = "https://attack.mitre.org/tactics/TA0004/"
173[[rule.threat]]
174framework = "MITRE ATT&CK"
175[[rule.threat.technique]]
176id = "T1484"
177name = "Domain or Tenant Policy Modification"
178reference = "https://attack.mitre.org/techniques/T1484/"
179[[rule.threat.technique.subtechnique]]
180id = "T1484.002"
181name = "Trust Modification"
182reference = "https://attack.mitre.org/techniques/T1484/002/"
183
184
185
186[rule.threat.tactic]
187id = "TA0005"
188name = "Defense Evasion"
189reference = "https://attack.mitre.org/tactics/TA0005/"

Triage and analysis

Investigating Entra ID Federated Identity Credential Modified

This rule detects changes to the federated identity credentials on an Entra ID application. Esql.fic_change says which change was observed:

  • fic_created: the credential collection was an empty array and now holds a credential.
  • fic_added: a credential Id is present in the new value and absent from the old value, and the credential count increased.
  • fic_issuer_changed: a credential Id is in both values, but its issuer URL changed.
  • fic_subject_changed: a credential Id is in both values with the same issuer, but its subject identifier changed. On shared public issuers such as GitHub Actions or GitLab the issuer is identical for every tenant, so the subject is the only value scoping the trust to a specific repository, branch, or environment.

The query expands the new credential array into one row per credential and matches each one to the old array by Id, so a change to the audience of an existing credential does not alert, and neither does removing a credential.

Possible investigation steps

  • Review azure.auditlogs.properties.initiated_by.user.userPrincipalName and ipAddress to identify who made the change and from where.
  • Use Esql.fic_change to decide whether this is a new trust, an existing trust pointed at a different issuer, or an existing trust re-scoped to a different subject.
  • For an issuer change, compare Esql.fic_old_issuer and Esql.fic_new_issuer for the credential in Esql.fic_id.
  • For a subject change, compare Esql.fic_old_subject and Esql.fic_new_subject. Confirm the new repository, organization, branch, or environment belongs to the organization and that the change was expected by the pipeline owner. A subject that is now null may indicate a switch to a claims matching expression; review the new credential object in azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value.
  • For an added credential, review Esql.fic_id, Esql.fic_new_issuer, and Esql.fic_new_subject.
  • Check whether the issuer domain is controlled by the organization.
  • Review the application's assigned roles and permissions to understand the scope of access.
  • Use azure.correlation_id to pivot to related changes in the same session.
  • Check for subsequent federated sign-ins as the application's service principal.

False positive analysis

  • Onboarding a workload to workload identity federation adds the first credential.
  • DevOps teams add further credentials for additional repositories, branches, or environments.
  • Migrations from one identity provider to another change the issuer on an existing credential id.
  • Renaming a branch, transferring a repository to a new organization, or moving a pipeline from a branch-scoped to an environment-scoped subject changes the subject on an existing credential id.
  • Validate the change with the application owner before taking action.

Response and remediation

  • If the change is unauthorized, immediately remove or revert the federated identity credential.
  • Rotate any secrets or certificates associated with the application.
  • Review Azure sign-in logs and audit logs for any unauthorized activity using the application's identity.
  • Disable the application or service principal if compromise is confirmed.
  • Investigate how the unauthorized change occurred (e.g., compromised admin account, over-privileged service principal).
  • Implement conditional access policies and PIM (Privileged Identity Management) to protect application management operations.

References

Related rules

to-top