This blog post is part of a series about Microsoft Entra Workload ID:

Integration of AzADServicePrincipalInsights as Custom Table

In the first part of the blog post, I’ve already described “AzADServicePrincipalInsights” (AzADSPI) from Julian Hayward. The tool allows to collect richfull insights and tracking changes of Workload Identities and export them as JSON in a repository. But we can use also the data to ingest them to Microsoft Sentinel for enrichment. Therefore, I’ve created a pipeline which ingest the data to a Microsoft Sentinel Workspace by using PowerShell scripts.

Executing AzADServicePrincipalInsights in your GitHub repo

  1. First of all, create a private project/repository with the name “AzADServicePrincipalInsights” in Azure DevOps or GitHub and clone or fork the repository from ‣. In this sample, I’m using GitHub for implementing AzADSPI and using GitHub Actions for the further automation.
  2. Create an app registration and add the required permission as Application Permission and Azure RBAC assignment. You can also create a user-assigned managed identity but in that case the Graph API permissions needs to be configured by API or PowerShell.

    Untitled

  3. Follow the steps from the following Microsoft Learn article to configure Federated Credentials in GitHub: Add federated credentials - Connect GitHub and Azure | Microsoft Learn Make sure, that the values CLIENT_ID , TENANT_ID and SUBSCRIPTION_ID has been added as repository secret.
  4. Use the workflow template file “.github/workflows/AzADServicePrincipalInsights_OIDC.yml” and customize the parameters. It’s mandatory to change the ManagementGroupId.

    Untitled

  5. Execute the workflow and verify that AzADSPI is able to collect the data and was able to commit the files successfully to the main branch.

Create Data Collection Endpoint and Rule to use Azure Monitor Ingest API

Next, we will create the required data collection endpoint in Azure Portal. Those single configuration steps are well documented in Microsoft Learn. Therefore I’ve added the links to the related articles in the headlines and added some notes and configuration parameters which are important to know.

  1. Create data collection endpoint In my example, I’m using the same subscription as Microsoft Sentinel for creating a data collection endpoint and rule. Make sure that Data Collection Endpoint and Rule are created in the same but in a dedicated resource group.

    Untitled

  2. Create new table in Log Analytics workspace

    I’ve chosen the table name “AzADServicePrincipalInsights_CL” which will be used later in the parser and analytics rules:

    Untitled

  3. Parse and filter sample data Get the script “AzADSPI_DataCollectionIngest.ps1” from my repository and execute it by using the parameter SampleDataOnly which allows you to get a JSON output. Export the content as file and use them as sample data.
  4. Assign permissions to the DCR Use the pre-created Service Principal or Managed Identity (with Federated Credential) which has been created previously for the role assignment. In this scenario, the GitHub workflow needs the privileges to ingest the data.

Implement Pipeline to ingest data to Microsoft Sentinel

  1. Next, create a workflow based on the template “AzADServicePrincipalInsights_Ingest.yml” from my repository and update the following values with the names and IDs from your environment:
    • DataCollectionEndpointName
    • DataCollectionResourceGroup
    • DataCollectionSubscriptionId

      Untitled

  2. Copy the PowerShell Script “AzADSPI_DataCollectionIngest.ps1” from my repository to the “pwsh” folder of the repository.
  3. Run the workflow and wait for 10-15 minutes to verify if the data has been successfully ingested. Check if any event entry exists on table AzADServicePrincipalInsights_CL.
  4. I’ve created a template for a KQL function with the name “AzADSPI” which will standardize the column names to my defined and preferred schema. This also supports me in sharing the same KQL query logic across other data sources that I’m using in my examples and detection queries. Follow the steps from Microsoft Learn article to create and use functions in Microsoft Sentinel.

    Untitled

Publish WatchList “WorkloadIdentityInfo” with SentinelEnrichment

Together with my colleague Fabian Bader, I have worked on an alternate way to publish enrichment data to Microsoft Sentinel. WatchLists are a great way to maintain a list of this kind of asset information which can be used in KQL queries within Microsoft Sentinel.

We have published a PowerShell module named “SentinelEnrichment” which automates the process to create and update the WatchList. This module can be executed in a GitHub action workflow, Automation Account, Azure Function or any other environment which supports PowerShell.

I’ve used some code from my EntraOps PoC project to gather various details about Workload Identities. The cmdlet can be found here and will provide the content for the WatchList which we like to upload in the following automation job. In this example, I will use an automation account for collecting the data and upload the WatchList to Sentinel.

