New External User Granted Admin
Id | d7424fd9-abb3-4ded-a723-eebe023aaa0b |
Rulename | New External User Granted Admin |
Description | This query will detect instances where a newly invited external user is granted an administrative role. By default this query will alert on any granted administrative role, however this can be modified using the roles variable if false positives occur in your environment. The maximum delta between invite and escalation to admin is 60 minues, this can be configured using the deltaBetweenInviteEscalation variable. |
Severity | Medium |
Tactics | Persistence |
Techniques | T1098.001 |
Required data connectors | AzureActiveDirectory |
Kind | Scheduled |
Query frequency | 1d |
Query period | 1d |
Trigger threshold | 0 |
Trigger operator | gt |
Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Cloud Identity Threat Protection Essentials/Analytic Rules/NewExtUserGrantedAdmin.yaml |
Version | 1.0.0 |
Arm template | d7424fd9-abb3-4ded-a723-eebe023aaa0b.json |
// Administrative roles to look for, default is all admin roles
let roles = dynamic(["Administrator", "Admin"]);
// The maximum distances between and invite and acceptance
let maxTimeBetweenInviteAccept = 30min;
// The delta (minutes) between the invite being sent and the account being escalated
let deltaBetweenInviteEscalation = 60;
// Collect external user invitations
let invite = AuditLogs
| where Category =~ "UserManagement"
| where OperationName =~ "Invite external user"
| extend Target = tostring(TargetResources[0].["userPrincipalName"])
| extend InviteInitiator = tostring(InitiatedBy.["user"].["userPrincipalName"])
| where isnotempty(InviteInitiator);
// Collect redeem events
let redeem = AuditLogs
| where Category =~ "UserManagement"
| where OperationName =~ "Redeem external user invite"
| where Result =~ "success"
| extend Target = tostring(TargetResources[0].["displayName"]) | extend Target = tostring(extract(@"UPN\:\s(.+)\,\sEmail",1,Target))
| where isnotempty(Target);
// Union the inivtation and redeem data then run the sequence_detect kusto plugin
invite
| union redeem
| order by TimeGenerated
| project TimeGenerated, Target, InviteInitiator, OperationName, TenantId
| evaluate sequence_detect(TimeGenerated, maxTimeBetweenInviteAccept, maxTimeBetweenInviteAccept, invite=(OperationName has "Invite external user"), redeem=(OperationName has "Redeem external user invite"), Target)
| join (
AuditLogs
| where Category =~ "RoleManagement"
| where AADOperationType in ("Assign", "AssignEligibleRole")
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
| mv-expand TargetResources
// Limit to external accounts
| where TargetResources.userPrincipalName has "EXT"
| mv-expand TargetResources.modifiedProperties
| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)
| where displayName_ =~ "Role.DisplayName"
| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))
// Perform check for admin roles
| where RoleName has_any(roles)
| extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))
| where Initiator != "MS-PIM"
| extend Target = tostring(TargetResources.userPrincipalName)
| summarize by TimeGenerated, OperationName, RoleName, Target, Initiator, Result
) on Target
// Calculate delta between the invite and the account escalation
| extend delta = datetime_diff("minute", TimeGenerated, invite_TimeGenerated)
| where delta <= deltaBetweenInviteEscalation
| project InvitationTime=invite_TimeGenerated, RedeemTime=redeem_TimeGenerated, GrantTime=TimeGenerated, ExternalUser=Target, RoleGranted=RoleName, AdminInitiator=Initiator, MinsBetweenInviteAndEscalation=delta
version: 1.0.0
queryFrequency: 1d
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
entityMappings:
- fieldMappings:
- columnName: ExternalUser
identifier: FullName
entityType: Account
- fieldMappings:
- columnName: AdminInitiator
identifier: FullName
entityType: Account
kind: Scheduled
queryPeriod: 1d
severity: Medium
query: |
// Administrative roles to look for, default is all admin roles
let roles = dynamic(["Administrator", "Admin"]);
// The maximum distances between and invite and acceptance
let maxTimeBetweenInviteAccept = 30min;
// The delta (minutes) between the invite being sent and the account being escalated
let deltaBetweenInviteEscalation = 60;
// Collect external user invitations
let invite = AuditLogs
| where Category =~ "UserManagement"
| where OperationName =~ "Invite external user"
| extend Target = tostring(TargetResources[0].["userPrincipalName"])
| extend InviteInitiator = tostring(InitiatedBy.["user"].["userPrincipalName"])
| where isnotempty(InviteInitiator);
// Collect redeem events
let redeem = AuditLogs
| where Category =~ "UserManagement"
| where OperationName =~ "Redeem external user invite"
| where Result =~ "success"
| extend Target = tostring(TargetResources[0].["displayName"]) | extend Target = tostring(extract(@"UPN\:\s(.+)\,\sEmail",1,Target))
| where isnotempty(Target);
// Union the inivtation and redeem data then run the sequence_detect kusto plugin
invite
| union redeem
| order by TimeGenerated
| project TimeGenerated, Target, InviteInitiator, OperationName, TenantId
| evaluate sequence_detect(TimeGenerated, maxTimeBetweenInviteAccept, maxTimeBetweenInviteAccept, invite=(OperationName has "Invite external user"), redeem=(OperationName has "Redeem external user invite"), Target)
| join (
AuditLogs
| where Category =~ "RoleManagement"
| where AADOperationType in ("Assign", "AssignEligibleRole")
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
| mv-expand TargetResources
// Limit to external accounts
| where TargetResources.userPrincipalName has "EXT"
| mv-expand TargetResources.modifiedProperties
| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)
| where displayName_ =~ "Role.DisplayName"
| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))
// Perform check for admin roles
| where RoleName has_any(roles)
| extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))
| where Initiator != "MS-PIM"
| extend Target = tostring(TargetResources.userPrincipalName)
| summarize by TimeGenerated, OperationName, RoleName, Target, Initiator, Result
) on Target
// Calculate delta between the invite and the account escalation
| extend delta = datetime_diff("minute", TimeGenerated, invite_TimeGenerated)
| where delta <= deltaBetweenInviteEscalation
| project InvitationTime=invite_TimeGenerated, RedeemTime=redeem_TimeGenerated, GrantTime=TimeGenerated, ExternalUser=Target, RoleGranted=RoleName, AdminInitiator=Initiator, MinsBetweenInviteAndEscalation=delta
triggerOperator: gt
id: d7424fd9-abb3-4ded-a723-eebe023aaa0b
description: |
'This query will detect instances where a newly invited external user is granted an administrative role. By default this query
will alert on any granted administrative role, however this can be modified using the roles variable if false positives occur
in your environment. The maximum delta between invite and escalation to admin is 60 minues, this can be configured using the
deltaBetweenInviteEscalation variable.'
triggerThreshold: 0
name: New External User Granted Admin
relevantTechniques:
- T1098.001
tactics:
- Persistence
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Cloud Identity Threat Protection Essentials/Analytic Rules/NewExtUserGrantedAdmin.yaml
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workspace": {
"type": "String"
}
},
"resources": [
{
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/d7424fd9-abb3-4ded-a723-eebe023aaa0b')]",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/d7424fd9-abb3-4ded-a723-eebe023aaa0b')]",
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules",
"kind": "Scheduled",
"apiVersion": "2022-11-01",
"properties": {
"displayName": "New External User Granted Admin",
"description": "'This query will detect instances where a newly invited external user is granted an administrative role. By default this query\nwill alert on any granted administrative role, however this can be modified using the roles variable if false positives occur\nin your environment. The maximum delta between invite and escalation to admin is 60 minues, this can be configured using the \ndeltaBetweenInviteEscalation variable.'\n",
"severity": "Medium",
"enabled": true,
"query": "// Administrative roles to look for, default is all admin roles\nlet roles = dynamic([\"Administrator\", \"Admin\"]);\n// The maximum distances between and invite and acceptance\nlet maxTimeBetweenInviteAccept = 30min;\n// The delta (minutes) between the invite being sent and the account being escalated\nlet deltaBetweenInviteEscalation = 60;\n// Collect external user invitations\nlet invite = AuditLogs\n| where Category =~ \"UserManagement\"\n| where OperationName =~ \"Invite external user\"\n| extend Target = tostring(TargetResources[0].[\"userPrincipalName\"])\n| extend InviteInitiator = tostring(InitiatedBy.[\"user\"].[\"userPrincipalName\"])\n| where isnotempty(InviteInitiator);\n// Collect redeem events\nlet redeem = AuditLogs\n| where Category =~ \"UserManagement\"\n| where OperationName =~ \"Redeem external user invite\"\n| where Result =~ \"success\"\n| extend Target = tostring(TargetResources[0].[\"displayName\"]) | extend Target = tostring(extract(@\"UPN\\:\\s(.+)\\,\\sEmail\",1,Target))\n| where isnotempty(Target);\n// Union the inivtation and redeem data then run the sequence_detect kusto plugin\ninvite\n| union redeem\n| order by TimeGenerated\n| project TimeGenerated, Target, InviteInitiator, OperationName, TenantId\n| evaluate sequence_detect(TimeGenerated, maxTimeBetweenInviteAccept, maxTimeBetweenInviteAccept, invite=(OperationName has \"Invite external user\"), redeem=(OperationName has \"Redeem external user invite\"), Target)\n| join (\nAuditLogs\n| where Category =~ \"RoleManagement\"\n| where AADOperationType in (\"Assign\", \"AssignEligibleRole\")\n| where ActivityDisplayName has_any (\"Add eligible member to role\", \"Add member to role\")\n| mv-expand TargetResources\n// Limit to external accounts\n| where TargetResources.userPrincipalName has \"EXT\"\n| mv-expand TargetResources.modifiedProperties\n| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)\n| where displayName_ =~ \"Role.DisplayName\"\n| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))\n// Perform check for admin roles\n| where RoleName has_any(roles)\n| extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)\n| extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))\n| where Initiator != \"MS-PIM\"\n| extend Target = tostring(TargetResources.userPrincipalName)\n| summarize by TimeGenerated, OperationName, RoleName, Target, Initiator, Result\n) on Target\n// Calculate delta between the invite and the account escalation\n| extend delta = datetime_diff(\"minute\", TimeGenerated, invite_TimeGenerated)\n| where delta <= deltaBetweenInviteEscalation\n| project InvitationTime=invite_TimeGenerated, RedeemTime=redeem_TimeGenerated, GrantTime=TimeGenerated, ExternalUser=Target, RoleGranted=RoleName, AdminInitiator=Initiator, MinsBetweenInviteAndEscalation=delta\n",
"queryFrequency": "P1D",
"queryPeriod": "P1D",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0,
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"Persistence"
],
"techniques": [
"T1098.001"
],
"alertRuleTemplateName": "d7424fd9-abb3-4ded-a723-eebe023aaa0b",
"customDetails": null,
"entityMappings": [
{
"entityType": "Account",
"fieldMappings": [
{
"identifier": "FullName",
"columnName": "ExternalUser"
}
]
},
{
"entityType": "Account",
"fieldMappings": [
{
"identifier": "FullName",
"columnName": "AdminInitiator"
}
]
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Cloud Identity Threat Protection Essentials/Analytic Rules/NewExtUserGrantedAdmin.yaml",
"templateVersion": "1.0.0"
}
}
]
}