Detection rules › Kusto
Azure DevOps Administrator Group Monitoring
'This detection monitors for additions to projects or project collection administration groups in an Azure DevOps Organization.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule body
id: 89e6adbd-612c-4fbe-bc3d-32f81baf3b6c
name: Azure DevOps Administrator Group Monitoring
description: |
'This detection monitors for additions to projects or project collection administration groups in an Azure DevOps Organization.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 4h
queryPeriod: 4h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
query: |
// Change to true to monitor for Project Administrator adds to *any* project
let MonitorAllProjects = false;
// If MonitorAllProjects is false, trigger only on Project Administrator add for the following projects
let ProjectsToMonitor = dynamic(['<project_X>','<project_Y>']);
ADOAuditLogs
| where Area == "Group" and OperationName == "Group.UpdateGroupMembership.Add"
| where Details has 'Administrators'
| where Details has "was added as a member of group" and (Details endswith '\\Project Administrators' or Details endswith '\\Project Collection Administrators')
| parse Details with AddedIdentity ' was added as a member of group [' EntityName ']\\' GroupName
| extend Level = iif(GroupName == 'Project Collection Administrators', 'Organization', 'Project'), AddedIdentityId = Data.MemberId
| extend Severity = iif(Level == 'Organization', 'High', 'Medium'), AlertDetails = strcat('At ', TimeGenerated, ' UTC ', ActorUPN, '/', ActorDisplayName, ' added ', AddedIdentity, ' to the ', EntityName, ' ', Level)
| where MonitorAllProjects == true or EntityName in (ProjectsToMonitor) or Level == 'Organization'
| project TimeGenerated, Severity, Adder = ActorUPN, AddedIdentity, AddedIdentityId, AlertDetails, Level, EntityName, GroupName, ActorAuthType = AuthenticationMechanism,
ActorIpAddress = IpAddress, ActorUserAgent = UserAgent, RawDetails = Details
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(Adder, "@")[0]), AccountUPNSuffix = tostring(split(Adder, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Adder
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ActorIpAddress
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let MonitorAllProjects = false;
let ProjectsToMonitor = dynamic(['<project_X>','<project_Y>']);
Stage 1: source
ADOAuditLogs
Stage 2: where
| where Area == "Group" and OperationName == "Group.UpdateGroupMembership.Add"
Stage 3: where
| where Details has 'Administrators'
Stage 4: where
| where Details has "was added as a member of group" and (Details endswith '\\Project Administrators' or Details endswith '\\Project Collection Administrators')
Stage 5: parse
| parse Details with AddedIdentity ' was added as a member of group [' EntityName ']\\' GroupName
Stage 6: extend
| extend Level = iif(GroupName == 'Project Collection Administrators', 'Organization', 'Project'), AddedIdentityId = Data.MemberId
Stage 7: extend
| extend Severity = iif(Level == 'Organization', 'High', 'Medium'), AlertDetails = strcat('At ', TimeGenerated, ' UTC ', ActorUPN, '/', ActorDisplayName, ' added ', AddedIdentity, ' to the ', EntityName, ' ', Level)
Stage 8: where
| where MonitorAllProjects == true or EntityName in (ProjectsToMonitor) or Level == 'Organization'
Stage 9: project
| project TimeGenerated, Severity, Adder = ActorUPN, AddedIdentity, AddedIdentityId, AlertDetails, Level, EntityName, GroupName, ActorAuthType = AuthenticationMechanism,
ActorIpAddress = IpAddress, ActorUserAgent = UserAgent, RawDetails = Details
Stage 10: extend
| extend timestamp = TimeGenerated
Stage 11: extend
| extend AccountName = tostring(split(Adder, "@")[0]), AccountUPNSuffix = tostring(split(Adder, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Area | eq |
| field:"Area" kind:eq value:"Group" |
Details | ends_with |
| field:"Details" kind:ends_with |
Details | match |
| field:"Details" kind:match |
EntityName | in |
| field:"EntityName" kind:in |
Level | eq |
| field:"Level" kind:eq value:"Organization" |
OperationName | eq |
| field:"OperationName" kind:eq value:"Group.UpdateGroupMembership.Add" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorAuthType | project |
ActorIpAddress | project |
ActorUserAgent | project |
AddedIdentity | project |
AddedIdentityId | project |
Adder | project |
AlertDetails | project |
EntityName | project |
GroupName | project |
Level | project |
RawDetails | project |
Severity | project |
TimeGenerated | project |
timestamp | extend |
AccountName | extend |
AccountUPNSuffix | extend |