Side Note: This solution offers a different set of information compared to the AzADSPI. Some details such as Azure RBAC assignments or Delegated API Permissions are not part of the WatchList. The size of a row is limited and therefore just a subset can be provided in a single WatchList.

Create and prepare an Automation Account

  1. Create an automation account and enable the System-assigned Managed Identity
  2. Add the required Microsoft Graph API application permissions to the Managed Identity by using Microsoft Graph PowerShell or any other Graph Client. I’ve customized the published sample by Niklas Tinner which can be found here.

     #Install required module
     Install-Module Microsoft.Graph -Scope CurrentUser
    
     #Connect to Graph
     Connect-MgGraph -Scopes Application.Read.All, AppRoleAssignment.ReadWrite.All, RoleManagement.ReadWrite.Directory
    
     #Insert Object ID of Managed Identity
     $ManagedIdentityObjectId = Read-Host -Prompt "Enter Object Id of the System-assigned Managed Identity on the Azure Resource"
    
     #Insert permissions of Graph
     $AppRoles = @("Application.Read.All","Group.Read.All", "RoleManagement.Read.Directory")
    
     #Find Graph permission
     $MsGraph = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"
     $Roles = $MsGraph.AppRoles | Where-Object {$_.Value -in $AppRoles}
    
     #Assign permissions
     foreach ($Role in $Roles) {
         New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ManagedIdentityObjectId -PrincipalId $ManagedIdentityObjectId -ResourceId $MsGraph.Id -AppRoleId $Role.Id
     }
     Disconnect-MgGraph
    
  3. In addition to Microsoft Graph, the automation accounts need also permission to write and delete WatchList(s) in the Microsoft Sentinel Workspace. Assign the managed identity the role “Microsoft Sentinel Contributor” on Resource Group-Level or create/use a custom RBAC role including the following least privileges:
    • Microsoft.SecurityInsights/Watchlists/read
    • Microsoft.SecurityInsights/Watchlists/write
    • Microsoft.SecurityInsights/Watchlists/delete
  4. Add the SentinelEnrichment PowerShell Module by using the import function from the PowerShell Gallery. Choose PowerShell 7.2 as runtime environment.
  5. Verify that the module “SentinelEnrichment” has been successfully imported as module.

    Untitled

Add and execute the runbook to create “WorkloadIdentityInfo”

  1. Add details about the Sentinel Workspace in the variables of the automation accounts. Create variables under the section “Shared Resources” with the following names and the related values:

    Untitled

    • SentinelResourceGroupName
    • SentinelSubscriptionId
    • SentinelWorkspaceName
  2. Create a new runbook with runbook type “PowerShell” and Runtime version 7.2.

    Untitled

  3. Copy the content of the script from my repository and past them into the runbook:

    Untitled

  4. Click on “Test pane” to validate that the script in association with the required permissions and variables works.
  5. At next, you need to click on “Publish” to leave the edit and test mode and release the runbook.
  6. Create a scheduler to run the script to update the WatchList automated on your preferred time interval.
  7. Check the results of the WorkloadIdentityInfo table after a successful execution of the runbook.

    Untitled

Enrichment of “Tiering Model” Classification

I’ve published a definition of the Enterprise Access Model to classify and identify resources in Microsoft Entra ID. This classification model is part of my PoC project “EntraOps” and shared as community-driven project. Julian Hayward has implemented the EntraOps classification to AzAdvertizer and will be also available out-of-the-box in a future release of AzADSPI.

The classification files can be also used in KQL for enrichment and allows to identify App Roles and Directory Role assignment in relation to the definition of “Control Plane” privileges.

Therefore, I’ve created Microsoft Sentinel functions for AzADSPI and WorkloadIdentityInfo which allows to apply the classification (as external data from the JSON file on the GitHub project) by executing the KQL query.

Classification of App Owner or Delegated Role Member

I’m planning to apply classification for all privileged users in Entra ID as part of the upcoming release of EntraOps which will cover all eligible, active and permanent members. In the meanwhile, I’m using the IdentityInfo table to get a list of all active or permanent Entra ID role member of the last 14 days. As already described before, EntraOps classification can be used to apply a general classification by adding them to the KQL logic.

