T1199

Trusted Relationship

Initial Access Last updated:

Adversaries may breach or otherwise leverage organizations who have access to intended victims. Access through trusted third-party relationships abuses an existing connection that may not be protected or receives less scrutiny than standard mechanisms of gaining access to a network. Organizations often grant elevated access to second or third-party external providers in order to allow them to manage internal systems as well as cloud-based environments. These relationships include IT services contractors, managed security providers, and infrastructure contractors. In Office 365 and Azure AD environments, organizations may grant Microsoft partners or resellers delegated administrator permissions. By compromising a partner or reseller account, an adversary may be able to leverage existing delegated administrator relationships or send new delegated administrator offers to clients in order to gain administrative control over the victim tenant.

What is T1199 Trusted Relationship?

Trusted Relationship (T1199) maps to the Initial Access tactic — the adversary is trying to get into your network in MITRE ATT&CK.

This page provides production-ready detection logic for Trusted Relationship, covering the data sources and telemetry it touches: Azure Active Directory: AuditLogs, Azure Active Directory: SigninLogs, Identity: User Account. The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.

MITRE ATT&CK

Tactic
Initial Access
Technique
T1199 Trusted Relationship
Canonical reference
https://attack.mitre.org/techniques/T1199/
Microsoft Sentinel / Defender
kusto
// Detect suspicious trusted relationship abuse — delegated admin grants, cross-tenant access, and service provider account anomalies
let SensitiveOperations = dynamic([
    "Add delegated permission grant",
    "Add partner to cross-tenant access setting",
    "Add policy to cross-tenant access setting",
    "Set partner information",
    "Add member to role",
    "Add app role assignment to service principal",
    "Add service principal"
]);
let SuspiciousRoles = dynamic([
    "Global Administrator",
    "Privileged Role Administrator",
    "Exchange Administrator",
    "SharePoint Administrator",
    "Security Administrator",
    "Helpdesk Administrator",
    "User Administrator"
]);
// Part 1: New delegated admin relationship grants and partner configuration changes
AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName in (SensitiveOperations)
| extend InitiatorUPN = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatorIP = tostring(InitiatedBy.user.ipAddress)
| extend InitiatorAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatorAppId = tostring(InitiatedBy.app.appId)
| extend TargetName = tostring(TargetResources[0].displayName)
| extend TargetType = tostring(TargetResources[0].type)
| extend TargetId = tostring(TargetResources[0].id)
| extend ModProps = tostring(TargetResources[0].modifiedProperties)
| where Result =~ "success"
| extend EventCategory = "DelegatedAdminGrant"
| project TimeGenerated, EventCategory, OperationName, InitiatorUPN, InitiatorIP,
          InitiatorAppName, InitiatorAppId, TargetName, TargetType, TargetId,
          ModProps, Result, CorrelationId
