T1528

Steal Application Access Token

Credential Access Last updated:

Adversaries may steal application access tokens as a means of acquiring credentials to access remote systems and resources. Application access tokens — including OAuth 2.0 tokens, Kubernetes service account tokens, cloud provider temporary credentials (Azure Managed Identity via IMDS, AWS STS instance role credentials, GCP service account tokens), and CI/CD pipeline secrets — authorize API requests on behalf of users or services. Token theft enables adversaries to impersonate legitimate identities, access cloud resources and SaaS platforms with the victim's permissions, and move laterally without requiring plaintext passwords. Real-world examples include APT29 stealing OAuth tokens via malicious application consent phishing, APT28 creating fraudulent OAuth apps masquerading as Google services, and threat actors exploiting compromised containers to extract Kubernetes service account tokens via the pod filesystem.

What is T1528 Steal Application Access Token?

Steal Application Access Token (T1528) maps to the Credential Access tactic — the adversary is trying to steal account names and passwords in MITRE ATT&CK.

This page provides production-ready detection logic for Steal Application Access Token, covering the data sources and telemetry it touches: Network: Network Connection Creation, File: File Access, Application Log: Application Log Content, Cloud Service: Cloud Service Metadata, Azure Active Directory: Audit Logs. 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
Credential Access
Technique
T1528 Steal Application Access Token
Canonical reference
https://attack.mitre.org/techniques/T1528/
Microsoft Sentinel / Defender
kusto
// T1528 — Steal Application Access Token
// Multi-vector detection covering IMDS token requests, Kubernetes token access,
// OAuth token cache file access, and high-privilege OAuth consent grants.

// --- Vector 1: Cloud metadata service (IMDS) token requests from unexpected processes ---
let LegitIMDSAgents = dynamic([
  "waagent.exe", "WindowsAzureGuestAgent.exe", "WaAppAgent.exe",
  "MonAgentCore.exe", "HealthService.exe", "MMAExtensionHeartbeatService.exe",
  "AzureAttestService.exe", "azd.exe", "AzureCLI.exe"
]);
let IMDSTokenRequests = DeviceNetworkEvents
| where Timestamp > ago(24h)
| where RemoteIP == "169.254.169.254"
| where RemoteUrl has_any ("metadata/identity", "latest/meta-data/iam/security-credentials", "computeMetadata/v1/instance/service-accounts", "metadata/instance")
| where InitiatingProcessFileName !in~ (LegitIMDSAgents)
| extend Vector = "IMDS Token Request"
| extend TokenPlatform = case(
    RemoteUrl has "metadata/identity", "Azure Managed Identity",
    RemoteUrl has "iam/security-credentials", "AWS Instance Role",
    RemoteUrl has "service-accounts", "GCP Service Account",
    "Cloud IMDS")
| extend RiskDetail = strcat("Unexpected process '", InitiatingProcessFileName, "' queried ", TokenPlatform, " IMDS endpoint")
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName,
          InitiatingProcessCommandLine, RemoteUrl, Vector, TokenPlatform, RiskDetail;

// --- Vector 2: Kubernetes service account token file reads ---
let LegitK8sProcesses = dynamic(["kubelet", "pause", "containerd-shim", "containerd-shim-runc-v2", "runc", "cri-o", "dockerd"]);
let K8sTokenAccess = DeviceFileEvents
| where Timestamp > ago(24h)
| where FolderPath has_any (
    "/var/run/secrets/kubernetes.io/serviceaccount",
    "/run/secrets/kubernetes.io",
    "/var/run/secrets/tokens")
| where FileName in~ ("token", "ca.crt")
| where InitiatingProcessFileName !in~ (LegitK8sProcesses)
| extend Vector = "Kubernetes Service Account Token"
| extend TokenPlatform = "Kubernetes"
| extend RiskDetail = strcat("Process '", InitiatingProcessFileName, "' read K8s service account token: ", FolderPath, "/", FileName)
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName,
          InitiatingProcessCommandLine, FileName, FolderPath, Vector, TokenPlatform, RiskDetail;

// --- Vector 3: OAuth / cloud CLI token cache access by unexpected processes ---
let LegitTokenOwners = dynamic([
  "gcloud", "gcloud.exe", "aws", "aws.exe", "az", "az.exe",
  "gh", "gh.exe", "git", "git.exe", "Code.exe", "code",
  "terraform", "terraform.exe", "kubectl", "kubectl.exe"
]);
let TokenCachePatterns = dynamic([
  "application_default_credentials.json",
  "accessTokens.json", "azureProfile.json",
  "msal_token_cache.json", "msal_token_cache.bin",
  "TokenCache.dat", ".git-credentials", "hosts.yml"
]);
let TokenCacheAccess = DeviceFileEvents
| where Timestamp > ago(24h)
| where FileName has_any (TokenCachePatterns)
    or (FolderPath has_any (".config/gcloud", ".azure", "TokenCache") and FileName endswith ".json")
| where ActionType in~ ("FileRead", "FileAccessed", "FileModified")
| where InitiatingProcessFileName !in~ (LegitTokenOwners)
| extend Vector = "OAuth Token Cache Access"
| extend TokenPlatform = case(
    FolderPath has ".config/gcloud" or FileName has "gcloud", "GCP",
    FolderPath has ".aws" or FileName has "aws", "AWS",
    FolderPath has ".azure" or FileName has "azure" or FileName has "msal", "Azure",
    FolderPath has ".gh" or FileName has "hosts.yml", "GitHub",
    "OAuth/Cloud")
| extend RiskDetail = strcat("Process '", InitiatingProcessFileName, "' accessed ", TokenPlatform, " token cache: ", FileName)
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName,
          InitiatingProcessCommandLine, FileName, FolderPath, Vector, TokenPlatform, RiskDetail;

// --- Vector 4: High-privilege OAuth consent grants in Azure AD ---
let HighRiskOAuthScopes = dynamic([
  "Mail.ReadWrite", "Mail.Read", "Files.ReadWrite.All",
  "User.Read.All", "Directory.ReadWrite.All",
  "offline_access", "full_access_as_user",
  "EWS.AccessAsUser.All", "Contacts.ReadWrite"
]);
let OAuthConsentGrants = AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName in ("Consent to application", "Add app role assignment to service principal", "Add delegated permission grant", "Add OAuth2PermissionGrant")
| where Result == "success"
| extend InitiatedByUser = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatedByIP = tostring(InitiatedBy.user.ipAddress)
| extend TargetApp = tostring(TargetResources[0].displayName)
| extend RawProps = tostring(TargetResources[0].modifiedProperties)
| where RawProps has_any (HighRiskOAuthScopes)
| extend Vector = "OAuth High-Privilege Consent Grant"
| extend TokenPlatform = "Azure AD / Microsoft 365"
| extend RiskDetail = strcat("User '", InitiatedByUser, "' consented to high-privilege OAuth app '", TargetApp, "' from IP: ", InitiatedByIP)
| project TimeGenerated, InitiatedByUser, InitiatedByIP, TargetApp, OperationName, RawProps, Vector, TokenPlatform, RiskDetail;

// --- Union all vectors ---
union
  (IMDSTokenRequests  | project Timestamp, Source=DeviceName,  Actor=AccountName,       Process=InitiatingProcessFileName, CommandLine=InitiatingProcessCommandLine, Vector, TokenPlatform, RiskDetail),
  (K8sTokenAccess     | project Timestamp, Source=DeviceName,  Actor=AccountName,       Process=InitiatingProcessFileName, CommandLine=InitiatingProcessCommandLine, Vector, TokenPlatform, RiskDetail),
  (TokenCacheAccess   | project Timestamp, Source=DeviceName,  Actor=AccountName,       Process=InitiatingProcessFileName, CommandLine=InitiatingProcessCommandLine, Vector, TokenPlatform, RiskDetail),
  (OAuthConsentGrants | project Timestamp=TimeGenerated, Source=InitiatedByIP, Actor=InitiatedByUser, Process=TargetApp, CommandLine=RawProps, Vector, TokenPlatform, RiskDetail)
| sort by Timestamp desc

Multi-vector detection for application access token theft using Microsoft Defender for Endpoint and Microsoft Sentinel tables. Covers four attack patterns: (1) unexpected processes querying cloud IMDS endpoints (169.254.169.254) for managed identity tokens across Azure, AWS, and GCP; (2) processes other than legitimate container runtimes reading Kubernetes service account token files from /var/run/secrets; (3) unexpected processes accessing cloud CLI OAuth token cache files (gcloud, az, aws, gh credentials); (4) high-privilege OAuth consent grants in Azure AD audit logs indicating potential OAuth phishing app abuse. Results are unioned into a common schema and sorted by time for triage.

high severity medium confidence

Data Sources

Network: Network Connection Creation File: File Access Application Log: Application Log Content Cloud Service: Cloud Service Metadata Azure Active Directory: Audit Logs

Required Tables

DeviceNetworkEvents DeviceFileEvents AuditLogs

False Positives

  • Security scanning tools and vulnerability assessment agents that enumerate IMDS endpoints as part of cloud posture checks (e.g., Prisma Cloud, Wiz, Orca)
  • Developer workstations where developers legitimately use multiple cloud CLIs (gcloud, az, aws) and IDEs that access token caches on behalf of the user
  • Legitimate Kubernetes operators and custom controllers that mount and read service account tokens as part of their normal authentication flow to the Kubernetes API
  • CI/CD pipeline agents (GitHub Actions runner, GitLab Runner, Jenkins agent) that access cloud credentials and token caches as part of authorized deployment workflows
  • IT administration scripts that use OAuth tokens for legitimate bulk operations (e.g., Microsoft Graph scripts for user provisioning, Azure automation runbooks)

Sigma rule & cross-platform mapping

The detection logic for Steal Application Access Token (T1528) 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:
  category: network_connection
  product: windows

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 1Query Azure IMDS Endpoint for Managed Identity Token

    Expected signal: Sysmon Event ID 3: Network Connection from powershell.exe to 169.254.169.254:80. DeviceNetworkEvents in MDE: RemoteIP=169.254.169.254, InitiatingProcessFileName=powershell.exe. The RemoteUrl field will contain the metadata identity path.

  2. Test 2Read Kubernetes Service Account Token from Pod Filesystem

    Expected signal: Linux auditd syscall audit event for openat/read on /var/run/secrets/kubernetes.io/serviceaccount/token with the process name (cat or the shell). Sysmon for Linux Event ID 11 (if deployed) for file access. The token value (JWT) will be visible in any memory or command output capture.

  3. Test 3Enumerate and Exfiltrate Azure CLI Token Cache

    Expected signal: Sysmon Event ID 11: File access/creation event with TargetFilename matching *msal_token_cache.json and Image=powershell.exe. DeviceFileEvents in MDE: FileName=msal_token_cache.json, ActionType=FileRead or FileAccessed, InitiatingProcessFileName=powershell.exe.

  4. Test 4Register Malicious OAuth App and Simulate Consent Phishing Link

    Expected signal: Azure AD AuditLogs OperationName='Add application' followed by 'Update application' with permissions modification. The registered app will appear in AuditLogs with the requesting user's UPN and source IP. If a test user clicks the generated consent URL, AuditLogs will show OperationName='Consent to application' with the scopes granted.


Response Playbook

Triage

  1. Identify the vector: IMDS query, Kubernetes token read, OAuth cache access, or OAuth consent grant. Each requires a different investigation path — IMDS and K8s are host-based; OAuth consent is identity-centric.
  2. For IMDS hits: identify the process that made the request (InitiatingProcessFileName / Image). Is this a known agent, an admin tool, or a suspicious binary? Check the process parent chain — was it spawned from a web server, a deserialization framework, or a container workload?
  3. For Kubernetes token reads: determine the container and namespace involved. Run: kubectl get pod <pod-name> -n <namespace> -o yaml to check the service account bound to the pod and its RBAC permissions (kubectl get clusterrolebindings,rolebindings -o wide). A token with cluster-admin is critical.
  4. For OAuth token cache access: determine if the process is a known credential-harvesting tool (LaZagne, SharpCloud, Mimikatz cloud module). Check VirusTotal and EDR telemetry for the initiating binary hash.
  5. For OAuth consent grants: review the granted application — is it registered in your tenant or external? Check the app's redirect URIs for suspicious domains. Query AuditLogs for the full consent event and map the user's location against their sign-in baseline in AADSignInLogs.
  6. Correlate the suspicious token access event with subsequent API calls: did the process or user account make cloud API calls (Azure Resource Manager, AWS API, Kubernetes API) immediately after the token access? This confirms successful theft and active use.
  7. Check whether the access token has already been used: query CloudAppEvents or SigninLogs for the affected application/identity in the 30 minutes following the token access event.

Containment

  1. For Kubernetes token theft: immediately revoke the service account token by deleting and recreating the ServiceAccount secret: kubectl delete secret <token-secret-name> -n <namespace>. Rotate the service account to generate a new token. If cluster-admin scope was exposed, treat the entire cluster as compromised.
  2. For IMDS-based token theft: immediately revoke the Managed Identity's role assignments in Azure IAM (az role assignment delete) or AWS IAM (aws iam remove-role-from-instance-profile). Detach and re-attach the identity after forensic capture to generate new credentials.
  3. For OAuth consent grant abuse: immediately revoke the OAuth consent in Azure AD portal (Enterprise Applications > [App] > Permissions > Revoke admin consent), or via PowerShell: Revoke-AzureADUserAllRefreshToken -ObjectId <user-id>. Block the malicious app's client ID via Conditional Access policy.
  4. For cloud CLI token cache theft: revoke all active sessions for the affected user (az ad user revoke-sign-in-sessions --id <upn>), rotate any service principal credentials associated with the tokens, and force re-authentication. For AWS, deactivate the affected IAM instance profile role temporarily.
  5. Isolate the compromised host if process-based token theft is confirmed — the endpoint is likely compromised and token theft may be a secondary action after initial access.
  6. Audit all actions taken with the stolen token by reviewing cloud provider activity logs (Azure Activity Log, AWS CloudTrail, GCP Cloud Audit Logs) for the identity in the time window following the theft event.

Evidence Collection

  1. Kubernetes token content and RBAC permissions: kubectl auth can-i --list --as=system:serviceaccount:<namespace>:<serviceaccount> — enumerate what the stolen token could do
  2. Azure Managed Identity token request log: check Azure IMDS access via Azure Monitor VM diagnostics or Defender for Cloud alert timeline for the affected VM
  3. OAuth app registration details: az ad app show --id <client-id> — capture redirect URIs, required permissions, and owner UPN for the consented application
  4. Cloud provider activity logs for the stolen identity: az monitor activity-log list --caller <client-id> (Azure), aws cloudtrail lookup-events --lookup-attributes AttributeKey=Username,AttributeValue=<role-name> (AWS)
  5. Process memory dump of the process that accessed the token (if still running) — may contain the raw token value: procdump.exe -ma <PID> C:\forensics\token_process.dmp
  6. Sysmon Event ID 1 parent process chain for the token-accessing process — reconstruct the full execution tree to identify initial access vector
  7. Network connections made by the process immediately after token access: Sysmon Event ID 3 or DeviceNetworkEvents correlated by ProcessId and Timestamp within a 5-minute window
  8. File system artifacts: %APPDATA%\.azure\accessTokens.json, ~/.config/gcloud/application_default_credentials.json, ~/.aws/credentials — preserve timestamps and content for forensic review

Escalation Criteria

  • ! Kubernetes service account token with cluster-admin or high-privilege RBAC role (edit, admin on sensitive namespaces) was accessed — this represents full cluster compromise potential
  • ! Azure Managed Identity token requested from a process running inside a container or web application context (e.g., nginx, java, python workers) — indicates server-side exploitation enabling cloud lateral movement
  • ! OAuth consent grant for offline_access scope (refresh token issued) — adversary can maintain long-term access that survives password resets without recurring phishing
  • ! Stolen token subsequently used from a geographically anomalous IP — confirmed token exfiltration and active use in a separate environment
  • ! CI/CD pipeline token accessed during off-hours by an unexpected process — indicates supply chain compromise with potential to affect all deployment targets
  • ! Multiple token vectors triggered on the same host within a short window (e.g., IMDS request + token cache access) — indicates systematic credential harvesting, not a single misconfiguration

Investigation Guide

Forensic Artifacts

  • > Linux: /var/run/secrets/kubernetes.io/serviceaccount/token — the actual JWT service account token; decode with: cat token | cut -d. -f2 | base64 -d | python3 -m json.tool to inspect claims and expiration
  • > Linux: /proc/<PID>/environ — environment variables of the token-stealing process; may contain KUBERNETES_SERVICE_TOKEN, AZURE_CLIENT_SECRET, or similar injected credentials
  • > Windows: %USERPROFILE%\.azure\accessTokens.json and %USERPROFILE%\.azure\msal_token_cache.json — Azure CLI token caches including access and refresh tokens
  • > Windows: %USERPROFILE%\.aws\credentials and %USERPROFILE%\.aws\config — AWS credential files potentially containing STS temporary tokens
  • > Windows: %APPDATA%\GitHub CLI\hosts.yml — GitHub personal access tokens stored by gh CLI
  • > Cloud: Azure Activity Log (az monitor activity-log list) — all Azure Resource Manager operations performed by the stolen Managed Identity
  • > Cloud: AWS CloudTrail (aws cloudtrail lookup-events) — all API calls made using stolen instance role credentials, with source IP addresses
  • > Azure AD: AuditLogs table OperationName='Add OAuth2PermissionGrant' — full history of OAuth consent events including granted scopes and consenting user
  • > Azure AD: SigninLogs with AuthenticationDetails — token-based sign-ins lack password authentication step, identifiable by authentication method = 'oauth2 token' or 'service principal'

Tuning Guidance

Start by building an allowlist of legitimate IMDS-querying processes for each VM role in your environment. Azure VMs will have waagent.exe, WindowsAzureGuestAgent.exe, and monitoring agents; document these per image type. For Kubernetes, the set of legitimate token-reading processes is determined by your container runtime (containerd, CRI-O) and any operators deployed. For OAuth consent, configure Azure AD to require admin consent for all applications requesting high-privilege delegated permissions — this eliminates the user-consent phishing vector entirely and converts all consent grants into auditable admin actions. For CI/CD environments, exclude known pipeline agent processes (runner, agent, executor) by combining process name with parent process name for higher specificity. The IMDS detection will have a higher false positive rate on developer machines where engineers run tools like terraform, pulumi, or ansible that legitimately call IMDS — consider scoping this detection to production server and container workloads only, excluding developer workstation collections. For the OAuth hunt, set a 7-day rolling window and exclude known first-party Microsoft applications (by verifying the app's verified publisher status). Adjust ClusterCIDR in the K8s hunting query to match your actual pod/node network range.


Hunting Queries

Hunt for processes querying the cloud IMDS endpoint for the first time on a given device. A first-time IMDS caller that is not a known cloud agent is highly suspicious and may indicate an attacker attempting to obtain a managed identity token post-compromise. Compares last 24 hours against a 30-day baseline of known callers per device.

Hunting — KQL
kql
// Hunt: Processes querying IMDS that have never done so before on this device (first-time IMDS callers)
let HistoricalIMDS = DeviceNetworkEvents
| where Timestamp between (ago(30d) .. ago(24h))
| where RemoteIP == "169.254.169.254"
| summarize KnownCallers=make_set(InitiatingProcessFileName) by DeviceName;
DeviceNetworkEvents
| where Timestamp > ago(24h)
| where RemoteIP == "169.254.169.254"
| join kind=leftanti HistoricalIMDS on $left.DeviceName == $right.DeviceName, $left.InitiatingProcessFileName == $right.KnownCallers
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteUrl
| sort by Timestamp desc
Hunting — SPL
spl
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3 DestinationIp="169.254.169.254" earliest=-30d latest=-24h
| stats values(Image) as HistoricalCallers by host
| join type=left host [
    search index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3 DestinationIp="169.254.169.254" earliest=-24h
    | table host, Image, CommandLine, _time ]
| eval IsNew=if(NOT mvfind(HistoricalCallers, Image) >= 0, "NEW", "KNOWN")
| where IsNew="NEW"
| table _time, host, Image, CommandLine, IsNew

Hunt for OAuth applications that have received high-privilege consent from multiple users or from multiple source IPs. OAuth phishing campaigns (APT28/APT29 style) typically trick many users into granting the same malicious app access. A single app collecting consents from multiple users, especially across different IPs or short time windows, is a strong indicator of a coordinated OAuth phishing operation.

Hunting — KQL
kql
// Hunt: OAuth apps with high-privilege permissions consented to by multiple users or from anomalous IPs
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName has_any ("Consent to application", "Add OAuth2PermissionGrant")
| where Result == "success"
| extend User = tostring(InitiatedBy.user.userPrincipalName)
| extend IP = tostring(InitiatedBy.user.ipAddress)
| extend AppName = tostring(TargetResources[0].displayName)
| extend Scopes = tostring(TargetResources[0].modifiedProperties)
| where Scopes has_any ("Mail.Read", "offline_access", "Files.ReadWrite", "User.Read.All", "Directory.ReadWrite")
| summarize ConsentCount=count(), UniqueUsers=dcount(User), UniqueIPs=dcount(IP), UserList=make_set(User), IPList=make_set(IP) by AppName, Scopes
| where ConsentCount > 1 or UniqueIPs > 1
| sort by ConsentCount desc
Hunting — SPL
spl
index=o365 sourcetype="o365:management:activity" (Operation="Consent to application" OR Operation="Add OAuth2PermissionGrant")
  (ModifiedProperties="*Mail.Read*" OR ModifiedProperties="*offline_access*" OR ModifiedProperties="*Files.ReadWrite*" OR ModifiedProperties="*User.Read.All*")
earliest=-7d
| stats count as ConsentCount, dc(UserId) as UniqueUsers, values(UserId) as UserList, dc(ClientIP) as UniqueIPs, values(ClientIP) as IPList by AppDisplayName, ModifiedProperties
| where ConsentCount > 1 OR UniqueIPs > 1
| sort - ConsentCount

Hunt for Kubernetes service account tokens being accessed frequently or across multiple hosts, and for token-based authentication to Kubernetes APIs originating from outside the expected cluster IP range. Legitimate in-cluster service account usage is bounded by the pod network CIDR; token reuse from external IPs indicates the token has been extracted and is being used by an adversary from outside the cluster.

Hunting — KQL
kql
// Hunt: Kubernetes service account tokens used from outside the cluster IP range
// Identifies JWT tokens being used from IPs not associated with your cluster nodes
let ClusterCIDR = "10.0.0.0/8"; // Adjust to your cluster network range
AuditLogs
| where TimeGenerated > ago(7d)
| where Category == "ServicePrincipalSignInLogs" or OperationName has "kubernetes"
| extend CallerIP = tostring(InitiatedBy.app.ipAddress)
| extend ServicePrincipal = tostring(InitiatedBy.app.displayName)
| where isnotempty(CallerIP)
| where ipv4_is_in_range(CallerIP, ClusterCIDR) == false
| where ServicePrincipal has_any ("system:serviceaccount", "kubernetes")
| project TimeGenerated, ServicePrincipal, CallerIP, OperationName, Result
| sort by TimeGenerated desc
Hunting — SPL
spl
index=linux_audit type=SYSCALL syscall=openat
  (key="k8s_token_access" OR comm!="kubelet")
  name="/var/run/secrets/kubernetes.io/serviceaccount/token"
earliest=-7d
| stats count as AccessCount, dc(host) as UniqueHosts, values(comm) as Processes, values(pid) as PIDs by name, auid
| where AccessCount > 5 OR UniqueHosts > 1
| sort - AccessCount

Atomic Red Team Tests

Test 1 Query Azure IMDS Endpoint for Managed Identity Token
windows

Simulates an adversary who has compromised an Azure VM or container and queries the Instance Metadata Service (IMDS) to obtain a Managed Identity access token for the Azure Resource Manager API. This is a primary technique used in cloud lateral movement after initial access to a compute resource. On a machine without an assigned Managed Identity, the request will return a 400 error, but the network connection event is still generated for detection testing.

Command

powershell
powershell.exe -Command "$headers = @{'Metadata'='true'}; Invoke-RestMethod -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -Headers $headers -Method GET | ConvertTo-Json"

Expected Telemetry

Sysmon Event ID 3: Network Connection from powershell.exe to 169.254.169.254:80. DeviceNetworkEvents in MDE: RemoteIP=169.254.169.254, InitiatingProcessFileName=powershell.exe. The RemoteUrl field will contain the metadata identity path.

Expected Detection

Alert fires on Vector='IMDS Token Request'. KQL: IMDSTokenRequests sub-query matches powershell.exe querying 169.254.169.254 with identity path. SPL: Sysmon Event ID 3 with DestinationIp=169.254.169.254 and Image containing powershell.exe triggers the union branch.

Test 2 Read Kubernetes Service Account Token from Pod Filesystem
linux

Simulates an adversary who has gained execution within a Kubernetes pod and reads the auto-mounted service account token to use for direct Kubernetes API calls. This is a standard technique in container escape and lateral movement playbooks. The token is a JWT that can be decoded and used with kubectl or direct API calls.

Command

bash
cat /var/run/secrets/kubernetes.io/serviceaccount/token && echo '' && cat /var/run/secrets/kubernetes.io/serviceaccount/namespace

Expected Telemetry

Linux auditd syscall audit event for openat/read on /var/run/secrets/kubernetes.io/serviceaccount/token with the process name (cat or the shell). Sysmon for Linux Event ID 11 (if deployed) for file access. The token value (JWT) will be visible in any memory or command output capture.

Expected Detection

K8sTokenAccess sub-query matches FolderPath containing /var/run/secrets/kubernetes.io/serviceaccount and FileName=token where InitiatingProcessFileName=cat. SPL Sysmon Event ID 11 branch fires on TargetFilename matching kubernetes.io token path.

Test 3 Enumerate and Exfiltrate Azure CLI Token Cache
windows

Simulates a credential harvester reading the Azure CLI OAuth token cache from a compromised Windows workstation. LaZagne and similar tools perform this exact operation. The access token in this file can be used directly with az CLI commands or raw REST API calls without re-authenticating. This test only reads the file path — no token is transmitted anywhere.

Command

powershell
powershell.exe -Command "$tokenPath = "$env:USERPROFILE\.azure\msal_token_cache.json"; if (Test-Path $tokenPath) { $content = Get-Content $tokenPath -Raw; Write-Output "Token cache found: $($content.Length) bytes"; $content | Select-String -Pattern '\"secret\"' | Select-Object -First 3 } else { Write-Output 'Azure CLI token cache not found on this system' }"

Expected Telemetry

Sysmon Event ID 11: File access/creation event with TargetFilename matching *msal_token_cache.json and Image=powershell.exe. DeviceFileEvents in MDE: FileName=msal_token_cache.json, ActionType=FileRead or FileAccessed, InitiatingProcessFileName=powershell.exe.

Expected Detection

TokenCacheAccess sub-query fires on FileName matching msal_token_cache.json accessed by powershell.exe (not in LegitTokenOwners list). Vector='Azure OAuth Token Cache'. SPL Sysmon Event ID 11 branch fires on TargetFilename matching msal_token_cache. SuspicionScore=2.

Test 4 Register Malicious OAuth App and Simulate Consent Phishing Link
windows

Simulates the preparatory steps an adversary takes before an OAuth phishing campaign: registering an application in Azure AD, setting required permissions, and generating a consent URL that would be sent to target users. This tests the AuditLogs detection for new app registrations with high-privilege scopes. Requires az CLI authenticated with sufficient Azure AD permissions. IMPORTANT: Run only in a non-production test tenant.

Command

powershell
az ad app create --display-name "Test-Detection-App-T1528" --required-resource-accesses "[{\"resourceAppId\": \"00000003-0000-0000-c000-000000000000\", \"resourceAccess\": [{\"id\": \"570282fd-fa5c-430d-a7fd-fc8dc98a9dca\", \"type\": \"Scope\"}]}]" --reply-urls "https://localhost" 2>&1

Cleanup

powershell
az ad app delete --id $(az ad app list --display-name 'Test-Detection-App-T1528' --query '[0].appId' -o tsv) 2>&1

Expected Telemetry

Azure AD AuditLogs OperationName='Add application' followed by 'Update application' with permissions modification. The registered app will appear in AuditLogs with the requesting user's UPN and source IP. If a test user clicks the generated consent URL, AuditLogs will show OperationName='Consent to application' with the scopes granted.

Expected Detection

OAuthConsentGrants sub-query fires when consent is granted for the registered app if it includes high-privilege scopes. The app registration itself fires related detections in T1136 (Create Account) and application monitoring rules. SPL o365:management:activity branch fires on Operation='Consent to application'.

Related Detections

Detection Variants (1)

Different telemetry and tradecraft for the same technique — pick the one that matches the data you collect.