let starttime = 14d;
let endtime = 3h;
// Add full UPN (user@domain.com) to Authorized Bypassers to ignore policy bypasses by certain authorized users
let AuthorizedBypassers = dynamic(['foo@baz.com', 'test@foo.com']);
let historicBypassers = ADOAuditLogs
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where OperationName == 'Git.RefUpdatePoliciesBypassed'
| distinct ActorUPN;
ADOAuditLogs
| where TimeGenerated >= ago(endtime)
| where OperationName == 'Git.RefUpdatePoliciesBypassed'
| where ActorUPN !in (historicBypassers) and ActorUPN !in (AuthorizedBypassers)
| parse ScopeDisplayName with OrganizationName '(Organization)'
| project TimeGenerated, ActorUPN, IpAddress, UserAgent, OrganizationName, ProjectName, RepoName = Data.RepoName, AlertDetails = Details, Branch = Data.Name,
BypassReason = Data.BypassReason, PRLink = strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_git/', Data.RepoName, '/pullrequest/', Data.PullRequestId)
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
queryFrequency: 3h
requiredDataConnectors: []
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/AzureDevOpsAuditing/Analytic Rules/AzDOHistoricPrPolicyBypassing.yaml
name: Azure DevOps Pull Request Policy Bypassing - Historic allow list
description: |
'This detection builds an allow list of historic PR policy bypasses and compares to recent history, flagging pull request bypasses that are not manually in the allow list and not historically included in the allow list.'
severity: Medium
status: Available
kind: Scheduled
triggerThreshold: 0
version: 1.0.6
relevantTechniques:
- T1098
id: 4d8de9e6-263e-4845-8618-cd23a4f58b70
triggerOperator: gt
queryPeriod: 14d
tactics:
- Persistence
query: |
let starttime = 14d;
let endtime = 3h;
// Add full UPN (user@domain.com) to Authorized Bypassers to ignore policy bypasses by certain authorized users
let AuthorizedBypassers = dynamic(['foo@baz.com', 'test@foo.com']);
let historicBypassers = ADOAuditLogs
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where OperationName == 'Git.RefUpdatePoliciesBypassed'
| distinct ActorUPN;
ADOAuditLogs
| where TimeGenerated >= ago(endtime)
| where OperationName == 'Git.RefUpdatePoliciesBypassed'
| where ActorUPN !in (historicBypassers) and ActorUPN !in (AuthorizedBypassers)
| parse ScopeDisplayName with OrganizationName '(Organization)'
| project TimeGenerated, ActorUPN, IpAddress, UserAgent, OrganizationName, ProjectName, RepoName = Data.RepoName, AlertDetails = Details, Branch = Data.Name,
BypassReason = Data.BypassReason, PRLink = strcat('https://dev.azure.com/', OrganizationName, '/', ProjectName, '/_git/', Data.RepoName, '/pullrequest/', Data.PullRequestId)
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
entityMappings:
- fieldMappings:
- identifier: FullName
columnName: ActorUPN
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
entityType: Account
- fieldMappings:
- identifier: Address
columnName: IpAddress
entityType: IP
- fieldMappings:
- identifier: Url
columnName: PRLink
entityType: URL