// List of (active/permanent) Directory role member with with enriched classification from EntraOps Privileged EAM
// by using IdentityInfo table from Microsoft Sentinel UEBA
let SensitiveEntraDirectoryRoles = externaldata(RoleName: string, RoleId: string, isPrivileged: bool, Classification: dynamic)["https://raw.githubusercontent.com/Cloud-Architekt/AzurePrivilegedIAM/main/Classification/Classification_EntraIdDirectoryRoles.json"] with(format='multijson')
| where Classification.EAMTierLevelName != "Unclassified"
| extend EAMTierLevelName = Classification.EAMTierLevelName
| project RoleName, isPrivileged, EAMTierLevelName;
let SensitiveUsers = IdentityInfo
| where TimeGenerated > ago(14d)
| summarize arg_max(TimeGenerated, *) by AccountObjectId
| mv-expand AssignedRoles
| extend RoleName = tostring(AssignedRoles)
| join kind=inner ( SensitiveEntraDirectoryRoles ) on RoleName;
SensitiveUsers
| project EAMTierLevelName, RoleName, AccountObjectId, AccountDisplayName, AccountUPN, IsAccountEnabled, UserType, SourceSystem

In addition, I’ve created just another function which allows you to get a combined list of all identities (human and workload identities) which are available from the IdentityInfo and the custom solution WorkloadIdentityInfo . This includes also the classification enrichment.

List of privileged assignments outside of Custom Table or WatchList

Sometimes it’s required to get a list of the recent added privileged assignments directly from the AuditLog to cover also the latest assignments before the next automation schedule or trigger will update the Custom Table or WatchList. Therefore, I’ve created a function which shows all Microsoft Graph API and Entra ID role assignments of the latest 24 hours.

Untitled

Enriched Incidents from Microsoft Security products

In the following section, I like to give some examples how alerts from Microsoft Security products can be enriched by the WorkloadIdentityInfo WatchList.

Risky Workload ID detected by Entra ID Protection

Leaked credentials of Workload Identity has been detected by Entra ID Protection. As already described in the previous part of the blog post series, the entity mapping is missing in Microsoft Sentinel. Therefore, I’ve written analytic rule which uses existing Alert (from the SecurityAlert table entry) to enrich them with detailed information from the WorkloadIdentityInfo watchlist in combination with the function PrivilegedWorkloadIdentity. This allows us to have an entity mapping to the Application object but also enriched information, including the Tiering Level of Privileged Access.

Below you’ll see the differences between the original incident on the left side and the enriched incident details (including privileged classification and entity details) from my custom analytics rules on the right side.

Untitled

You will find the analytics rule templates on my repository: 🧪 Workload ID Protection Alerts with Enriched Information

Side Note: Using this custom analytic rule could lead to duplicated incidents and alerts if incident creation has been already enabled for the alerts. Avoid to use incident creation rules for this type of Identity Protection Alerts or use an automation rule to avoid duplicates.

Suspicious activities detected by MDA App Governance

MDA App Governance includes many built-in alert policies but also the option to configure customized patterns to detect suspicious activity. But similar to Entra ID Protection alerts for Workload Identities, the mapping to the entity and description details are also missing. Therefore, I’ve created an rule template to parse the ApplicationId from the “AlertLink URL” and correlate them with the WorkloadIdentityInfo for entity enrichment. In addition, the description details are not visible in the incident overview.

Untitled

As you can see in the next sample, a high volume of e-mail search activities has been detected by using a privileged interface. Information about the Workload Identity from the WatchList and classification by using the PrivilegedWorkloadIdentityInfo allows to add some related custom alert detail fields to the incident.

Untitled

You will find the analytics rule templates on my repository: 🧪 Workload ID Protection Alerts with Enriched Information

Threat policy (anomaly detection) alerts from Microsoft Defender XDR

Microsoft Defender XDR includes a few anomaly detection policies for OAuth apps (e.g., Unusual ISP for an OAuth App). We are using just another analytics rules to use the entry from the SecurityAlert to create an enriched incident.

Untitled

AzureSentinel/Detections/EID-WorkloadIdentities/MDA Threat detection policy with Enriched Information (WorkloadIdentityInfo).yaml at main · Cloud-Architekt/AzureSentinel (github.com)

The previous named templates can be found here: 🧪 MDA Threat detection policy for OAuth Apps with Enriched Information

Hunting queries with enriched data of Microsoft Security products

Anomalous changes on high-sensitive Workload Identities

