Microsoft Sentinel Analytic Rules
cloudbrothers.infoAzure Sentinel RepoToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

Azure DevOps Agent Pool Created Then Deleted

Back
Idacfdee3f-b794-404a-aeba-ef6a1fa08ad1
RulenameAzure DevOps Agent Pool Created Then Deleted
DescriptionAs well as adding build agents to an existing pool to execute malicious activity within a pipeline, an attacker could create a complete new agent pool and use this for execution.

Azure DevOps allows for the creation of agent pools with Azure hosted infrastructure or self-hosted infrastructure. Given the additional customizability of self-hosted agents this detection focuses on the creation of new self-hosted pools.

To further reduce false positive rates the detection looks for pools created and deleted relatively quickly (within 7 days by default), as an attacker is likely to remove a malicious pool once used in order to reduce/remove evidence of their activity.
SeverityHigh
TacticsDefenseEvasion
TechniquesT1578.002
KindScheduled
Query frequency7d
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/ADOAgentPoolCreatedDeleted.yaml
Version1.0.5
Arm templateacfdee3f-b794-404a-aeba-ef6a1fa08ad1.json
Deploy To Azure
let lookback = 14d;
let timewindow = 7d;
ADOAuditLogs
| where TimeGenerated > ago(lookback)
| where OperationName =~ "Library.AgentPoolCreated"
| extend AgentCloudId = tostring(Data.AgentCloudId)
| extend PoolType = iif(isnotempty(AgentCloudId), "Azure VMs", "Self Hosted")
// Comment this line out to include cloud pools as well
| where PoolType == "Self Hosted"
| extend AgentPoolName = tostring(Data.AgentPoolName)
| extend AgentPoolId = tostring(Data.AgentPoolId)
| extend IsHosted = tostring(Data.IsHosted)
| extend IsLegacy = tostring(Data.IsLegacy)
| extend timekey = bin(TimeGenerated, timewindow)
// Join only with pools deleted in the same window
| join (ADOAuditLogs
| where TimeGenerated > ago(lookback)
| where OperationName =~ "Library.AgentPoolDeleted"
| extend AgentPoolName = tostring(Data.AgentPoolName)
| extend AgentPoolId = tostring(Data.AgentPoolId)
| extend timekey = bin(TimeGenerated, timewindow)) on AgentPoolId, timekey
| project-reorder TimeGenerated, ActorUPN, UserAgent, IpAddress, AuthenticationMechanism, OperationName, AgentPoolName, IsHosted, IsLegacy, Data
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
description: |
  'As well as adding build agents to an existing pool to execute malicious activity within a pipeline, an attacker could create a complete new agent pool and use this for execution.
  Azure DevOps allows for the creation of agent pools with Azure hosted infrastructure or self-hosted infrastructure. Given the additional customizability of self-hosted agents this   detection focuses on the creation of new self-hosted pools.
  To further reduce false positive rates the detection looks for pools created and deleted relatively quickly (within 7 days by default), as an attacker is likely to remove a malicious pool once used in order to reduce/remove evidence of their activity.'  
kind: Scheduled
tactics:
- DefenseEvasion
requiredDataConnectors: []
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/ADOAgentPoolCreatedDeleted.yaml
severity: High
name: Azure DevOps Agent Pool Created Then Deleted
triggerThreshold: 0
queryPeriod: 14d
query: |
  let lookback = 14d;
  let timewindow = 7d;
  ADOAuditLogs
  | where TimeGenerated > ago(lookback)
  | where OperationName =~ "Library.AgentPoolCreated"
  | extend AgentCloudId = tostring(Data.AgentCloudId)
  | extend PoolType = iif(isnotempty(AgentCloudId), "Azure VMs", "Self Hosted")
  // Comment this line out to include cloud pools as well
  | where PoolType == "Self Hosted"
  | extend AgentPoolName = tostring(Data.AgentPoolName)
  | extend AgentPoolId = tostring(Data.AgentPoolId)
  | extend IsHosted = tostring(Data.IsHosted)
  | extend IsLegacy = tostring(Data.IsLegacy)
  | extend timekey = bin(TimeGenerated, timewindow)
  // Join only with pools deleted in the same window
  | join (ADOAuditLogs
  | where TimeGenerated > ago(lookback)
  | where OperationName =~ "Library.AgentPoolDeleted"
  | extend AgentPoolName = tostring(Data.AgentPoolName)
  | extend AgentPoolId = tostring(Data.AgentPoolId)
  | extend timekey = bin(TimeGenerated, timewindow)) on AgentPoolId, timekey
  | project-reorder TimeGenerated, ActorUPN, UserAgent, IpAddress, AuthenticationMechanism, OperationName, AgentPoolName, IsHosted, IsLegacy, Data
  | extend timestamp = TimeGenerated
  | extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])  
relevantTechniques:
- T1578.002
id: acfdee3f-b794-404a-aeba-ef6a1fa08ad1
queryFrequency: 7d
status: Available
triggerOperator: gt
version: 1.0.5
entityMappings:
- entityType: Account
  fieldMappings:
  - columnName: ActorUPN
    identifier: FullName
  - columnName: AccountName
    identifier: Name
  - columnName: AccountUPNSuffix
    identifier: UPNSuffix
- entityType: IP
  fieldMappings:
  - columnName: IpAddress
    identifier: Address