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

Azure DevOps Service Connection Abuse

Back
Idd564ff12-8f53-41b8-8649-44f76b37b99f
RulenameAzure DevOps Service Connection Abuse
DescriptionFlags 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.
SeverityMedium
TacticsPersistence
Impact
TechniquesT1098
T1496
KindScheduled
Query frequency1d
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOServiceConnectionUsage.yaml
Version1.0.5
Arm templated564ff12-8f53-41b8-8649-44f76b37b99f.json
Deploy To Azure
// 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"
];
AzureDevOpsAuditing
| 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])
id: d564ff12-8f53-41b8-8649-44f76b37b99f
queryFrequency: 1d
version: 1.0.5
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOServiceConnectionUsage.yaml
requiredDataConnectors: []
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"
  ];
  AzureDevOpsAuditing
  | 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])  
name: Azure DevOps Service Connection Abuse
status: Available
kind: Scheduled
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
triggerOperator: gt
entityMappings:
- entityType: Account
  fieldMappings:
  - columnName: ActorUPN
    identifier: FullName
  - columnName: AccountName
    identifier: Name
  - columnName: AccountUPNSuffix
    identifier: UPNSuffix
- entityType: IP
  fieldMappings:
  - columnName: IpAddress
    identifier: Address
triggerThreshold: 0
queryPeriod: 14d
tactics:
- Persistence
- Impact
relevantTechniques:
- T1098
- T1496
{
  "$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/d564ff12-8f53-41b8-8649-44f76b37b99f')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/d564ff12-8f53-41b8-8649-44f76b37b99f')]",
      "properties": {
        "alertRuleTemplateName": "d564ff12-8f53-41b8-8649-44f76b37b99f",
        "customDetails": null,
        "description": "'Flags builds/releases that use a large number of service connections if they aren't manually in the allow list.\nThis 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.'\n",
        "displayName": "Azure DevOps Service Connection Abuse",
        "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/AzDOServiceConnectionUsage.yaml",
        "query": "// How many greater than Service Connections you want to view per build/release\nlet ServiceConnectionThreshold = 4;\nlet BypassDefIds = datatable(DefId:string, Type:string, ProjectName:string)\n[\n//\"103\", \"Release\", \"ProjectA\",\n//\"42\", \"Release\", \"ProjectB\",\n//\"122\", \"Build\", \"ProjectB\"\n];\nAzureDevOpsAuditing\n| where OperationName == \"Library.ServiceConnectionExecuted\"\n| extend DefId = tostring(Data.DefinitionId), Type = tostring(Data.PlanType), ConnectionId = tostring(Data.ConnectionId)\n| parse ScopeDisplayName with OrganizationName ' (Organization)'\n| summarize CurrentCount = dcount(tostring(ConnectionId)), ConnectionNames = make_set(tostring(Data.ConnectionName)), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)\n  by OrganizationName, tostring(DefId), tostring(Type), ProjectId, ProjectName, ActorUPN, IpAddress\n| where CurrentCount > ServiceConnectionThreshold\n| join kind=anti BypassDefIds on $left.DefId==$right.DefId and $left.Type == $right.Type and $left.ProjectName == $right.ProjectName\n| extend link = iif(\n  Type == \"Build\", strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_build?definitionId=', DefId),\n  strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_release?_a=releases&view=mine&definitionId=', DefId))\n| extend timestamp = StartTime\n| extend AccountName = tostring(split(ActorUPN, \"@\")[0]), AccountUPNSuffix = tostring(split(ActorUPN, \"@\")[1])\n",
        "queryFrequency": "P1D",
        "queryPeriod": "P14D",
        "severity": "Medium",
        "status": "Available",
        "subTechniques": [],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "Impact",
          "Persistence"
        ],
        "techniques": [
          "T1098",
          "T1496"
        ],
        "templateVersion": "1.0.5",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}