We have two interesting data sources which can be used for anomaly-based detection: Microsoft Defender XDR Behaviors can be used for checking unusual addition of credentials, as you can see in the following hunting query. Permissions will be enriched with the data from the classification model.

let SensitiveMsGraphPermissions = externaldata(EAMTierLevelName: string, Category: string, AppRoleDisplayName: string)["https://raw.githubusercontent.com/Cloud-Architekt/AzurePrivilegedIAM/main/Classification/Classification_AppRoles.json"] with(format='multijson');
BehaviorInfo
| where ActionType == "UnusualAdditionOfCredentialsToAnOauthApp"
| join BehaviorEntities on BehaviorId
| where EntityType == "OAuthApplication"
| extend Permissions = parse_json(AdditionalFields1).Permissions
| mv-expand Permissions
| extend Permission = tostring(Permissions.PermissionName)
| join kind=inner ( SensitiveMsGraphPermissions | project EnterpriseAccessModelTiering = EAMTierLevelName, EnterpriseAccessModelCategory = Category, AppRoleDisplayName ) on $left.Permission == $right.AppRoleDisplayName
| summarize ApplicationPermissions = make_list(Permission), EnterpriseAccessModelTiering = make_set(EnterpriseAccessModelTiering), EnterpriseAccessModelCategory = make_set(EnterpriseAccessModelCategory) by Timestamp, BehaviorId, ActionType, Description, Categories, AttackTechniques, ServiceSource, DetectionSource, DataSources, AccountUpn, Application, ApplicationId

Untitled

The User Behavior Entity Analytics in Microsoft Sentinel includes also anomalies about “Application Management” activities from the user. This use case is not limited to a hunting query and would be also a potential anomaly-based detection. In this sample, we use the UEBA tables in combination with the IdentityInfo to build an analytics rule in Microsoft Sentinel for create an incident with “Medium” severity under the following conditions

  • Investigation Priority Score is greater than 1 in combination if one of the conditions will be satisfied:
    • Actor has no active or permanent Entra ID role assignment in the past 14 days
    • Risky User with Risk Level of Medium or higher

The incident will be increased to “High” severity if the Actor has been assigned to “Control Plane” Entra ID role in the past 14 days. All other results will be set to severity “Informational” and not included in the incident creation.

Untitled

You can find the KQL logic which is also part of the analytic rule-version of this hunting query: 🧪 UEBA Behavior anomaly on Application Management

Hunting compromised owner of Application and Service principal object

As already described, AzADSPI includes some advanced details of the Workload Identities objects (such as Application or Service Principal Owner). This data is now available in Sentinel by ingesting the data to the custom table. Therefore, we can use them in a hunting query to find risk events related to identities which has also ownership of Workload Identities.

The query is very simple and just correlates the ownership details from the custom table with the AADRiskEvents table from Entra ID Protection:

let ServicePrincipalOwner = PrivilegedAzADSPI
    | mv-expand parse_json(ServicePrincipalOwners)
    | extend OwnerId = tostring(ServicePrincipalOwners.id)
    | extend Ownership = "ServicePrincipalObject";
let ApplicationOwner = PrivilegedAzADSPI
    | mv-expand parse_json(ApplicationOwners)
    | extend OwnerId = tostring(ApplicationOwners.id)
    | extend Ownership = "ApplicationObject";
AADUserRiskEvents
| where TimeGenerated >ago(365d)
| join kind=inner (
    union ServicePrincipalOwner, ApplicationOwner
) on $left.UserId == $right.OwnerId
| project TimeGenerated, UserDisplayName, UserId, UserPrincipalName, RiskEventType, RiskDetail, RiskLevel, RiskState, WorkloadIdentityName, WorkloadIdentityType, Ownership

The result can be look like this one, attempt to get access to a Primary Refresh Token of a user has been detected which has access to a Workload Identity. Optionally, the classification of the affected workload identity could be also added.

Untitled

Attack path from unsecured workload environments

KQL offers the option to create “cross-service queries” which give us the chance to get results from the Azure Resource Graph in combination with data in the Microsoft Sentinel Workspace. At time of writing the blog post, this is not supported for Analytics Rules. However, it can be used in hunting queries. In this sample, we will use this feature to get a list of all attack paths from “Microsoft Defender for Cloud” CSPM feature which includes a Workload Identity as entity. Afterwards we use the result to enrich them with data from the WorkloadIdentityInfo table.

