{
  "technique_id": "T1199",
  "name": "Trusted Relationship",
  "priority": "high",
  "status": "complete",
  "version": "0.1.0",
  "last_reviewed": "2026-07-23",
  "generated_by": "SOC Response Atlas by Basyrix",
  "tactics": [
    "Initial Access"
  ],
  "platforms": [
    "IaaS",
    "Identity Provider",
    "Linux",
    "macOS",
    "Office Suite",
    "SaaS",
    "Windows"
  ],
  "summary": "Adversaries may breach or otherwise leverage organizations who have access to intended victims. Access through trusted third party relationship 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...",
  "soc_recommendation": "Investigate Trusted Relationship activity in the context of Initial Access: confirm scope, affected host/identity, and whether it matches expected administrative behaviour before deciding this is benign.",
  "d3fend_mappings": [
    {
      "id": "D3-UGLPA",
      "name": "User Geolocation Logon Pattern Analysis",
      "relationship": "detect",
      "practical_action": "Monitor for User Geolocation Logon Pattern Analysis indicators relevant to this technique.",
      "tooling": [
        "Defender for Endpoint"
      ]
    },
    {
      "id": "D3-ST",
      "name": "Session Termination",
      "relationship": "evict",
      "practical_action": "Use Session Termination to remove the adversary's foothold once this technique is confirmed.",
      "tooling": [
        "Sentinel",
        "Defender for Endpoint"
      ]
    },
    {
      "id": "D3-NTF",
      "name": "Network Traffic Filtering",
      "relationship": "isolate",
      "practical_action": "Apply Network Traffic Filtering to contain the blast radius once this technique is observed.",
      "tooling": [
        "Sentinel",
        "Defender for Endpoint"
      ]
    }
  ],
  "investigation_steps": {
    "microsoft": [
      "Review Defender for Endpoint / Defender XDR alerts and timeline for the affected host or identity.",
      "Check Sentinel analytics rules and incidents correlated with this technique.",
      "Review Entra ID sign-in and audit logs if the technique involves an identity or cloud resource."
    ],
    "generic": [
      "Confirm whether the observed trusted relationship activity matches expected administrative or application behaviour.",
      "Identify the host, account, or resource where the activity occurred and its business criticality.",
      "Check for related alerts before and after this activity to reconstruct the broader intrusion timeline.",
      "Real detection reference: \"Trusted Relationship Anomalous Teams Admin Activity\" -- Distinct from IA-006's simple third-party sign-in rule: this looks for Teams admin actions after anomalous partner/guest sign-ins from rare countries/IPs."
    ]
  },
  "evidence_to_collect": [
    "Host or resource affected",
    "Account or identity involved",
    "Timestamp of the activity",
    "Related process, file, or network artifact",
    "Any preceding or follow-on alerts"
  ],
  "response_actions": [
    {
      "name": "Revoke the third-party account's access temporarily pending",
      "category": "Response",
      "risk": "Medium",
      "automation_safe": false,
      "approval_required": true,
      "tool": "See investigation guide",
      "notes": "Revoke the third-party account's access temporarily pending investigation"
    },
    {
      "name": "Contact vendor security team directly to confirm whether the",
      "category": "Response",
      "risk": "Low",
      "automation_safe": true,
      "approval_required": false,
      "tool": "See investigation guide",
      "notes": "Contact vendor security team directly to confirm whether their account/system is compromised"
    },
    {
      "name": "Review all actions taken by the vendor account in the last 3",
      "category": "Response",
      "risk": "Low",
      "automation_safe": true,
      "approval_required": false,
      "tool": "See investigation guide",
      "notes": "Review all actions taken by the vendor account in the last 30 days"
    },
    {
      "name": "If vendor compromise confirmed: treat as supply chain incide",
      "category": "Response",
      "risk": "Low",
      "automation_safe": true,
      "approval_required": false,
      "tool": "See investigation guide",
      "notes": "If vendor compromise confirmed: treat as supply chain incident — full tenant audit required"
    }
  ],
  "queries": {
    "kql": [
      {
        "name": "GEN-IA-019 — Trusted Relationship Anomalous Teams Admin Activity",
        "description": "Distinct from IA-006's simple third-party sign-in rule: this looks for Teams admin actions after anomalous partner/guest sign-ins from rare countries/IPs. (Source: Bell Integration baseline detection library.)",
        "query": "let minimumCountries = 2;\nlet deltaThreshold = 95;\nlet countryPrevalenceThreshold = 10;\nlet projectedEndTime = 60m;\nlet queryfrequency = 1d;\nlet queryperiod = 14d;\nlet TrustedRelationshipAllowIpWatchlist =\n    _GetWatchlist('wl-baseline-ia019-trusted-relationship-allow-ip')\n    | extend WatchlistIP = tostring(column_ifexists(\"SearchKey\", column_ifexists(\"remoteip\", column_ifexists(\"TrustedRelationshipAllowIP\", column_ifexists(\"IPAddress\", \"\")))))\n    | where isnotempty(WatchlistIP)\n    | project WatchlistIP;\nlet officeActivitySource =\n    union isfuzzy=true OfficeActivity,\n    (datatable(TimeGenerated:datetime, Operation:string, UserId:string)[]);\nlet aadFunc = (tableName: string) {\n    let signinData =\n        union isfuzzy=true\n            (SigninLogs | extend SourceTable = \"SigninLogs\"),\n            (AADNonInteractiveUserSignInLogs | extend SourceTable = \"AADNonInteractiveUserSignInLogs\"),\n            (datatable(TimeGenerated:datetime, AppDisplayName:string, ConditionalAccessStatus:string, LocationDetails:dynamic, IPAddress:string, UserPrincipalName:string, SourceTable:string)[])\n        | where SourceTable == tableName\n        | where TimeGenerated > ago(queryperiod)\n        | where AppDisplayName has \"Teams\" and ConditionalAccessStatus =~ \"success\"\n        | extend Country = tostring(todynamic(LocationDetails)['countryOrRegion'])\n        | where isnotempty(Country) and isnotempty(IPAddress)\n        | where not(IPAddress in (TrustedRelationshipAllowIpWatchlist));\n    let countryPrevalence =\n        signinData\n        | summarize CountCountrySignin = count() by Country\n        | extend TotalSignin = toscalar(signinData | summarize count())\n        | extend CountryPrevalence = toreal(CountCountrySignin) / toreal(TotalSignin) * 100;\n    let userIpSignin =\n        signinData\n        | summarize CountIPSignin = count(), Country = any(Country), ListSigninTimeGenerated = make_list(TimeGenerated) by IPAddress, UserPrincipalName;\n    let userIpDelta =\n        userIpSignin\n        | summarize MaxIPSignin = max(CountIPSignin), MinIPSignin = min(CountIPSignin), DistinctCountries = dcount(Country), Countries = make_set(Country) by UserPrincipalName\n        | extend UserIPDelta = toreal(MaxIPSignin - MinIPSignin) / toreal(MaxIPSignin) * 100;\n    officeActivitySource\n    | where TimeGenerated > ago(queryfrequency)\n    | where Operation in~ (\"TeamsAdminAction\", \"MemberAdded\", \"MemberRemoved\", \"MemberRoleChanged\", \"AppInstalled\", \"BotAddedToTeam\")\n    | project OperationTimeGenerated = TimeGenerated, UserId = tolower(UserId), Operation\n    | join kind=inner (\n        userIpDelta\n        | where DistinctCountries >= minimumCountries\n        | where UserIPDelta >= deltaThreshold\n        | join kind=leftouter userIpSignin on UserPrincipalName\n        | join kind=leftouter countryPrevalence on Country\n        | where CountryPrevalence < countryPrevalenceThreshold\n        | project UserPrincipalName, SuspiciousIP = IPAddress, UserIPDelta, DistinctCountries, Countries,\n                  SuspiciousSigninCountry = Country, SuspiciousCountryPrevalence = CountryPrevalence,\n                  EventTimes = ListSigninTimeGenerated\n    ) on $left.UserId == $right.UserPrincipalName\n    | mv-expand SigninTimeGenerated = EventTimes\n    | extend SigninTimeGenerated = todatetime(SigninTimeGenerated)\n    | where OperationTimeGenerated between (SigninTimeGenerated .. (SigninTimeGenerated + projectedEndTime))\n};\nlet aadSignin = aadFunc(\"SigninLogs\");\nlet aadNonInt = aadFunc(\"AADNonInteractiveUserSignInLogs\");\nunion isfuzzy=true aadSignin, aadNonInt\n| summarize arg_max(SigninTimeGenerated, *) by UserPrincipalName, SuspiciousIP, OperationTimeGenerated\n| summarize\n    ActivitySummary = make_bag(pack(tostring(SigninTimeGenerated), pack(\"Operation\", tostring(Operation), \"OperationTime\", OperationTimeGenerated))),\n    Countries = any(Countries),\n    DistinctCountries = max(DistinctCountries),\n    UserIPDelta = max(UserIPDelta)\n    by UserPrincipalName, SuspiciousIP, SuspiciousSigninCountry, SuspiciousCountryPrevalence\n| extend IPCustomEntity = SuspiciousIP,\n         AccountCustomEntity = UserPrincipalName"
      },
      {
        "name": "2b701288-b428-4fb8-805e-e4372c574786 — Anomalous login followed by Teams action",
        "description": "Detects anomalous IP address usage by user accounts and then checks to see if a suspicious Teams action is performed. Query calculates IP usage Delta for each user account and selects accounts where a delta >= 90% is observed between the most and least used IP. To further reduce results the query performs a prevalence check on the lowest used IP's country, only keeping IP's where the country is unusual for the tenant (dynamic ranges). Please note, if the initial logic of prevalence to find suspicious logon activity is noisy then consider adding filtering based on Location. Finally the user accounts activity within Teams logs is checked for suspicious commands (modifying user privileges or admin actions) during the period the suspicious IP was active.' (Source: Microsoft's official Azure-Sentinel Detections (MIT licensed).)",
        "query": "//The bigger the window the better the data sample size, as we use IP prevalence, more sample data is better.\n//The minimum number of countries that the account has been accessed from [default: 2]\nlet minimumCountries = 2;\n//The delta (%) between the largest in-use IP and the smallest [default: 95]\nlet deltaThreshold = 95;\n//The maximum (%) threshold that the country appears in login data [default: 10]\nlet countryPrevalenceThreshold = 10;\n//The time to project forward after the last login activity [default: 60min]\nlet projectedEndTime = 60m;\nlet queryfrequency = 1d;\nlet queryperiod = 14d;\nlet aadFunc = (tableName: string) {\n    // Get successful signins to Teams\n    let signinData =\n        table(tableName)\n        | where TimeGenerated > ago(queryperiod)\n        | where AppDisplayName has \"Teams\" and ConditionalAccessStatus =~ \"success\"\n        | extend Country = tostring(todynamic(LocationDetails)['countryOrRegion'])\n        | where isnotempty(Country) and isnotempty(IPAddress);\n    // Calculate prevalence of countries\n    let countryPrevalence =\n        signinData\n        | summarize CountCountrySignin = count() by Country\n        | extend TotalSignin = toscalar(signinData | summarize count())\n        | extend CountryPrevalence = toreal(CountCountrySignin) / toreal(TotalSignin) * 100;\n    // Count signins by user and IP address\n    let userIpSignin =\n        signinData\n        | summarize CountIPSignin = count(), Country = any(Country), ListSigninTimeGenerated = make_list(TimeGenerated) by IPAddress, UserPrincipalName;\n    // Calculate delta between the IP addresses with the most and minimum activity by user\n    let userIpDelta =\n        userIpSignin\n        | summarize MaxIPSignin = max(CountIPSignin), MinIPSignin = min(CountIPSignin), DistinctCountries = dcount(Country), make_set(Country) by UserPrincipalName\n        | extend UserIPDelta = toreal(MaxIPSignin - MinIPSignin) / toreal(MaxIPSignin) * 100;\n    // Collect Team operations the user account has performed within a time range of the suspicious signins\n    OfficeActivity\n    | where TimeGenerated > ago(queryfrequency)\n    | where Operation in~ (\"TeamsAdminAction\", \"MemberAdded\", \"MemberRemoved\", \"MemberRoleChanged\", \"AppInstalled\", \"BotAddedToTeam\")\n    | where not (Operation in~ (\"MemberAdded\", \"MemberRemoved\") and CommunicationType in~ (\"GroupChat\", \"OneonOne\")) // These events have been noisy and are related to initiaing chat conversation and not admin operations.\n    | project OperationTimeGenerated = TimeGenerated, UserId = tolower(UserId), Operation\n    | join kind = inner(\n        userIpDelta\n        // Check users with activity from distinct countries\n        | where DistinctCountries >= minimumCountries\n        // Check users with high IP delta\n        | where UserIPDelta >= deltaThreshold\n        // Add information about signins and countries\n        | join kind = leftouter userIpSignin on UserPrincipalName\n        | join kind = leftouter countryPrevalence on Country\n        // Check activity that comes from nonprevalent countries\n        | where CountryPrevalence < countryPrevalenceThreshold\n        | project\n            UserPrincipalName,\n            SuspiciousIP = IPAddress,\n            UserIPDelta,\n            SuspiciousSigninCountry = Country,\n            SuspiciousCountryPrevalence = CountryPrevalence,\n            EventTimes = ListSigninTimeGenerated\n    ) on $left.UserId == $right.UserPrincipalName\n    // Check the signins occured 60 min before the Teams operations\n    | mv-expand SigninTimeGenerated = EventTimes\n    | extend SigninTimeGenerated = todatetime(SigninTimeGenerated)\n    | where OperationTimeGenerated between (SigninTimeGenerated .. (SigninTimeGenerated + projectedEndTime))\n};\nlet aadSignin = aadFunc(\"SigninLogs\");\nlet aadNonInt = aadFunc(\"AADNonInteractiveUserSignInLogs\");\nunion isfuzzy=true aadSignin, aadNonInt\n| summarize arg_max(SigninTimeGenerated, *) by UserPrincipalName, SuspiciousIP, OperationTimeGenerated\n| summarize\n    ActivitySummary = make_bag(pack(tostring(SigninTimeGenerated), pack(\"Operation\", tostring(Operation), \"OperationTime\", OperationTimeGenerated)))\n    by UserPrincipalName, SuspiciousIP, SuspiciousSigninCountry, SuspiciousCountryPrevalence\n| extend AccountName = tostring(split(UserPrincipalName, \"@\")[0]), AccountUPNSuffix = tostring(split(UserPrincipalName, \"@\")[1])"
      },
      {
        "name": "adc32a33-1cd6-46f5-8801-e3ed8337885f — External Upstream Source Added to Azure DevOps Feed",
        "description": "The detection looks for new external sources added to an Azure DevOps feed. An allow list can be customized to explicitly allow known good sources. An attacker could look to add a malicious feed in order to inject malicious packages into a build pipeline.' (Source: Microsoft's official Azure-Sentinel Detections (MIT licensed).)",
        "query": "// Add any known allowed sources and source locations to the filter below (the NuGet Gallery has been added here as an example).\nlet allowed_sources = dynamic([\"NuGet Gallery\"]);\nlet allowed_locations = dynamic([\"https://api.nuget.org/v3/index.json\"]);\nADOAuditLogs\n// Look for feeds created or modified at either the organization or project level\n| where OperationName matches regex \"Artifacts.Feed.(Org|Project).Modify\"\n| where Details has \"UpstreamSources, added\"\n| extend FeedName = tostring(Data.FeedName)\n| extend FeedId = tostring(Data.FeedId)\n| extend UpstreamsAdded = Data.UpstreamsAdded\n// As multiple feeds may be added expand these out\n| mv-expand UpstreamsAdded\n// Only focus on external feeds\n| where UpstreamsAdded.UpstreamSourceType !~ \"internal\"\n| extend SourceLocation = tostring(UpstreamsAdded.Location)\n| extend SourceName = tostring(UpstreamsAdded.Name)\n// Exclude sources and locations in the allow list\n| where SourceLocation !in (allowed_locations) and SourceName !in (allowed_sources)\n| extend SourceProtocol = tostring(UpstreamsAdded.Protocol)\n| extend SourceStatus = tostring(UpstreamsAdded.Status)\n| project-reorder TimeGenerated, OperationName, ScopeDisplayName, ProjectName, FeedName, SourceName, SourceLocation, SourceProtocol, ActorUPN, UserAgent, IpAddress\n| extend timestamp = TimeGenerated\n| extend AccountName = tostring(split(ActorUPN, \"@\")[0]), AccountUPNSuffix = tostring(split(ActorUPN, \"@\")[1])"
      }
    ],
    "spl": [],
    "esql": [
      {
        "name": "1c6a8c7a-5cb6-4a82-ba27-d5a5b8a40a38 — Entra ID Illicit Consent Grant via Registered Application",
        "description": "(ESQL) Identifies an illicit consent grant request on-behalf-of a registered Entra ID application. Adversaries may create and register an application in Microsoft Entra ID for the purpose of requesting user consent to access resources. This is accomplished by tricking a user into granting consent to the application, typically via a pre-made phishing URL... (Source: Elastic's official detection-rules repository (Elastic License v2).)",
        "query": "FROM logs-azure.auditlogs-* metadata _id, _version, _index\n| WHERE (azure.auditlogs.operation_name == \"Consent to application\"\n    OR event.action == \"Consent to application\")\n  AND event.outcome == \"success\"\n\n| MV_EXPAND azure.auditlogs.properties.additional_details.value\n| WHERE azure.auditlogs.properties.additional_details.value\n    RLIKE \"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\"\n| RENAME azure.auditlogs.properties.additional_details.value AS Esql.app_id\n\n| STATS\n    Esql.timestamp_first_seen = MIN(@timestamp),\n    Esql.timestamp_last_seen = MAX(@timestamp),\n    Esql.app_display_name_values = VALUES(`azure.auditlogs.properties.target_resources.0.display_name`),\n    Esql.service_principal_id_values = VALUES(`azure.auditlogs.properties.target_resources.0.id`),\n    Esql.is_admin_consent_values = VALUES(`azure.auditlogs.properties.target_resources.0.modified_properties.0.new_value`),\n    Esql.is_app_only_values = VALUES(`azure.auditlogs.properties.target_resources.0.modified_properties.1.new_value`),\n    Esql.on_behalf_of_all_values = VALUES(`azure.auditlogs.properties.target_resources.0.modified_properties.2.new_value`),\n    Esql.consent_context_tags_values = VALUES(`azure.auditlogs.properties.target_resources.0.modified_properties.3.new_value`),\n    Esql.consent_permissions_values = VALUES(`azure.auditlogs.properties.target_resources.0.modified_properties.4.new_value`),\n    Esql.consent_reason_values = VALUES(`azure.auditlogs.properties.target_resources.0.modified_properties.5.new_value`),\n    Esql.user_id_values = VALUES(azure.auditlogs.properties.initiated_by.user.id),\n    Esql.ip_address_values = VALUES(azure.auditlogs.properties.initiated_by.user.ipAddress),\n    Esql.tenant_id_values = VALUES(azure.tenant_id),\n    Esql.correlation_id_values = VALUES(azure.auditlogs.properties.correlation_id),\n    Esql.event_count = COUNT(*)\n    BY azure.auditlogs.properties.initiated_by.user.userPrincipalName, Esql.app_id\n\n| WHERE Esql.timestamp_first_seen >= NOW() - 9 minutes\n| KEEP Esql.*, azure.*"
      },
      {
        "name": "1ca62f14-4787-4913-b7af-df11745a49da — New GitHub App Installed",
        "description": "(EQL) This rule detects when a new GitHub App has been installed in your organization account. GitHub Apps extend GitHub's functionality both within and outside of GitHub. When an app is installed it is granted permissions to read or modify your repository and organization data. Only trusted apps should be installed and any newly installed apps should be investigated to verify their legitimacy... (Source: Elastic's official detection-rules repository (Elastic License v2).)",
        "query": "configuration where data_stream.dataset == \"github.audit\" and event.action == \"integration_installation.create\""
      }
    ]
  },
  "automation": {
    "safe": [
      "Add recommendation as Sentinel incident comment.",
      "Run enrichment queries.",
      "Create ServiceNow SecOps task."
    ],
    "approval_required": [
      "Contain or disable the affected host/account.",
      "Any change to production configuration."
    ]
  },
  "escalation_criteria": [
    "Trusted Relationship activity observed on a privileged account or critical system.",
    "Activity follows or precedes other suspicious behaviour in the same investigation.",
    "Automated triage cannot confidently rule out malicious intent."
  ],
  "false_positive_considerations": [
    "Legitimate administrative or maintenance activity matching this pattern.",
    "Approved security testing or red team exercise.",
    "Known benign software producing similar telemetry."
  ],
  "confluence": {
    "title": "T1199 - Trusted Relationship Response Guidance",
    "labels": [
      "mitre",
      "attack",
      "d3fend",
      "secops",
      "basyrix"
    ],
    "sections": [
      "summary",
      "d3fend_mappings",
      "investigation_steps",
      "response_actions",
      "queries",
      "automation",
      "escalation_criteria",
      "false_positive_considerations"
    ]
  }
}