| union kind=outer (
    // Part 2: Cross-tenant sign-ins by external service providers accessing privileged resources
    SigninLogs
    | where TimeGenerated > ago(24h)
    | where ResultType == 0
    | where CrossTenantAccessType != "none" and isnotempty(CrossTenantAccessType)
    | where ResourceTenantId != HomeTenantId
    | extend IsPrivilegedApp = AppDisplayName has_any ("Exchange", "SharePoint", "Teams", "Security", "Compliance", "Azure Active Directory", "Graph")
    | where IsPrivilegedApp == true
    | extend EventCategory = "CrossTenantPrivilegedAccess"
    | project TimeGenerated, EventCategory,
              OperationName = strcat("Cross-Tenant Sign-In via ", CrossTenantAccessType),
              InitiatorUPN = UserPrincipalName,
              InitiatorIP = IPAddress,
              InitiatorAppName = AppDisplayName,
              InitiatorAppId = AppId,
              TargetName = ResourceDisplayName,
              TargetType = "Application",
              TargetId = ResourceTenantId,
              ModProps = tostring(DeviceDetail),
              Result = "Success",
              CorrelationId
)
| union kind=outer (
    // Part 3: Privileged role assignments to external/guest accounts (often MSP onboarding step)
    AuditLogs
    | where TimeGenerated > ago(24h)
    | where OperationName in ("Add member to role", "Add eligible member to role")
    | extend InitiatorUPN = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatorIP = tostring(InitiatedBy.user.ipAddress)
    | extend TargetUPN = tostring(TargetResources[0].userPrincipalName)
    | extend RoleName = tostring(TargetResources[1].displayName)
    | where RoleName in (SuspiciousRoles)
    | where TargetUPN contains "#EXT#" or TargetUPN contains "_" // Guest/external account patterns
    | where Result =~ "success"
    | extend EventCategory = "ExternalAccountPrivilegedRoleGrant"
    | project TimeGenerated, EventCategory,
              OperationName = strcat("Role Grant: ", RoleName, " to external account"),
              InitiatorUPN, InitiatorIP,
              InitiatorAppName = "",
              InitiatorAppId = "",
              TargetName = TargetUPN,
              TargetType = "User",
              TargetId = tostring(TargetResources[0].id),
              ModProps = RoleName,
              Result = "Success",
              CorrelationId
)
| sort by TimeGenerated desc

