Azure DevOps Pipeline Created and Deleted on the Same Day
Id | 17f23fbe-bb73-4324-8ecf-a18545a5dc26 |
Rulename | Azure DevOps Pipeline Created and Deleted on the Same Day |
Description | An attacker with access to Azure DevOps could create a pipeline to inject artifacts used by other pipelines, or to create a malicious software build that looks legitimate by using a pipeline that incorporates legitimate elements. An attacker would also likely want to cover their tracks once conducting such activity. This query looks for Pipelines created and deleted within the same day, this is unlikely to be legitimate user activity in the majority of cases. |
Severity | Medium |
Tactics | Execution |
Techniques | T1072 |
Kind | Scheduled |
Query frequency | 3d |
Query period | 3d |
Trigger threshold | 0 |
Trigger operator | gt |
Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOPipelineCreatedDeletedOneDay.yaml |
Version | 1.0.4 |
Arm template | 17f23fbe-bb73-4324-8ecf-a18545a5dc26.json |
let timeframe = 3d;
// Get Release Pipeline Creation Events and group by day
ADOAuditLogs
| where TimeGenerated > ago(timeframe)
| where OperationName =~ "Release.ReleasePipelineCreated"
// Group by day
| extend timekey = bin(TimeGenerated, 1d)
| extend PipelineId = tostring(Data.PipelineId)
| extend PipelineName = tostring(Data.PipelineName)
// Rename some columns to make output clearer
| project-rename TimeCreated = TimeGenerated, CreatingUser = ActorUPN, CreatingUserAgent = UserAgent, CreatingIP = IpAddress
// Join with Release Pipeline Deletions where Pipeline ID is the same and deletion occurred on same day as creation
| join (ADOAuditLogs
| where TimeGenerated > ago(timeframe)
| where OperationName =~ "Release.ReleasePipelineDeleted"
// Group by day
| extend timekey = bin(TimeGenerated, 1d)
| extend PipelineId = tostring(Data.PipelineId)
| extend PipelineName = tostring(Data.PipelineName)
// Rename some things to make the output clearer
| project-rename TimeDeleted = TimeGenerated,DeletingUser = ActorUPN, DeletingUserAgent = UserAgent, DeletingIP = IpAddress) on PipelineId, timekey
| project TimeCreated, TimeDeleted, PipelineName, PipelineId, CreatingUser, CreatingIP, CreatingUserAgent, DeletingUser, DeletingIP, DeletingUserAgent, ScopeDisplayName, ProjectName, Data, OperationName, OperationName1
| extend timestamp = TimeCreated
| extend CreatingUserAccountName = tostring(split(CreatingUser, "@")[0]), CreatingUserAccountUPNSuffix = tostring(split(CreatingUser, "@")[1])
| extend DeletingUserAccountName = tostring(split(DeletingUser, "@")[0]), DeletingUserAccountUPNSuffix = tostring(split(DeletingUser, "@")[1])
queryPeriod: 3d
entityMappings:
- fieldMappings:
- identifier: FullName
columnName: CreatingUser
- identifier: Name
columnName: CreatingUserAccountName
- identifier: UPNSuffix
columnName: CreatingUserAccountUPNSuffix
entityType: Account
- fieldMappings:
- identifier: FullName
columnName: DeletingUser
- identifier: Name
columnName: DeletingUserAccountName
- identifier: UPNSuffix
columnName: DeletingUserAccountUPNSuffix
entityType: Account
- fieldMappings:
- identifier: Address
columnName: CreatingIP
entityType: IP
- fieldMappings:
- identifier: Address
columnName: DeletingIP
entityType: IP
relevantTechniques:
- T1072
severity: Medium
requiredDataConnectors: []
id: 17f23fbe-bb73-4324-8ecf-a18545a5dc26
triggerOperator: gt
kind: Scheduled
query: |
let timeframe = 3d;
// Get Release Pipeline Creation Events and group by day
ADOAuditLogs
| where TimeGenerated > ago(timeframe)
| where OperationName =~ "Release.ReleasePipelineCreated"
// Group by day
| extend timekey = bin(TimeGenerated, 1d)
| extend PipelineId = tostring(Data.PipelineId)
| extend PipelineName = tostring(Data.PipelineName)
// Rename some columns to make output clearer
| project-rename TimeCreated = TimeGenerated, CreatingUser = ActorUPN, CreatingUserAgent = UserAgent, CreatingIP = IpAddress
// Join with Release Pipeline Deletions where Pipeline ID is the same and deletion occurred on same day as creation
| join (ADOAuditLogs
| where TimeGenerated > ago(timeframe)
| where OperationName =~ "Release.ReleasePipelineDeleted"
// Group by day
| extend timekey = bin(TimeGenerated, 1d)
| extend PipelineId = tostring(Data.PipelineId)
| extend PipelineName = tostring(Data.PipelineName)
// Rename some things to make the output clearer
| project-rename TimeDeleted = TimeGenerated,DeletingUser = ActorUPN, DeletingUserAgent = UserAgent, DeletingIP = IpAddress) on PipelineId, timekey
| project TimeCreated, TimeDeleted, PipelineName, PipelineId, CreatingUser, CreatingIP, CreatingUserAgent, DeletingUser, DeletingIP, DeletingUserAgent, ScopeDisplayName, ProjectName, Data, OperationName, OperationName1
| extend timestamp = TimeCreated
| extend CreatingUserAccountName = tostring(split(CreatingUser, "@")[0]), CreatingUserAccountUPNSuffix = tostring(split(CreatingUser, "@")[1])
| extend DeletingUserAccountName = tostring(split(DeletingUser, "@")[0]), DeletingUserAccountUPNSuffix = tostring(split(DeletingUser, "@")[1])
tactics:
- Execution
triggerThreshold: 0
queryFrequency: 3d
status: Available
description: |
'An attacker with access to Azure DevOps could create a pipeline to inject artifacts used by other pipelines, or to create a malicious software build that looks legitimate by using a pipeline that incorporates legitimate elements.
An attacker would also likely want to cover their tracks once conducting such activity. This query looks for Pipelines created and deleted within the same day, this is unlikely to be legitimate user activity in the majority of cases.'
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOPipelineCreatedDeletedOneDay.yaml
name: Azure DevOps Pipeline Created and Deleted on the Same Day
version: 1.0.4
{
"$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/17f23fbe-bb73-4324-8ecf-a18545a5dc26')]",
"kind": "Scheduled",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/17f23fbe-bb73-4324-8ecf-a18545a5dc26')]",
"properties": {
"alertRuleTemplateName": "17f23fbe-bb73-4324-8ecf-a18545a5dc26",
"customDetails": null,
"description": "'An attacker with access to Azure DevOps could create a pipeline to inject artifacts used by other pipelines, or to create a malicious software build that looks legitimate by using a pipeline that incorporates legitimate elements. \nAn attacker would also likely want to cover their tracks once conducting such activity. This query looks for Pipelines created and deleted within the same day, this is unlikely to be legitimate user activity in the majority of cases.'\n",
"displayName": "Azure DevOps Pipeline Created and Deleted on the Same Day",
"enabled": true,
"entityMappings": [
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "CreatingUser",
"identifier": "FullName"
},
{
"columnName": "CreatingUserAccountName",
"identifier": "Name"
},
{
"columnName": "CreatingUserAccountUPNSuffix",
"identifier": "UPNSuffix"
}
]
},
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "DeletingUser",
"identifier": "FullName"
},
{
"columnName": "DeletingUserAccountName",
"identifier": "Name"
},
{
"columnName": "DeletingUserAccountUPNSuffix",
"identifier": "UPNSuffix"
}
]
},
{
"entityType": "IP",
"fieldMappings": [
{
"columnName": "CreatingIP",
"identifier": "Address"
}
]
},
{
"entityType": "IP",
"fieldMappings": [
{
"columnName": "DeletingIP",
"identifier": "Address"
}
]
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOPipelineCreatedDeletedOneDay.yaml",
"query": "let timeframe = 3d;\n// Get Release Pipeline Creation Events and group by day\nADOAuditLogs\n| where TimeGenerated > ago(timeframe)\n| where OperationName =~ \"Release.ReleasePipelineCreated\"\n// Group by day\n| extend timekey = bin(TimeGenerated, 1d)\n| extend PipelineId = tostring(Data.PipelineId)\n| extend PipelineName = tostring(Data.PipelineName)\n// Rename some columns to make output clearer\n| project-rename TimeCreated = TimeGenerated, CreatingUser = ActorUPN, CreatingUserAgent = UserAgent, CreatingIP = IpAddress\n// Join with Release Pipeline Deletions where Pipeline ID is the same and deletion occurred on same day as creation\n| join (ADOAuditLogs\n| where TimeGenerated > ago(timeframe)\n| where OperationName =~ \"Release.ReleasePipelineDeleted\"\n// Group by day\n| extend timekey = bin(TimeGenerated, 1d)\n| extend PipelineId = tostring(Data.PipelineId)\n| extend PipelineName = tostring(Data.PipelineName)\n// Rename some things to make the output clearer\n| project-rename TimeDeleted = TimeGenerated,DeletingUser = ActorUPN, DeletingUserAgent = UserAgent, DeletingIP = IpAddress) on PipelineId, timekey\n| project TimeCreated, TimeDeleted, PipelineName, PipelineId, CreatingUser, CreatingIP, CreatingUserAgent, DeletingUser, DeletingIP, DeletingUserAgent, ScopeDisplayName, ProjectName, Data, OperationName, OperationName1\n| extend timestamp = TimeCreated\n| extend CreatingUserAccountName = tostring(split(CreatingUser, \"@\")[0]), CreatingUserAccountUPNSuffix = tostring(split(CreatingUser, \"@\")[1])\n| extend DeletingUserAccountName = tostring(split(DeletingUser, \"@\")[0]), DeletingUserAccountUPNSuffix = tostring(split(DeletingUser, \"@\")[1])\n",
"queryFrequency": "P3D",
"queryPeriod": "P3D",
"severity": "Medium",
"status": "Available",
"subTechniques": [],
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"Execution"
],
"techniques": [
"T1072"
],
"templateVersion": "1.0.4",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0
},
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
}
]
}