Detection rules › Kusto
Azure DevOps Personal Access Token (PAT) misuse
'This Alert detects whenever a PAT is used in ways that PATs are not normally used. May require an allow list and baselining. Reference - https://docs.microsoft.com/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page Use this query for baselining: ADOAuditLogs | distinct OperationName'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Impact |
Rule body
id: ac891683-53c3-4f86-86b4-c361708e2b2b
name: Azure DevOps Personal Access Token (PAT) misuse
description: |
'This Alert detects whenever a PAT is used in ways that PATs are not normally used. May require an allow list and baselining.
Reference - https://docs.microsoft.com/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page
Use this query for baselining:
ADOAuditLogs
| distinct OperationName'
severity: High
status: Available
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- Impact
relevantTechniques:
- T1496
- T1559
query: |
// Allowlisted UPNs should likely stay empty
let AllowlistedUpns = datatable(UPN:string)['foo@bar.com', 'test@foo.com'];
// Operation Name parts that will alert
let HasAnyBlocklist = datatable(OperationNamePart:string)['Security.','Project.','AuditLog.','Extension.'];
// Distinct Operation Names that will flag
let HasExactBlocklist = datatable(OperationName:string)['Group.UpdateGroupMembership.Add','Library.ServiceConnectionExecuted','Pipelines.PipelineModified',
'Release.ReleasePipelineModified', 'Git.RefUpdatePoliciesBypassed'];
ADOAuditLogs
| where AuthenticationMechanism startswith "PAT" and (OperationName has_any (HasAnyBlocklist) or OperationName in (HasExactBlocklist))
and ActorUPN !in (AllowlistedUpns)
| project TimeGenerated, AuthenticationMechanism, ProjectName, ActorUPN, ActorDisplayName, IpAddress, UserAgent, OperationName, Details, Data
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUPN
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
version: 1.0.6
kind: Scheduled
Stages and Predicates
Let binding: AllowlistedUpns
let AllowlistedUpns = datatable(UPN:string)['foo@bar.com', 'test@foo.com'];
Let binding: HasAnyBlocklist
let HasAnyBlocklist = datatable(OperationNamePart:string)['Security.','Project.','AuditLog.','Extension.'];
Let binding: HasExactBlocklist
let HasExactBlocklist = datatable(OperationName:string)['Group.UpdateGroupMembership.Add','Library.ServiceConnectionExecuted','Pipelines.PipelineModified',
'Release.ReleasePipelineModified', 'Git.RefUpdatePoliciesBypassed'];
Stage 1: source
ADOAuditLogs
Stage 2: where
| where AuthenticationMechanism startswith "PAT" and (OperationName has_any (HasAnyBlocklist) or OperationName in (HasExactBlocklist))
and ActorUPN !in (AllowlistedUpns)
Stage 3: project
| project TimeGenerated, AuthenticationMechanism, ProjectName, ActorUPN, ActorDisplayName, IpAddress, UserAgent, OperationName, Details, Data
Stage 4: extend
| extend timestamp = TimeGenerated
Stage 5: extend
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
ActorUPN | eq | AllowlistedUpns | excludes:ActorUPN field:"ActorUPN" value:"AllowlistedUpns" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AuthenticationMechanism | starts_with |
| field:"AuthenticationMechanism" kind:starts_with value:"PAT" |
OperationName | in |
| field:"OperationName" kind:in value:"HasExactBlocklist" |
OperationName | match |
| field:"OperationName" kind:match value:"HasAnyBlocklist" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorDisplayName | project |
ActorUPN | project |
AuthenticationMechanism | project |
Data | project |
Details | project |
IpAddress | project |
OperationName | project |
ProjectName | project |
TimeGenerated | project |
UserAgent | project |
timestamp | extend |
AccountName | extend |
AccountUPNSuffix | extend |