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 AdditionAbuse - Historic allow list

Back
Id5efb0cfd-063d-417a-803b-562eae5b0301
RulenameAzure DevOps Service Connection Addition/Abuse - Historic allow list
DescriptionThis detection builds an allow list of historic service connection use by Builds and Releases and compares to recent history, flagging growth of service connection use which are not manually included in the allow list and

not historically included in the allow list Build/Release runs. 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 frequency6h
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOHistoricServiceConnectionAdds.yaml
Version1.0.4
Arm template5efb0cfd-063d-417a-803b-562eae5b0301.json
Deploy To Azure
let starttime = 14d;
let endtime = 6h;
// Ignore Build/Releases with less/equal this number
let ServiceConnectionThreshold = 3;
// New Connections need to exhibit execution of more "new" connections than this number.
let NewConnectionThreshold = 1;
// List of Builds/Releases to ignore in your space
let BypassDefIds = datatable(DefId:string, Type:string, ProjectName:string)
[
//"103", "Release", "ProjectA",
//"42", "Release", "ProjectB",
//"122", "Build", "ProjectB"
];
let HistoricDefs = AzureDevOpsAuditing
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where OperationName == "Library.ServiceConnectionExecuted"
| extend DefId = tostring(Data.DefinitionId), Type = tostring(Data.PlanType), ConnectionId = tostring(Data.ConnectionId)
| summarize HistoricCount = dcount(tostring(ConnectionId)), ConnectionNames = make_set(tostring(Data.ConnectionName))
  by DefId = tostring(DefId), Type = tostring(Type), ProjectId, ProjectName, ActorUPN;
AzureDevOpsAuditing
| where TimeGenerated >= ago(endtime)
| 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, DefId = tostring(DefId), Type = tostring(Type), ProjectId, ProjectName, ActorUPN
| where CurrentCount > ServiceConnectionThreshold
| join (HistoricDefs) on ProjectId, DefId, Type, ActorUPN
| join kind=anti BypassDefIds on $left.DefId==$right.DefId and $left.Type == $right.Type and $left.ProjectName == $right.ProjectName
| extend link = iff(
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))
| where CurrentCount >= HistoricCount + NewConnectionThreshold
| project StartTime, OrganizationName, ProjectName, DefId, link, RecentDistinctServiceConnections = CurrentCount, HistoricDistinctServiceConnections = HistoricCount,
  RecentConnections = ConnectionNames, HistoricConnections = ConnectionNames1, ActorUPN
| extend timestamp = StartTime
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
name: Azure DevOps Service Connection Addition/Abuse - Historic allow list
query: |
  let starttime = 14d;
  let endtime = 6h;
  // Ignore Build/Releases with less/equal this number
  let ServiceConnectionThreshold = 3;
  // New Connections need to exhibit execution of more "new" connections than this number.
  let NewConnectionThreshold = 1;
  // List of Builds/Releases to ignore in your space
  let BypassDefIds = datatable(DefId:string, Type:string, ProjectName:string)
  [
  //"103", "Release", "ProjectA",
  //"42", "Release", "ProjectB",
  //"122", "Build", "ProjectB"
  ];
  let HistoricDefs = AzureDevOpsAuditing
  | where TimeGenerated between (ago(starttime) .. ago(endtime))
  | where OperationName == "Library.ServiceConnectionExecuted"
  | extend DefId = tostring(Data.DefinitionId), Type = tostring(Data.PlanType), ConnectionId = tostring(Data.ConnectionId)
  | summarize HistoricCount = dcount(tostring(ConnectionId)), ConnectionNames = make_set(tostring(Data.ConnectionName))
    by DefId = tostring(DefId), Type = tostring(Type), ProjectId, ProjectName, ActorUPN;
  AzureDevOpsAuditing
  | where TimeGenerated >= ago(endtime)
  | 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, DefId = tostring(DefId), Type = tostring(Type), ProjectId, ProjectName, ActorUPN
  | where CurrentCount > ServiceConnectionThreshold
  | join (HistoricDefs) on ProjectId, DefId, Type, ActorUPN
  | join kind=anti BypassDefIds on $left.DefId==$right.DefId and $left.Type == $right.Type and $left.ProjectName == $right.ProjectName
  | extend link = iff(
  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))
  | where CurrentCount >= HistoricCount + NewConnectionThreshold
  | project StartTime, OrganizationName, ProjectName, DefId, link, RecentDistinctServiceConnections = CurrentCount, HistoricDistinctServiceConnections = HistoricCount,
    RecentConnections = ConnectionNames, HistoricConnections = ConnectionNames1, ActorUPN
  | extend timestamp = StartTime
  | extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])  
