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.4
Arm templateacfdee3f-b794-404a-aeba-ef6a1fa08ad1.json
Deploy To Azure
let lookback = 14d;
let timewindow = 7d;
AzureDevOpsAuditing
| 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 (AzureDevOpsAuditing
| 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
name: Azure DevOps Agent Pool Created Then Deleted
requiredDataConnectors: []
entityMappings:
- fieldMappings:
  - identifier: FullName
    columnName: ActorUPN
  - identifier: Name
    columnName: AccountName
  - identifier: UPNSuffix
    columnName: AccountUPNSuffix
  entityType: Account
- fieldMappings:
  - identifier: Address
    columnName: IpAddress
  entityType: IP
triggerThreshold: 0
id: acfdee3f-b794-404a-aeba-ef6a1fa08ad1
tactics:
- DefenseEvasion
version: 1.0.4
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/ADOAgentPoolCreatedDeleted.yaml
queryPeriod: 14d
kind: Scheduled
queryFrequency: 7d
severity: High
status: Available
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.'  
query: |
  let lookback = 14d;
  let timewindow = 7d;
  AzureDevOpsAuditing
  | 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 (AzureDevOpsAuditing
  | 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])  
triggerOperator: gt
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2024-01-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/acfdee3f-b794-404a-aeba-ef6a1fa08ad1')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/acfdee3f-b794-404a-aeba-ef6a1fa08ad1')]",
      "properties": {
        "alertRuleTemplateName": "acfdee3f-b794-404a-aeba-ef6a1fa08ad1",
        "customDetails": null,
        "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.\nAzure 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.\nTo 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.'\n",
        "displayName": "Azure DevOps Agent Pool Created Then Deleted",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "ActorUPN",
                "identifier": "FullName"
              },
              {
                "columnName": "AccountName",
                "identifier": "Name"
              },
              {
                "columnName": "AccountUPNSuffix",
                "identifier": "UPNSuffix"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "IpAddress",
                "identifier": "Address"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/ADOAgentPoolCreatedDeleted.yaml",
        "query": "let lookback = 14d;\nlet timewindow = 7d;\nAzureDevOpsAuditing\n| where TimeGenerated > ago(lookback)\n| where OperationName =~ \"Library.AgentPoolCreated\"\n| extend AgentCloudId = tostring(Data.AgentCloudId)\n| extend PoolType = iif(isnotempty(AgentCloudId), \"Azure VMs\", \"Self Hosted\")\n// Comment this line out to include cloud pools as well\n| where PoolType == \"Self Hosted\"\n| extend AgentPoolName = tostring(Data.AgentPoolName)\n| extend AgentPoolId = tostring(Data.AgentPoolId)\n| extend IsHosted = tostring(Data.IsHosted)\n| extend IsLegacy = tostring(Data.IsLegacy)\n| extend timekey = bin(TimeGenerated, timewindow)\n// Join only with pools deleted in the same window\n| join (AzureDevOpsAuditing\n| where TimeGenerated > ago(lookback)\n| where OperationName =~ \"Library.AgentPoolDeleted\"\n| extend AgentPoolName = tostring(Data.AgentPoolName)\n| extend AgentPoolId = tostring(Data.AgentPoolId)\n| extend timekey = bin(TimeGenerated, timewindow)) on AgentPoolId, timekey\n| project-reorder TimeGenerated, ActorUPN, UserAgent, IpAddress, AuthenticationMechanism, OperationName, AgentPoolName, IsHosted, IsLegacy, Data\n| extend timestamp = TimeGenerated\n| extend AccountName = tostring(split(ActorUPN, \"@\")[0]), AccountUPNSuffix = tostring(split(ActorUPN, \"@\")[1])\n",
        "queryFrequency": "P7D",
        "queryPeriod": "P14D",
        "severity": "High",
        "status": "Available",
        "subTechniques": [
          "T1578.002"
        ],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "DefenseEvasion"
        ],
        "techniques": [
          "T1578"
        ],
        "templateVersion": "1.0.4",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}