Analytic rule catalog
Delinea - Privilege escalation detected role permission changes
Back
| Id | c24252d1-d98c-48db-b2f9-4cb37dd2208a |
| Rulename | Delinea - Privilege escalation detected (role / permission changes) |
| Description | Detects role assignments, permission or right grants, admin grants and elevation events that the acting user has not performed against the same target in the prior 14 days. Only grant-type actions (added / assigned / granted / elevated / approved / created / updated) against role, permission, privilege, admin or entitlement objects are considered; removals and read-only events are excluded, and plain group-membership or ownership events are not matched because they make up most routine directory activity. One alert is produced per actor per run - review the FieldChanges column for the before/after of each change. Tune ’escalationObjects’ and ‘grantVerbs’ to your tenant’s event taxonomy, and use ‘excludedEventNames’, ’excludedServiceTypes’ and ’excludedActors’ to silence known-benign events, services (for example directory synchronization) and service accounts. |
| Severity | High |
| Tactics | PrivilegeEscalation Persistence |
| Techniques | T1098 T1078 |
| Required data connectors | DelineaPlatformConnector |
| Kind | Scheduled |
| Query frequency | 1h |
| Query period | 14d |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Delinea%20Platform/Analytic%20Rules/DelineaPrivilegeEscalation.yaml |
| Version | 1.2.0 |
| Arm template | c24252d1-d98c-48db-b2f9-4cb37dd2208a.json |
let detectionWindow = 1h; // must equal queryFrequency
let baseline = 14d; // must stay <= queryPeriod
// TUNING 1 of 2: the event name must name a privileged object AND end in a grant-type verb.
// Bare 'member', 'assign', 'owner' and 'group' are deliberately absent - they match
// routine directory user and group activity.
let escalationObjects = @"(?i)role|permission|privilege|admin|rights|entitlement|elevat|justintime";
let grantVerbs = @"(?i)(Added|Assigned|Granted|Elevated|Approved|Created|Updated|Modified|Changed)$";
// TUNING 2 of 2: tenant specific exclusions, filled in at deployment time. Exact match,
// case insensitive. Leave empty if unused.
let excludedEventNames = dynamic([]);
// e.g. dynamic(["Delinea.Vault.Group.Updated"]);
let excludedServiceTypes = dynamic([]);
// e.g. dynamic(["Identity"]);
let excludedActors = dynamic([]);
// e.g. dynamic(["svc-directory-sync"]);
let grants =
DelineaAuditEvents_CL
| where TimeGenerated > ago(baseline)
| extend IngestedAt = ingestion_time()
| extend d = parse_json(RawData)
| extend EventName = tostring(coalesce(d.eventType.name, d.eventType.internalName))
| where EventName matches regex escalationObjects and EventName matches regex grantVerbs
| extend Actor = tostring(coalesce(d.actor.name, d.actor.email, d.actor.displayName)),
ServiceType = tostring(d.service.type),
Target = tostring(coalesce(d.target.displayName, d.target.name)),
// ID first - display names can change. Empty when the event carries no target.
TargetKey = tostring(coalesce(d.target.id, d.target.platformId, d.target.name, d.target.displayName)),
FieldChanges = tostring(d.fieldChanges),
RawSrcIP = tostring(d.source.host.network.ipAddress)
| where isnotempty(Actor)
| where not(EventName in~ (excludedEventNames))
and not(ServiceType in~ (excludedServiceTypes))
and not(Actor in~ (excludedActors))
// '0.0.0.0' is the placeholder reported for events that have no network origin.
| extend SrcIP = iff(RawSrcIP in ("", "0.0.0.0"), "", RawSrcIP);
// Batch by ingestion time so a late arriving event is still evaluated exactly once.
// The baseline is keyed on the target too, so repeating a grant on the same target is
// suppressed but the same grant against a new target still alerts.
let priorGrants = grants | where IngestedAt <= ago(detectionWindow) | distinct Actor, EventName, TargetKey;
grants
| where IngestedAt > ago(detectionWindow)
| join kind=leftanti priorGrants on Actor, EventName, TargetKey
| summarize Changes = count(), EventNames = make_set(EventName, 20),
Targets = make_set_if(Target, isnotempty(Target), 20),
FieldChanges = make_list_if(FieldChanges, isnotempty(FieldChanges), 10),
SourceIPs = make_set_if(SrcIP, isnotempty(SrcIP), 20),
SrcIP = take_anyif(SrcIP, isnotempty(SrcIP)),
StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Actor
| project StartTime, EndTime, Actor, Changes, EventNames, Targets, FieldChanges, SourceIPs, SrcIP
| sort by StartTime desc
customDetails:
EventNames: EventNames
Targets: Targets
Changes: Changes
entityMappings:
- entityType: Account
fieldMappings:
- columnName: Actor
identifier: Name
- entityType: IP
fieldMappings:
- columnName: SrcIP
identifier: Address
tactics:
- PrivilegeEscalation
- Persistence
status: Available
queryPeriod: 14d
queryFrequency: 1h
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
matchingMethod: Selected
reopenClosedIncident: false
lookbackDuration: P1D
groupByEntities:
- Account
requiredDataConnectors:
- connectorId: DelineaPlatformConnector
dataTypes:
- DelineaAuditEvents_CL
severity: High
relevantTechniques:
- T1098
- T1078
name: Delinea - Privilege escalation detected (role / permission changes)
kind: Scheduled
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Delinea%20Platform/Analytic%20Rules/DelineaPrivilegeEscalation.yaml
eventGroupingSettings:
aggregationKind: AlertPerResult
description: |
Detects role assignments, permission or right grants, admin grants and elevation events that
the acting user has not performed against the same target in the prior 14 days. Only grant-type actions (added /
assigned / granted / elevated / approved / created / updated) against role, permission,
privilege, admin or entitlement objects are considered; removals and read-only events are
excluded, and plain group-membership or ownership events are not matched because they make up
most routine directory activity. One alert is produced per actor per run - review the
FieldChanges column for the before/after of each change.
Tune 'escalationObjects' and 'grantVerbs' to your tenant's event taxonomy, and use
'excludedEventNames', 'excludedServiceTypes' and 'excludedActors' to silence known-benign
events, services (for example directory synchronization) and service accounts.
triggerOperator: gt
id: c24252d1-d98c-48db-b2f9-4cb37dd2208a
version: 1.2.0
triggerThreshold: 0
query: |
let detectionWindow = 1h; // must equal queryFrequency
let baseline = 14d; // must stay <= queryPeriod
// TUNING 1 of 2: the event name must name a privileged object AND end in a grant-type verb.
// Bare 'member', 'assign', 'owner' and 'group' are deliberately absent - they match
// routine directory user and group activity.
let escalationObjects = @"(?i)role|permission|privilege|admin|rights|entitlement|elevat|justintime";
let grantVerbs = @"(?i)(Added|Assigned|Granted|Elevated|Approved|Created|Updated|Modified|Changed)$";
// TUNING 2 of 2: tenant specific exclusions, filled in at deployment time. Exact match,
// case insensitive. Leave empty if unused.
let excludedEventNames = dynamic([]);
// e.g. dynamic(["Delinea.Vault.Group.Updated"]);
let excludedServiceTypes = dynamic([]);
// e.g. dynamic(["Identity"]);
let excludedActors = dynamic([]);
// e.g. dynamic(["svc-directory-sync"]);
let grants =
DelineaAuditEvents_CL
| where TimeGenerated > ago(baseline)
| extend IngestedAt = ingestion_time()
| extend d = parse_json(RawData)
| extend EventName = tostring(coalesce(d.eventType.name, d.eventType.internalName))
| where EventName matches regex escalationObjects and EventName matches regex grantVerbs
| extend Actor = tostring(coalesce(d.actor.name, d.actor.email, d.actor.displayName)),
ServiceType = tostring(d.service.type),
Target = tostring(coalesce(d.target.displayName, d.target.name)),
// ID first - display names can change. Empty when the event carries no target.
TargetKey = tostring(coalesce(d.target.id, d.target.platformId, d.target.name, d.target.displayName)),
FieldChanges = tostring(d.fieldChanges),
RawSrcIP = tostring(d.source.host.network.ipAddress)
| where isnotempty(Actor)
| where not(EventName in~ (excludedEventNames))
and not(ServiceType in~ (excludedServiceTypes))
and not(Actor in~ (excludedActors))
// '0.0.0.0' is the placeholder reported for events that have no network origin.
| extend SrcIP = iff(RawSrcIP in ("", "0.0.0.0"), "", RawSrcIP);
// Batch by ingestion time so a late arriving event is still evaluated exactly once.
// The baseline is keyed on the target too, so repeating a grant on the same target is
// suppressed but the same grant against a new target still alerts.
let priorGrants = grants | where IngestedAt <= ago(detectionWindow) | distinct Actor, EventName, TargetKey;
grants
| where IngestedAt > ago(detectionWindow)
| join kind=leftanti priorGrants on Actor, EventName, TargetKey
| summarize Changes = count(), EventNames = make_set(EventName, 20),
Targets = make_set_if(Target, isnotempty(Target), 20),
FieldChanges = make_list_if(FieldChanges, isnotempty(FieldChanges), 10),
SourceIPs = make_set_if(SrcIP, isnotempty(SrcIP), 20),
SrcIP = take_anyif(SrcIP, isnotempty(SrcIP)),
StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Actor
| project StartTime, EndTime, Actor, Changes, EventNames, Targets, FieldChanges, SourceIPs, SrcIP
| sort by StartTime desc
{
"$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/c24252d1-d98c-48db-b2f9-4cb37dd2208a')]",
"kind": "Scheduled",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/c24252d1-d98c-48db-b2f9-4cb37dd2208a')]",
"properties": {
"alertRuleTemplateName": "c24252d1-d98c-48db-b2f9-4cb37dd2208a",
"customDetails": {
"Changes": "Changes",
"EventNames": "EventNames",
"Targets": "Targets"
},
"description": "Detects role assignments, permission or right grants, admin grants and elevation events that\nthe acting user has not performed against the same target in the prior 14 days. Only grant-type actions (added /\nassigned / granted / elevated / approved / created / updated) against role, permission,\nprivilege, admin or entitlement objects are considered; removals and read-only events are\nexcluded, and plain group-membership or ownership events are not matched because they make up\nmost routine directory activity. One alert is produced per actor per run - review the\nFieldChanges column for the before/after of each change.\nTune 'escalationObjects' and 'grantVerbs' to your tenant's event taxonomy, and use\n'excludedEventNames', 'excludedServiceTypes' and 'excludedActors' to silence known-benign\nevents, services (for example directory synchronization) and service accounts.\n",
"displayName": "Delinea - Privilege escalation detected (role / permission changes)",
"enabled": true,
"entityMappings": [
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "Actor",
"identifier": "Name"
}
]
},
{
"entityType": "IP",
"fieldMappings": [
{
"columnName": "SrcIP",
"identifier": "Address"
}
]
}
],
"eventGroupingSettings": {
"aggregationKind": "AlertPerResult"
},
"incidentConfiguration": {
"createIncident": true,
"groupingConfiguration": {
"enabled": true,
"groupByEntities": [
"Account"
],
"lookbackDuration": "P1D",
"matchingMethod": "Selected",
"reopenClosedIncident": false
}
},
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Delinea%20Platform/Analytic%20Rules/DelineaPrivilegeEscalation.yaml",
"query": "let detectionWindow = 1h; // must equal queryFrequency\nlet baseline = 14d; // must stay <= queryPeriod\n// TUNING 1 of 2: the event name must name a privileged object AND end in a grant-type verb.\n// Bare 'member', 'assign', 'owner' and 'group' are deliberately absent - they match\n// routine directory user and group activity.\nlet escalationObjects = @\"(?i)role|permission|privilege|admin|rights|entitlement|elevat|justintime\";\nlet grantVerbs = @\"(?i)(Added|Assigned|Granted|Elevated|Approved|Created|Updated|Modified|Changed)$\";\n// TUNING 2 of 2: tenant specific exclusions, filled in at deployment time. Exact match,\n// case insensitive. Leave empty if unused.\nlet excludedEventNames = dynamic([]);\n// e.g. dynamic([\"Delinea.Vault.Group.Updated\"]);\nlet excludedServiceTypes = dynamic([]);\n// e.g. dynamic([\"Identity\"]);\nlet excludedActors = dynamic([]);\n// e.g. dynamic([\"svc-directory-sync\"]);\nlet grants =\n DelineaAuditEvents_CL\n | where TimeGenerated > ago(baseline)\n | extend IngestedAt = ingestion_time()\n | extend d = parse_json(RawData)\n | extend EventName = tostring(coalesce(d.eventType.name, d.eventType.internalName))\n | where EventName matches regex escalationObjects and EventName matches regex grantVerbs\n | extend Actor = tostring(coalesce(d.actor.name, d.actor.email, d.actor.displayName)),\n ServiceType = tostring(d.service.type),\n Target = tostring(coalesce(d.target.displayName, d.target.name)),\n // ID first - display names can change. Empty when the event carries no target.\n TargetKey = tostring(coalesce(d.target.id, d.target.platformId, d.target.name, d.target.displayName)),\n FieldChanges = tostring(d.fieldChanges),\n RawSrcIP = tostring(d.source.host.network.ipAddress)\n | where isnotempty(Actor)\n | where not(EventName in~ (excludedEventNames))\n and not(ServiceType in~ (excludedServiceTypes))\n and not(Actor in~ (excludedActors))\n // '0.0.0.0' is the placeholder reported for events that have no network origin.\n | extend SrcIP = iff(RawSrcIP in (\"\", \"0.0.0.0\"), \"\", RawSrcIP);\n// Batch by ingestion time so a late arriving event is still evaluated exactly once.\n// The baseline is keyed on the target too, so repeating a grant on the same target is\n// suppressed but the same grant against a new target still alerts.\nlet priorGrants = grants | where IngestedAt <= ago(detectionWindow) | distinct Actor, EventName, TargetKey;\ngrants\n| where IngestedAt > ago(detectionWindow)\n| join kind=leftanti priorGrants on Actor, EventName, TargetKey\n| summarize Changes = count(), EventNames = make_set(EventName, 20),\n Targets = make_set_if(Target, isnotempty(Target), 20),\n FieldChanges = make_list_if(FieldChanges, isnotempty(FieldChanges), 10),\n SourceIPs = make_set_if(SrcIP, isnotempty(SrcIP), 20),\n SrcIP = take_anyif(SrcIP, isnotempty(SrcIP)),\n StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Actor\n| project StartTime, EndTime, Actor, Changes, EventNames, Targets, FieldChanges, SourceIPs, SrcIP\n| sort by StartTime desc\n",
"queryFrequency": "PT1H",
"queryPeriod": "P14D",
"severity": "High",
"status": "Available",
"subTechniques": [],
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"Persistence",
"PrivilegeEscalation"
],
"techniques": [
"T1078",
"T1098"
],
"templateVersion": "1.2.0",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0
},
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
}
]
}