Azure DevOps Service Connection Abuse
| Id | d564ff12-8f53-41b8-8649-44f76b37b99f |
| Rulename | Azure DevOps Service Connection Abuse |
| Description | Flags builds/releases that use a large number of service connections if they aren’t manually in the allow list. This is to determine if someone is hijacking a build/release and adding many service connections in order to abuse or dump credentials from service connections. |
| Severity | Medium |
| Tactics | Persistence Impact |
| Techniques | T1098 T1496 |
| Kind | Scheduled |
| Query frequency | 1d |
| Query period | 14d |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOServiceConnectionUsage.yaml |
| Version | 1.0.6 |
| Arm template | d564ff12-8f53-41b8-8649-44f76b37b99f.json |
// How many greater than Service Connections you want to view per build/release
let ServiceConnectionThreshold = 4;
let BypassDefIds = datatable(DefId:string, Type:string, ProjectName:string)
[
//"103", "Release", "ProjectA",
//"42", "Release", "ProjectB",
//"122", "Build", "ProjectB"
];
ADOAuditLogs
| where OperationName == "Library.ServiceConnectionExecuted"
| extend DefId = tostring(Data.DefinitionId), Type = tostring(Data.PlanType), ConnectionId = tostring(Data.ConnectionId)
| parse ScopeDisplayName with OrganizationName ' (Organization)'
| summarize CurrentCount = dcount(tostring(ConnectionId)), ConnectionNames = make_set(tostring(Data.ConnectionName)), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
by OrganizationName, tostring(DefId), tostring(Type), ProjectId, ProjectName, ActorUPN, IpAddress
| where CurrentCount > ServiceConnectionThreshold
| join kind=anti BypassDefIds on $left.DefId==$right.DefId and $left.Type == $right.Type and $left.ProjectName == $right.ProjectName
| extend link = iif(
Type == "Build", strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_build?definitionId=', DefId),
strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_release?_a=releases&view=mine&definitionId=', DefId))
| extend timestamp = StartTime
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
description: |
'Flags builds/releases that use a large number of service connections if they aren't manually in the allow list.
This is to determine if someone is hijacking a build/release and adding many service connections in order to abuse or dump credentials from service connections.'
kind: Scheduled
tactics:
- Persistence
- Impact
requiredDataConnectors: []
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOServiceConnectionUsage.yaml
severity: Medium
name: Azure DevOps Service Connection Abuse
triggerThreshold: 0
queryPeriod: 14d
query: |
// How many greater than Service Connections you want to view per build/release
let ServiceConnectionThreshold = 4;
let BypassDefIds = datatable(DefId:string, Type:string, ProjectName:string)
[
//"103", "Release", "ProjectA",
//"42", "Release", "ProjectB",
//"122", "Build", "ProjectB"
];
ADOAuditLogs
| where OperationName == "Library.ServiceConnectionExecuted"
| extend DefId = tostring(Data.DefinitionId), Type = tostring(Data.PlanType), ConnectionId = tostring(Data.ConnectionId)
| parse ScopeDisplayName with OrganizationName ' (Organization)'
| summarize CurrentCount = dcount(tostring(ConnectionId)), ConnectionNames = make_set(tostring(Data.ConnectionName)), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
by OrganizationName, tostring(DefId), tostring(Type), ProjectId, ProjectName, ActorUPN, IpAddress
| where CurrentCount > ServiceConnectionThreshold
| join kind=anti BypassDefIds on $left.DefId==$right.DefId and $left.Type == $right.Type and $left.ProjectName == $right.ProjectName
| extend link = iif(
Type == "Build", strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_build?definitionId=', DefId),
strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_release?_a=releases&view=mine&definitionId=', DefId))
| extend timestamp = StartTime
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
relevantTechniques:
- T1098
- T1496
id: d564ff12-8f53-41b8-8649-44f76b37b99f
queryFrequency: 1d
status: Available
triggerOperator: gt
version: 1.0.6
entityMappings:
- entityType: Account
fieldMappings:
- columnName: ActorUPN
identifier: FullName
- columnName: AccountName
identifier: Name
- columnName: AccountUPNSuffix
identifier: UPNSuffix
- entityType: IP
fieldMappings:
- columnName: IpAddress
identifier: Address