Detects trusted relationship abuse across three dimensions in Azure AD and Office 365: (1) new delegated admin permission grants and partner cross-tenant access configuration changes in AuditLogs, signaling MSP relationship establishment or modification; (2) successful cross-tenant sign-ins by external service providers accessing privileged applications such as Exchange, SharePoint, and Security Center; (3) privileged role assignments to external or guest accounts (identified by #EXT# suffix or external UPN patterns). Together these cover the most common indicators of T1199 in cloud environments — partner relationship exploitation, GDAP/DAP abuse, and MSP-pivoted access. Requires Azure AD P1 for SigninLogs with CrossTenantAccessType field.

high severity medium confidence

Data Sources

Azure Active Directory: AuditLogs Azure Active Directory: SigninLogs Identity: User Account

Required Tables

AuditLogs SigninLogs

False Positives

  • Legitimate MSP or IT service provider onboarding — new partner relationships being established with proper change management approval will trigger delegated admin grant events
  • Authorized Azure AD B2B guest user provisioning for vendors or contractors accessing collaboration tools like Teams or SharePoint
  • Microsoft first-party service accounts (e.g., Microsoft Support, Intune Service Principal) appearing as cross-tenant sign-ins when performing tenant management actions
  • Scheduled MSP maintenance windows where service provider accounts access privileged resources as part of contracted SLA obligations
  • Security team adding a MSSP or MDR provider with Global Reader or Security Reader role for monitoring purposes

Sigma rule & cross-platform mapping

The detection logic for Trusted Relationship (T1199) above is provided in a vendor-neutral form so you can deploy it on any SIEM. The same logic is shipped here as native KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the following logsource:

logsource:
  product: azure

Browse the community-maintained Sigma rules for this technique:


Testing Methodology

Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1Simulate Delegated Admin Permission Grant via Azure AD PowerShell

    Expected signal: Azure AD AuditLogs: OperationName 'Add delegated permission grant', Result 'success', InitiatedBy.user.userPrincipalName = executing account, TargetResources[0].displayName = 'Microsoft Graph'. Visible in Microsoft 365 compliance portal under Audit search with Activity = 'Add delegated permission grant'.

  2. Test 2Assign Privileged Role to External Guest Account Simulating MSP Onboarding

    Expected signal: Azure AD AuditLogs: OperationName 'Add member to role', Result 'success', TargetResources[1].displayName = 'Global Reader', TargetResources[0].userPrincipalName contains '#EXT#'. Also generates 'Invite external user' event. Both visible in AuditLogs table within 5-10 minutes.

  3. Test 3Simulate MSP Network Logon from External IP Using Service Account

    Expected signal: Windows Security Event ID 4624: LogonType=3 (Network), TargetUserName='svc_msp_test', SourceNetworkAddress=127.0.0.1. Security Event ID 4672 if the account has elevated privileges. Visible in Windows Event Viewer under Security log within seconds of execution.

  4. Test 4Create New Service Principal and Grant API Permissions Simulating Post-Access Backdoor

    Expected signal: Azure AD AuditLogs: OperationName 'Add application', Result 'success', TargetResources[0].displayName = 'df00tech-detection-test-app'. Followed by OperationName 'Update application' with ModifiedProperties showing RequiredResourceAccess including Mail.Read scope. Both events include InitiatedBy.user.userPrincipalName of the executing account.


Response Playbook

Triage

  1. Identify the account that initiated or used the trusted relationship: check whether the UPN belongs to an internal employee, a known MSP service account, or an external partner tenant. Query AuditLogs: summarize by InitiatorUPN, category, and result to see scope of activity in the past 72 hours.
  2. Verify the relationship legitimacy: check with IT management and procurement whether this MSP/partner/contractor relationship exists in the official vendor registry. Ask specifically: Was a formal onboarding ticket or change request created? What is the contracted scope of access?
  3. Review what actions were performed under the trusted relationship: pull AuditLogs filtered by CorrelationId or session, OfficeActivity filtered by UserId, and DeviceLogonEvents filtered by AccountName for the suspicious account. Focus on mailbox access, file downloads, role changes, and new service principal creation.
  4. Check the geolocation and ASN of the source IP: does it match the MSP's known data center ranges or offices? Use the IP in threat intelligence feeds to determine if it's associated with known malicious infrastructure. A VPN or datacenter exit from an unusual country is a strong indicator of compromise.
  5. Assess privilege level: what roles or permissions does the third-party account hold? Global Administrator, Exchange Administrator, or Security Administrator access from an MSP account that has been compromised gives adversaries full tenant control. Determine the blast radius immediately.
  6. Check for delegated admin abuse indicators: in AuditLogs, look for 'Add partner to cross-tenant access setting' or 'Add delegated permission grant' operations immediately preceding the suspicious sign-in. This pattern suggests the adversary established their own access path after compromising the MSP.
  7. Look for lateral movement from MSP-managed systems: if the MSP has RMM (Remote Monitoring and Management) tools installed on endpoints (e.g., ConnectWise, Kaseya, Datto), check DeviceProcessEvents for unusual processes spawned by RMM agents, and DeviceNetworkEvents for beaconing from those processes.

Containment

  1. Immediately revoke the compromised third-party account's access tokens and sessions: in Azure AD, navigate to Users > [account] > Revoke sessions. For MSP delegated admin relationships, remove the delegated admin relationship via the Microsoft 365 Admin Center under Settings > Partner relationships.
  2. Disable or block the source IP range at the firewall and conditional access level: create an Azure AD Named Location for the suspicious IP(s) and add a Conditional Access policy blocking sign-ins from those locations to all cloud apps. For on-premises, block the range at the perimeter firewall.
  3. If Global Administrator or equivalent privilege was abused: rotate all service principal credentials and OAuth tokens immediately. Audit all Conditional Access policies, federation settings, and external identity configurations for backdoors added during the attack window.
  4. Isolate any on-premises endpoints that the MSP's RMM tool has agent access to, particularly if the RMM platform (Kaseya, ConnectWise, SolarWinds) was the vector. Use EDR isolation on affected endpoints until RMM agent integrity is verified.
  5. Notify the MSP/third-party provider of the suspected compromise: they may have other customers affected. Coordinate with them to reset all service account credentials and review their own environment for indicators of compromise.
  6. Preserve all audit logs before rotating credentials: export AuditLogs, SigninLogs, and OfficeActivity for the attack timeframe to an immutable storage location (e.g., Azure Immutable Blob Storage) to support forensic investigation and potential legal action.

Evidence Collection

  1. Azure AD AuditLogs for the past 30 days filtered by the compromised account UPN and all accounts in the same partner tenant — captures every privileged action taken under the trusted relationship
  2. Azure AD SigninLogs for all successful and failed sign-ins by the third-party account — provides geolocation, device fingerprint, and access patterns over time
  3. Office 365 Unified Audit Log (UAL) — accessed via Microsoft Purview Compliance portal or Exchange PowerShell: Search-UnifiedAuditLog — for mailbox access, file downloads, SharePoint activity, and Teams messages read by the external account
  4. Microsoft Cloud App Security (Defender for Cloud Apps) activity logs — if deployed, provides richer session context including specific file and data accessed under the third-party account
  5. Azure Resource Manager Activity Logs — if the MSP had Azure subscription access, review via the Azure Monitor Activity Log blade for resource creation, deletion, policy changes, or role assignments
  6. RMM agent logs and management console audit trails from the MSP's tooling (ConnectWise Automate, Kaseya VSA, Datto RMM) — shows what scripts were executed on managed endpoints
  7. Network flow data or firewall logs from the perimeter showing connections from the MSP's IP ranges in the days preceding the incident — establishes a baseline of normal access timing and volume to identify anomalies
  8. Endpoint memory and disk forensics on any systems the RMM agent touched — collect process memory dump if malicious tools were deployed, and check for persistence mechanisms in startup locations, scheduled tasks, and WMI subscriptions

Escalation Criteria

  • ! Evidence that the trusted relationship was used to establish a backdoor: new service principal created, new Conditional Access exclusion added, federation trust modified, or emergency access account created by the external account
  • ! Data exfiltration indicators: large volume SharePoint/OneDrive downloads, bulk mailbox exports, or Azure Storage blob access from the third-party account — especially to IPs outside the MSP's known infrastructure
  • ! Lateral movement from MSP-managed endpoints to non-MSP-managed systems: DeviceLogonEvents showing the MSP service account authenticating to domain controllers, sensitive file servers, or backup infrastructure
  • ! Multiple customer environments affected: if this is an MSP compromise, the same malicious patterns may appear across multiple tenants managed by the same partner — coordinate with MSSP/CISA if this is confirmed
  • ! Privileged identity infrastructure tampered with: changes to Azure AD PIM (Privileged Identity Management) settings, removal of MFA requirements for privileged roles, or new emergency access accounts without MFA
  • ! Evidence of supply chain extension: the third-party account was used to access your software build systems, code repositories, or deployment pipelines — indicating possible intent to compromise your own customers

Investigation Guide

Forensic Artifacts

  • > Azure AD AuditLogs: OperationName 'Add delegated permission grant' or 'Add partner to cross-tenant access setting' — records the exact timestamp and actor that established the trusted relationship
  • > Azure AD SigninLogs: CrossTenantAccessType field — differentiates B2B collaboration, B2B direct connect, and Microsoft Support access types for external accounts
  • > Microsoft 365 Admin Center: Settings > Partner relationships — lists all active DAP (Delegated Admin Privileges) and GDAP (Granular Delegated Admin Privileges) relationships with partner tenant IDs and assigned roles
  • > Office 365 Unified Audit Log: WorkloadName=AzureActiveDirectory, Operations related to role assignment and permission grants — provides 90-day retention of privileged operations
  • > Azure Activity Log: Authorization/roleAssignments/write events — captures RBAC role assignments made to service principals or external accounts on Azure subscriptions
  • > RMM agent installation artifacts on endpoints: registry keys at HKLM\SOFTWARE\[RMM Vendor], installed service entries in services.msc, and scheduled tasks created by the RMM agent
  • > Windows Security EventID 4648 (Explicit Credential Use) on domain-joined machines — logged when MSP accounts use explicit credentials to connect to internal systems
  • > Azure AD Application Registration audit trail: app registrations and service principals created or modified by external accounts — check for applications with Mail.Read, Files.ReadWrite.All, or Directory.ReadWrite.All permissions

Tuning Guidance

The primary tuning challenge for T1199 is distinguishing legitimate MSP/partner activity from abuse. Build a Named Locations list in Azure AD Conditional Access containing all documented MSP and vendor IP ranges, then add this to the KQL query as an exclusion for the baseline AlertLevel. Maintain a partner registry (ideally in a CMDB or ticketing system) mapping each partner's Azure AD tenant ID to their contracted scope of access and allowed IP ranges — use this to create allowlist conditions in your detection. For the AuditLogs query, filter out operations matching known change ticket windows (correlate with ITSM system via ServiceNow integration or Sentinel Playbook). For the DeviceLogonEvents hunting query, build an exclusion list of documented MSP management IP CIDR ranges extracted from vendor contracts. Time-of-day analysis is valuable here: MSP access outside contracted business hours or outside agreed maintenance windows should have lower threshold for alerting. Consider integrating DMARC/DKIM/SPF data — phishing-based MSP compromise often precedes the trusted relationship abuse, so email threat intelligence can provide early warning.


Hunting Queries

Hunt for all privileged administrative actions performed by external or guest accounts (UserType=4, #EXT# UPN pattern) over the past 30 days. A high action count from an external account that is not part of a documented change window is a strong indicator of MSP account compromise or unauthorized third-party access. The 30-day window captures dwell time common in trusted relationship attacks.

Hunting — KQL
kql
// Hunt for all privileged actions performed by external/guest accounts across the tenant
AuditLogs
| where TimeGenerated > ago(30d)
| extend ActorUPN = tostring(InitiatedBy.user.userPrincipalName)
| extend ActorTenantId = tostring(InitiatedBy.user.tenantId)
| where ActorUPN contains "#EXT#" or ActorTenantId != tostring(parse_json(tostring(AdditionalDetails))["tenantId"])
| where Category in ("RoleManagement", "ApplicationManagement", "Policy", "ProvisioningManagement")
| where Result =~ "success"
| summarize
    ActionCount = count(),
    UniqueOperations = dcount(OperationName),
    Operations = make_set(OperationName, 20),
    Earliest = min(TimeGenerated),
    Latest = max(TimeGenerated)
    by ActorUPN, ActorTenantId, Category
| where ActionCount > 0
| sort by ActionCount desc
Hunting — SPL
spl
sourcetype="o365:management:activity" UserType=4
  (RecordType="AzureActiveDirectory" OR RecordType="AzureActiveDirectoryAccountLogon" OR RecordType="ExchangeAdmin")
| eval ActorUPN=UserId
| eval IsPrivilegedOp=if(match(Operation, "(?i)(role|permission|grant|assign|admin|policy|delete|remove|update)"), 1, 0)
| where IsPrivilegedOp=1
| stats count as ActionCount, dc(Operation) as UniqueOps, values(Operation) as OperationList, earliest(_time) as Earliest, latest(_time) as Latest by ActorUPN, OrganizationId
| sort - ActionCount

Hunt for privileged administrative actions (new service principals, OAuth consent, delegated permission grants) performed by external accounts within 60 minutes of a successful cross-tenant sign-in. This temporal correlation identifies the 'foothold-then-establish-persistence' pattern typical of MSP account compromise where attackers quickly add backdoor applications or permissions before their access window closes.

Hunting — KQL
kql
// Hunt for new service principals or OAuth app grants created shortly after an MSP cross-tenant sign-in
let ExternalSignins = SigninLogs
    | where TimeGenerated > ago(30d)
    | where CrossTenantAccessType != "none" and ResultType == 0
    | project SigninTime = TimeGenerated, ExternalUser = UserPrincipalName, SourceIP = IPAddress;
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName in ("Add service principal", "Add application", "Consent to application.", "Add delegated permission grant")
| where Result =~ "success"
| extend ActorUPN = tostring(InitiatedBy.user.userPrincipalName)
| extend TargetName = tostring(TargetResources[0].displayName)
| join kind=inner (
    ExternalSignins
    | extend JoinKey = ExternalUser
) on $left.ActorUPN == $right.ExternalUser
| where abs(datetime_diff('minute', TimeGenerated, SigninTime)) < 60
| project TimeGenerated, OperationName, ActorUPN, TargetName, SourceIP, SigninTime, CorrelationId
| sort by TimeGenerated desc
Hunting — SPL
spl
sourcetype="o365:management:activity" UserType=4 Operation="UserLoggedIn" ResultStatus="Succeeded"
| eval ExternalUser=UserId
| eval LoginTime=_time
| join type=inner ExternalUser
  [search sourcetype="o365:management:activity"
    (Operation="Add-RoleGroupMember" OR Operation="New-ManagementRoleAssignment" OR
     Operation="Consent to application." OR Operation="Add delegated permission grant.")
  | eval ExternalUser=UserId
  | eval PrivAction=Operation]
| eval TimeDiff=abs(LoginTime - _time)
| where TimeDiff < 3600
| table _time, ExternalUser, PrivAction, ClientIP, OrganizationId, TimeDiff
| sort TimeDiff

Hunt for service or contractor accounts matching common MSP/vendor naming patterns (svc_, msp, managed, vendor, contractor) authenticating via network logon from public IP addresses. Legitimate MSP access should originate from known, documented IP ranges. Logins from public IPs not matching documented MSP network ranges may indicate stolen credentials being used by an adversary who has compromised the MSP but is not operating from the MSP's infrastructure.

Hunting — KQL
kql
// Hunt for DeviceLogonEvents where service accounts matching MSP/contractor patterns authenticate from external IP ranges
DeviceLogonEvents
| where Timestamp > ago(14d)
| where LogonType in ("Network", "RemoteInteractive")
| where ActionType == "LogonSuccess"
| where AccountName matches regex @"(?i)(svc_|_svc|msp|managed|vendor|contractor|remote|support|helpdesk|admin)"
| extend IsPublicIP = not(ipv4_is_private(RemoteIP))
| where IsPublicIP == true
| summarize
    LogonCount = count(),
    UniqueDevices = dcount(DeviceName),
    Devices = make_set(DeviceName, 10),
    UniqueSourceIPs = dcount(RemoteIP),
    SourceIPs = make_set(RemoteIP, 10),
    Earliest = min(Timestamp),
    Latest = max(Timestamp)
    by AccountName, AccountDomain
| where LogonCount > 2
| sort by LogonCount desc
Hunting — SPL
spl
index=wineventlog sourcetype="WinEventLog:Security" EventCode=4624 LogonType=3
  NOT (SourceNetworkAddress="10.*" OR SourceNetworkAddress="172.16.*" OR SourceNetworkAddress="192.168.*" OR SourceNetworkAddress="127.*" OR SourceNetworkAddress="-" OR SourceNetworkAddress="::1")
| eval IsMSPAccount=if(match(TargetUserName, "(?i)(svc_|_svc|msp|managed|vendor|contractor|remote|support|helpdesk)"), 1, 0)
| where IsMSPAccount=1
| stats count as LogonCount, dc(ComputerName) as UniqueDevices, values(ComputerName) as DeviceList, dc(SourceNetworkAddress) as UniqueSourceIPs, values(SourceNetworkAddress) as SourceIPList, earliest(_time) as Earliest, latest(_time) as Latest by TargetUserName, TargetDomainName
| where LogonCount > 2
| sort - LogonCount

Atomic Red Team Tests

Test 1 Simulate Delegated Admin Permission Grant via Azure AD PowerShell
windows

Creates a delegated permission grant in Azure AD simulating an MSP or partner being granted delegated access to manage the tenant. This replicates the administrative action an adversary would take after compromising a partner account that already has delegated admin access — establishing new or expanded OAuth permissions. Requires AzureAD or Microsoft.Graph PowerShell module and an account with Privileged Role Administrator rights.

Command

powershell
# Install module if needed: Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes "Application.ReadWrite.All","DelegatedPermissionGrant.ReadWrite.All"
# List existing service principals to find target
$sp = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" | Select-Object -First 1
# Create delegated permission grant for current user (simulates partner access grant)
$params = @{
    ClientId = (Get-MgContext).ClientId
    ConsentType = "AllPrincipals"
    ResourceId = $sp.Id
    Scope = "User.Read Directory.Read.All"
}
New-MgOauth2PermissionGrant -BodyParameter $params

Cleanup

powershell
# Remove the permission grant
$grant = Get-MgOauth2PermissionGrant -Filter "clientId eq '$(Get-MgContext).ClientId'"
if ($grant) { Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id }
Disconnect-MgGraph

Expected Telemetry

Azure AD AuditLogs: OperationName 'Add delegated permission grant', Result 'success', InitiatedBy.user.userPrincipalName = executing account, TargetResources[0].displayName = 'Microsoft Graph'. Visible in Microsoft 365 compliance portal under Audit search with Activity = 'Add delegated permission grant'.

Expected Detection

KQL Part 1 triggers on 'Add delegated permission grant' in AuditLogs. SPL EventCategory='DelegatedPermissionGrant' with Operation match. SuspicionIndicators score increases based on initiator account type.

Test 2 Assign Privileged Role to External Guest Account Simulating MSP Onboarding
windows

Invites an external guest user and assigns them a privileged Azure AD role, simulating the onboarding step an adversary would perform after gaining access to a tenant via a compromised MSP account. The Global Reader role is used here as a non-destructive alternative to Global Administrator for testing purposes. Generates the 'Add member to role' and 'Invite external user' audit events used in the detection.

Command

powershell
# Requires Microsoft.Graph module and account with User Administrator + Privileged Role Administrator
Connect-MgGraph -Scopes "User.Invite.All","RoleManagement.ReadWrite.Directory"
# Invite a test external account
$inviteParams = @{
    InvitedUserEmailAddress = "[email protected]"
    InviteRedirectUrl = "https://myapps.microsoft.com"
    SendInvitationMessage = $false
}
$invitation = New-MgInvitation -BodyParameter $inviteParams
$guestUserId = $invitation.InvitedUser.Id
# Find the Global Reader role
$role = Get-MgDirectoryRole -Filter "displayName eq 'Global Reader'"
if (-not $role) {
    # Activate role if not yet instantiated
    $roleTemplate = Get-MgDirectoryRoleTemplate -Filter "displayName eq 'Global Reader'"
    $role = New-MgDirectoryRole -RoleTemplateId $roleTemplate.Id
}
# Assign role to guest (simulates MSP account being granted access)
$memberParams = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/users/$guestUserId" }
New-MgDirectoryRoleMemberByRef -DirectoryRoleId $role.Id -BodyParameter $memberParams
Write-Output "Guest $($invitation.InvitedUser.Mail) assigned Global Reader role ID $($role.Id)"

Cleanup

powershell
# Remove role assignment and delete guest user
$role = Get-MgDirectoryRole -Filter "displayName eq 'Global Reader'"
Remove-MgDirectoryRoleMemberByRef -DirectoryRoleId $role.Id -DirectoryObjectId $guestUserId
Remove-MgUser -UserId $guestUserId
Disconnect-MgGraph

Expected Telemetry

Azure AD AuditLogs: OperationName 'Add member to role', Result 'success', TargetResources[1].displayName = 'Global Reader', TargetResources[0].userPrincipalName contains '#EXT#'. Also generates 'Invite external user' event. Both visible in AuditLogs table within 5-10 minutes.

Expected Detection

KQL Part 3 triggers on 'Add member to role' for external account (UPN contains '#EXT#') assigned to a sensitive role. SuspicionIndicators score elevated. SPL EventCategory='DelegatedPermissionGrant' for role assignment operation.

Test 3 Simulate MSP Network Logon from External IP Using Service Account
windows

Uses PsExec or net use to perform a network (Type 3) logon to a local machine using a service account with MSP-style naming convention, simulating a remote service provider authenticating from an external network segment. This generates Security EventID 4624 with LogonType=3, which the on-premises portion of the detection monitors. Safe to run in a lab — authenticates only to localhost or a test machine.

Command

powershell
# Create a test service account with MSP-style naming
net user svc_msp_test TestPassword123! /add /comment:"Test MSP service account for detection validation"
# Perform a network logon to localhost simulating remote MSP access
# Using net use to force Type 3 logon
net use \\127.0.0.1\IPC$ /user:svc_msp_test TestPassword123!
# Verify the logon
net session

Cleanup

powershell
net use \\127.0.0.1\IPC$ /delete
net user svc_msp_test /delete

Expected Telemetry

Windows Security Event ID 4624: LogonType=3 (Network), TargetUserName='svc_msp_test', SourceNetworkAddress=127.0.0.1. Security Event ID 4672 if the account has elevated privileges. Visible in Windows Event Viewer under Security log within seconds of execution.

Expected Detection

SPL ExternalServiceAccountNetworkLogon branch triggers: IsMSPAccount=1 matches 'svc_' prefix, EventCode=4624, LogonType=3. Note: localhost is filtered as private IP in production query — in testing, temporarily remove the 127.* exclusion to validate the detection logic fires correctly.

Test 4 Create New Service Principal and Grant API Permissions Simulating Post-Access Backdoor
windows

Registers a new Azure AD application and grants it API permissions, simulating the backdoor persistence technique used by adversaries like HAFNIUM and APT29 after gaining access via a compromised MSP or partner account. Adversaries create application registrations with delegated or application permissions to maintain persistent access independent of the original compromised account. Uses Microsoft Graph PowerShell to generate detectable AuditLogs events.

Command

powershell
Connect-MgGraph -Scopes "Application.ReadWrite.All"
# Register a new application simulating backdoor app creation
$appParams = @{
    DisplayName = "df00tech-detection-test-app"
    SignInAudience = "AzureADMyOrg"
}
$app = New-MgApplication -BodyParameter $appParams
Write-Output "Created app: $($app.DisplayName) with ID $($app.Id) (AppId: $($app.AppId))"
# Get Microsoft Graph resource SP
$graphSP = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
# Add Mail.Read permission to simulate data access capability
$requiredAccess = @{
    ResourceAppId = "00000003-0000-0000-c000-000000000000"
    ResourceAccess = @(
        @{ Id = "570282fd-fa5c-430d-a7fd-fc8dc98a9dca"; Type = "Scope" }  # Mail.Read
    )
}
Update-MgApplication -ApplicationId $app.Id -RequiredResourceAccess @($requiredAccess)
Write-Output "Granted Mail.Read permission to test app"

Cleanup

powershell
Remove-MgApplication -ApplicationId $app.Id
Disconnect-MgGraph

Expected Telemetry

Azure AD AuditLogs: OperationName 'Add application', Result 'success', TargetResources[0].displayName = 'df00tech-detection-test-app'. Followed by OperationName 'Update application' with ModifiedProperties showing RequiredResourceAccess including Mail.Read scope. Both events include InitiatedBy.user.userPrincipalName of the executing account.

Expected Detection

KQL Part 1 triggers on 'Add service principal' and 'Add application' OperationNames in AuditLogs. If executed immediately after a cross-tenant sign-in, the temporal correlation hunting query (huntingQueries[1]) also triggers within the 60-minute join window.

Related Detections