arg("").securityresources
    | where type == "microsoft.security/attackpaths"
    | extend AttackPathDisplayName = tostring(properties["displayName"])
    | mvexpand (properties.graphComponent.entities)
    | extend Entity = parse_json(properties_graphComponent_entities)
    | extend EntityType = (Entity.entityType)
    | extend EntityName = (Entity.entityName)
    | extend EntityResourceId = (Entity.entityIdentifiers.azureResourceId)
    | where EntityType == "serviceprincipal" or EntityType == "managedidentity"
    | project id, AttackPathDisplayName, tostring(EntityName), EntityType, Description = tostring(properties["description"]), RiskFactors = tostring(properties["riskFactors"]), MitreTtp = tostring(properties["mITRETacticsAndTechniques"]), AttackStory = tostring(properties["attackStory"]), RiskLevel = tostring(properties["riskLevel"]), Target = tostring(properties["target"])
| join hint.remote=right ( PrivilegedWorkloadIdentityInfo
) on $left.EntityName == $right.ServicePrincipalObjectId

Untitled

In the result, we can see the details from the MDC Attack Path but also the assigned permissions and classification of the affected workload identity.

Side Note: You can also build own attack correlation with the data from AzADSPI. The ingested data includes the columns ManagedIdentityAssociatedAzureResources and ManagedIdentityFederatedIdentityCredentials which gives you the chance to correlate the Workload Identity to the assigned Azure or Federated Entity (such as GitHub or Kubernetes workload).

Custom analytics rules with WorkloadIdentityInfo in Microsoft Sentinel

Added Ownership on privileged workload identity to lower-privileged user

In general, assignment of owners to Application and Service Principals should be avoided. Microsoft offers already a rule template (“Add owner to application”) to cover this use case for Application object. But keep in mind, also an owner of service principal object is able to issue a valid credential to the workload identity.

I’ve created an analytics rule which will generate an incident with high severity if an ownership of Application or Service Principal has been assigned to a user which has lower privileges than the application. Not only the actor will be included in the incident, also the target principal is part of the entity mapping.

Untitled

This rule template can be found here and can be used in combination with the WorkloadIdentityInfo:

🧪Added Ownership to workload identity (WorkloadIdentityInfo)

Added credential on privileged workload identity by lower privileged users

Issuing a credential by an owner is a sensitive management operation. Especially, if the owner has the chance use an impersonation of the workload identity for privilege escalation. In the next example, I’m using the classification of the workload but also actor to identify if the credential has been issued by a lower privileged user. This incident can be also correlate to a previous incident (“Added Ownership on privileged workload identity to lower-privileged user”) to understand the context of both security events as multi-stage attack.

Untitled

You can find the analytic rule in my repository and should be deployed in combination with the previous rule template: 🧪 [Added Credential to privileged workload by lower or non-privileged user (WorkloadIdentityInfo)](https://github.com/Cloud-Architekt/AzureSentinel/blob/main/Detections/EID-WorkloadIdentities/Added%20Credential%20to%20privileged%20workload%20by%20lower%20or%20non-privileged%20user%20(WorkloadIdentityInfo.yaml)

Token Replay from compromised or unsecured workload environments

I’ve build and shared some hunting queries for correlation between a sign-in and activity event from the AzureActivity and the new MicrosoftGraphActivityLogs in October this year. This can be used as potential indicator for token replay in my opinion.

Replay of tokens from unsecured DevOps or other workload environments has been one of my live demos in previous community talks about workload identities. Correlation between IP address from sign-in and activity was of the few available options before (e.g., Azure Activity with Federated Credentials outside of GitHub Workflow activity). But now we can build additional queries to use the UniqueTokenIdentifier to build a correlation between the acquired token from the sign-in process and the activity of the issued token.

Dynamic severity can be set on conditions if the IP address is suspicious or is coming from unfamiliar IP service tags/location. In this sample, the severity is increased to high if it’s outside of Azure.

Untitled

The analytic rule logic is available for Azure Resource Manager and Microsoft Graph here: 🧪 Token Replay from workload identity with privileges in Microsoft Azure (WorkloadIdentityInfo).yaml

🧪 Token Replay from workload identity with privileges in Microsoft Entra or Microsoft 365 (WorkloadIdentityInfo)

Next: Incident Response and Conditional Access

In the next part of the blog post series, we will go into details about incident response on Workload Identities and how Conditional Access can be used for automated response. So, stay tuned!