description: |
  'This detection builds an allow list of historic service connection use by Builds and Releases and compares to recent history, flagging growth of service connection use which are not manually included in the allow list and
  not historically included in the allow list Build/Release runs.  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.'  
tactics:
- Persistence
- Impact
requiredDataConnectors: []
queryPeriod: 14d
queryFrequency: 6h
status: Available
triggerThreshold: 0
id: 5efb0cfd-063d-417a-803b-562eae5b0301
triggerOperator: gt
version: 1.0.4
relevantTechniques:
- T1098
- T1496
severity: Medium
kind: Scheduled
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: ActorUPN
  - identifier: Name
    columnName: AccountName
  - identifier: UPNSuffix
    columnName: AccountUPNSuffix
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOHistoricServiceConnectionAdds.yaml
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2023-02-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/5efb0cfd-063d-417a-803b-562eae5b0301')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/5efb0cfd-063d-417a-803b-562eae5b0301')]",
      "properties": {
        "alertRuleTemplateName": "5efb0cfd-063d-417a-803b-562eae5b0301",
        "customDetails": null,
        "description": "'This detection builds an allow list of historic service connection use by Builds and Releases and compares to recent history, flagging growth of service connection use which are not manually included in the allow list and\nnot historically included in the allow list Build/Release runs.  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.'\n",
        "displayName": "Azure DevOps Service Connection Addition/Abuse - Historic allow list",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "ActorUPN",
                "identifier": "FullName"
              },
              {
                "columnName": "AccountName",
                "identifier": "Name"
              },
              {
                "columnName": "AccountUPNSuffix",
                "identifier": "UPNSuffix"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOHistoricServiceConnectionAdds.yaml",
        "query": "let starttime = 14d;\nlet endtime = 6h;\n// Ignore Build/Releases with less/equal this number\nlet ServiceConnectionThreshold = 3;\n// New Connections need to exhibit execution of more \"new\" connections than this number.\nlet NewConnectionThreshold = 1;\n// List of Builds/Releases to ignore in your space\nlet BypassDefIds = datatable(DefId:string, Type:string, ProjectName:string)\n[\n//\"103\", \"Release\", \"ProjectA\",\n//\"42\", \"Release\", \"ProjectB\",\n//\"122\", \"Build\", \"ProjectB\"\n];\nlet HistoricDefs = AzureDevOpsAuditing\n| where TimeGenerated between (ago(starttime) .. ago(endtime))\n| where OperationName == \"Library.ServiceConnectionExecuted\"\n| extend DefId = tostring(Data.DefinitionId), Type = tostring(Data.PlanType), ConnectionId = tostring(Data.ConnectionId)\n| summarize HistoricCount = dcount(tostring(ConnectionId)), ConnectionNames = make_set(tostring(Data.ConnectionName))\n  by DefId = tostring(DefId), Type = tostring(Type), ProjectId, ProjectName, ActorUPN;\nAzureDevOpsAuditing\n| where TimeGenerated >= ago(endtime)\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, DefId = tostring(DefId), Type = tostring(Type), ProjectId, ProjectName, ActorUPN\n| where CurrentCount > ServiceConnectionThreshold\n| join (HistoricDefs) on ProjectId, DefId, Type, ActorUPN\n| join kind=anti BypassDefIds on $left.DefId==$right.DefId and $left.Type == $right.Type and $left.ProjectName == $right.ProjectName\n| extend link = iff(\nType == \"Build\", strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_build?definitionId=', DefId),\nstrcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_release?_a=releases&view=mine&definitionId=', DefId))\n| where CurrentCount >= HistoricCount + NewConnectionThreshold\n| project StartTime, OrganizationName, ProjectName, DefId, link, RecentDistinctServiceConnections = CurrentCount, HistoricDistinctServiceConnections = HistoricCount,\n  RecentConnections = ConnectionNames, HistoricConnections = ConnectionNames1, ActorUPN\n| extend timestamp = StartTime\n| extend AccountName = tostring(split(ActorUPN, \"@\")[0]), AccountUPNSuffix = tostring(split(ActorUPN, \"@\")[1])\n",
        "queryFrequency": "PT6H",
        "queryPeriod": "P14D",
        "severity": "Medium",
        "status": "Available",
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "Impact",
          "Persistence"
        ],
        "techniques": [
          "T1098",
          "T1496"
        ],
        "templateVersion": "1.0.4",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}