Detection rules › Kusto

User Added to Admin Role

Severity
low
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Detects a user being added to a new privileged role. Monitor these additions to ensure the users are made eligible for these roles are intended to have these levels of access. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts

MITRE ATT&CK coverage

TacticTechniques
Privilege EscalationT1078.004 Valid Accounts: Cloud Accounts

Event coverage

Rule body kusto

id: 2a09f8cb-deb7-4c40-b08b-9137667f1c0b
name: User Added to Admin Role
description: |
  'Detects a user being added to a new privileged role. Monitor these additions to ensure the users are made eligible for these roles are intended to have these levels of access.
    Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts'
severity: Low
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  AuditLogs
    | where OperationName in ("Add eligible member (permanent)", "Add eligible member (eligible)", "Add member to role")
    | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "User"
        | extend Target = tostring(TargetResource.userPrincipalName),
                 props = TargetResource.modifiedProperties
    )
    | mv-apply Property = props on 
    (
        where Property.displayName =~ "Role.DisplayName"
        | extend RoleName = trim('"',tostring(Property.newValue))
    )
    | where RoleName contains "admin"
    | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
    | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
    | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
    | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
    | extend InitiatedBy = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
    | extend TargetUserPrincipalName = iff(OperationName=="Add member to role",tostring(TargetResources[0].userPrincipalName),tostring(TargetResources[2].userPrincipalName))
    | extend TargetAadUserId = iff(OperationName=="Add member to role", tostring(TargetResources[0].id), tostring(TargetResources[2].id))
    | extend AddedUser = TargetUserPrincipalName
    | extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
    | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
    | project-reorder TimeGenerated, AddedUser, RoleName, InitiatedBy
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUserPrincipalName
      - identifier: Name
        columnName: TargetAccountName
      - identifier: UPNSuffix
        columnName: TargetAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: TargetAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName   
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName in ("Add eligible member (permanent)", "Add eligible member (eligible)", "Add member to role")

Stage 3: kusto:mv-apply

| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "User"
      | extend Target = tostring(TargetResource.userPrincipalName),
               props = TargetResource.modifiedProperties
  )

Stage 4: kusto:mv-apply

| mv-apply Property = props on 
  (
      where Property.displayName =~ "Role.DisplayName"
      | extend RoleName = trim('"',tostring(Property.newValue))
  )

Stage 5: where

| where RoleName contains "admin"

Stage 6: extend (11 consecutive steps)

| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatedBy = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
| extend TargetUserPrincipalName = iff(OperationName=="Add member to role",tostring(TargetResources[0].userPrincipalName),tostring(TargetResources[2].userPrincipalName))
| extend TargetAadUserId = iff(OperationName=="Add member to role", tostring(TargetResources[0].id), tostring(TargetResources[2].id))
| extend AddedUser = TargetUserPrincipalName
| extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])

Stage 7: project-reorder

| project-reorder TimeGenerated, AddedUser, RoleName, InitiatedBy

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
OperationNamein
  • Add eligible member (eligible) transforms: cased
  • Add eligible member (permanent) transforms: cased
  • Add member to role transforms: cased
RoleNamecontains
  • admin
displayNameeq
  • Role.DisplayName
typeeq
  • User

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIPAddressextend
InitiatedByextend
TargetUserPrincipalNameextend
TargetAadUserIdextend
AddedUserextend
TargetAccountNameextend
TargetAccountUPNSuffixextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend