# T1528 - Steal Application Access Token

## SOC Recommendation
Investigate Steal Application Access Token activity in the context of Credential Access: confirm scope, affected host/identity, and whether it matches expected administrative behaviour before deciding this is benign.

## D3FEND Mappings
| D3FEND Technique | Relationship | Practical SOC Action |
|---|---|---|
| Credential Compromise Scope Analysis | Detect | Monitor for Credential Compromise Scope Analysis indicators relevant to this technique. |
| Credential Hardening | Harden | Apply Credential Hardening to reduce this technique's viability before an incident occurs. |
| Credential Revocation | Evict | Use Credential Revocation to remove the adversary's foothold once this technique is confirmed. |
| Credential Transmission Scoping | Isolate | Apply Credential Transmission Scoping to contain the blast radius once this technique is observed. |

## Investigation Steps
1. Review Defender for Endpoint / Defender XDR alerts and timeline for the affected host or identity.
2. Check Sentinel analytics rules and incidents correlated with this technique.
3. Review Entra ID sign-in and audit logs if the technique involves an identity or cloud resource.

## 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
| Action | Risk | Automation Safe | Approval Required |
|---|---|---|---|
| Contain the affected host or account | Medium | No | Yes |
| Collect and preserve evidence | Low | Yes | No |

## KQL
```kql
ContrastADRAttackEvents_CL
| where result =~ "EXPLOITED" and rule =~ "SQL-INJECTION"
| project-rename hostname = host_hostname
//please add your DLP logs table in place of ContrastWAFLogs_CL and hostname colomun in place of hostname below and uncomment the queries below
//| join kind= inner (ContrastWAFLogs_CL | where TimeGenerated >= ago(5m)) on hostname
```
```kql
let detectionTime = 1d;
let joinLookback = 14d;
let threshold = 5;
let o365_attack_regex = "contacts.read|user.read|mail.read|notes.read.all|mailboxsettings.readwrite|Files.ReadWrite.All|mail.send|files.read|files.read.all";
let o365_attack = dynamic(["contacts.read", "user.read", "mail.read", "notes.read.all", "mailboxsettings.readwrite", "Files.ReadWrite.All", "mail.send", "files.read", "files.read.all"]);
AuditLogs
| where TimeGenerated > ago(detectionTime)
| where LoggedByService =~ "Core Directory"
| where Category =~ "ApplicationManagement"
| where OperationName =~ "Consent to application"
| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "ServicePrincipal"
      | extend AppDisplayName = tostring(TargetResource.displayName),
               AppClientId = tostring(TargetResource.id),
               props = TargetResource.modifiedProperties
  )
| where AppClientId !in ((externaldata(knownAppClientId:string, knownAppDisplayName:string)[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/Microsoft.OAuth.KnownApplications.csv"] with (format="csv"))) // NOTE: a MATCH from this list will cause the alert to NOT fire - please modify for your environment!
| mv-apply ConsentFull = props on 
  (
      where ConsentFull.displayName =~ "ConsentAction.Permissions"
  )
| parse ConsentFull with * "ConsentType: " GrantConsentType ", Scope: " GrantScope1 ", CreatedDateTime" * "]" *
| where GrantConsentType != "AllPrincipals" // NOTE: we are ignoring if OAuth application was granted to all users via an admin - but admin due diligence should be audited occasionally
| where ConsentFull has_any (o365_attack)  
| extend GrantScopeCount = countof(tolower(GrantScope1), o365_attack_regex, 'regex')
| where GrantScopeCount > threshold
| extend GrantInitiatedByAppName = tostring(InitiatedBy.app.displayName)
| extend GrantInitiatedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend GrantInitiatedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend GrantInitiatedByAadUserId = tostring(InitiatedBy.user.id)
| extend GrantIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))
| extend GrantInitiatedBy = iff(isnotempty(GrantInitiatedByUserPrincipalName), GrantInitiatedByUserPrincipalName, GrantInitiatedByAppName)
| mv-apply AdditionalDetail = AdditionalDetails on 
  (
      where AdditionalDetail.key =~ "User-Agent"
      | extend GrantUserAgent = AdditionalDetail.value
  )
| project TimeGenerated, GrantConsentType, GrantScope1, GrantInitiatedBy, AppDisplayName, GrantInitiatedByUserPrincipalName, GrantInitiatedByAadUserId, GrantInitiatedByAppName, GrantInitiatedByAppServicePrincipalId, GrantIpAddress, GrantUserAgent, AppClientId, OperationName, ConsentFull, CorrelationId
| join kind = leftouter (AuditLogs
  | where TimeGenerated > ago(joinLookback)
  | where LoggedByService =~ "Core Directory"
  | where Category =~ "ApplicationManagement"
  | where OperationName =~ "Add service principal"
  | mv-apply TargetResource = TargetResources on 
      (
          where TargetResource.type =~ "ServicePrincipal"
          | extend props = TargetResource.modifiedProperties,
                  AppClientId = tostring(TargetResource.id)
      )
  | mv-apply Property = props on 
      (
          where Property.displayName =~ "AppAddress" and Property.newValue has "AddressType"
          | extend AppReplyURLs = trim('"',tostring(Property.newValue))
      )
  | distinct AppClientId, tostring(AppReplyURLs)
) on AppClientId
| join kind = innerunique (AuditLogs
      | where TimeGenerated > ago(joinLookback)
      | where LoggedByService =~ "Core Directory"
      | where Category =~ "ApplicationManagement"
      | where OperationName =~ "Add OAuth2PermissionGrant" or OperationName =~ "Add delegated permission grant"
          | mv-apply TargetResource = TargetResources on 
          (
              where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
              | extend GrantAuthentication = tostring(TargetResource.displayName)
          )
      | extend GrantOperation = OperationName
      | project GrantAuthentication, GrantOperation, CorrelationId
  ) on CorrelationId
| project TimeGenerated, GrantConsentType, GrantScope1, GrantInitiatedBy, AppDisplayName, AppReplyURLs, GrantInitiatedByUserPrincipalName, GrantInitiatedByAadUserId, GrantInitiatedByAppName, GrantInitiatedByAppServicePrincipalId, GrantIpAddress, GrantUserAgent, AppClientId, GrantAuthentication, OperationName, GrantOperation, CorrelationId, ConsentFull
| extend Name = tostring(split(GrantInitiatedByUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(GrantInitiatedByUserPrincipalName,'@',1)[0])
```
```kql
let timeframe = ago(5m);
DuoSecurityTrustMonitor_CL
| where TimeGenerated >= timeframe
| extend AccountName = tostring(split(surfaced_auth_user_name_s, "@")[0]), AccountUPNSuffix = tostring(split(surfaced_auth_user_name_s, "@")[1])
```

## Escalation Criteria
- Steal Application Access Token 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.

Generated by SOC Response Atlas by Basyrix.
