Detection rules › Kusto

Dataverse - Hierarchy security manipulation

Status
available
Severity
medium
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Identifies suspicious behaviors in hierarchy security including: - Hierarchy security disabled. - User assigns themselves as a manager. - User assigns themselves to a monitored position.

MITRE ATT&CK coverage

Rule body kusto

id: 2df0adf5-92a8-4ee0-a123-3eb5be1eed02
kind: Scheduled
name: Dataverse - Hierarchy security manipulation
description: |
  Identifies suspicious behaviors in hierarchy security including:
  - Hierarchy security disabled.
  - User assigns themselves as a manager.
  - User assigns themselves to a monitored position.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1548
  - T1078
query: |
  let monitored_position_ids = dynamic([
      // Enter a list of monitored position ID (guids)
      //"79380ac5-da2a-ed11-9db1-000d3a58d546"
      ]);
  let query_frequency = 1h;
  let security_disabled_events = DataverseActivity
      | where TimeGenerated >= ago(query_frequency)
      | where Message == "Update" and EntityName == "organization"
      | mv-expand Fields
      | where Fields.Name == "ishierarchicalsecuritymodelenabled"
      | where Fields.Value == "False"
      | extend Message = "Hierarchy security has been disabled"
      | project TimeGenerated, UserId, ClientIp, InstanceUrl, Message;
  let assign_self_as_manager_events = DataverseActivity
      | where TimeGenerated >= ago(query_frequency)
      | where Message == "Update" and EntityName == "systemuser"
      | mv-expand Fields
      | where Fields.Name == "parentsystemuserid"
      | extend ModifiedManager = tostring(Fields.Value)
      | where SystemUserId == ModifiedManager
      | extend Message = "User added self as manager of another user";
  let assign_self_to_position_events = DataverseActivity
      | where TimeGenerated >= ago(query_frequency)
      | where Message == "Update" and EntityName == "systemuser"
      | mv-expand Position = Fields
      | where Position.Name == "positionid" and tostring(Position.Value) in (monitored_position_ids)
      | mv-expand Target = Fields
      | where Target.Name == "systemuserid"
      | extend UserAssigned = tostring(Target.Value)
      | where SystemUserId == UserAssigned
      | extend
          Message = "User assigned self to a monitored position",
          PositionId = tostring(Position.Value);
  union
      security_disabled_events,
      assign_self_as_manager_events,
      assign_self_to_position_events
  | extend
      CloudAppId = int(32780),
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1])
  | project
      TimeGenerated,
      UserId,
      ClientIp,
      InstanceUrl,
      Message,
      PositionId,
      CloudAppId,
      AccountName,
      UPNSuffix
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - Suspicious hierarchy security modifications
    in {{InstanceUrl}} '
  alertDescriptionFormat: '{{Message}}. Events detected for user {{UserId}}.'
version: 3.2.0

Stages and Predicates

Parameters

let query_frequency = 1h;

Let binding: monitored_position_ids

let monitored_position_ids = dynamic([
    ]);

Let binding: security_disabled_events

let security_disabled_events = DataverseActivity
    | where TimeGenerated >= ago(query_frequency)
    | where Message == "Update" and EntityName == "organization"
    | mv-expand Fields
    | where Fields.Name == "ishierarchicalsecuritymodelenabled"
    | where Fields.Value == "False"
    | extend Message = "Hierarchy security has been disabled"
    | project TimeGenerated, UserId, ClientIp, InstanceUrl, Message;

Derived from query_frequency.

Let binding: assign_self_as_manager_events

let assign_self_as_manager_events = DataverseActivity
    | where TimeGenerated >= ago(query_frequency)
    | where Message == "Update" and EntityName == "systemuser"
    | mv-expand Fields
    | where Fields.Name == "parentsystemuserid"
    | extend ModifiedManager = tostring(Fields.Value)
    | where SystemUserId == ModifiedManager
    | extend Message = "User added self as manager of another user";

Derived from query_frequency.

Let binding: assign_self_to_position_events

let assign_self_to_position_events = DataverseActivity
    | where TimeGenerated >= ago(query_frequency)
    | where Message == "Update" and EntityName == "systemuser"
    | mv-expand Position = Fields
    | where Position.Name == "positionid" and tostring(Position.Value) in (monitored_position_ids)
    | mv-expand Target = Fields
    | where Target.Name == "systemuserid"
    | extend UserAssigned = tostring(Target.Value)
    | where SystemUserId == UserAssigned
    | extend
        Message = "User assigned self to a monitored position",
        PositionId = tostring(Position.Value);

Derived from monitored_position_ids, query_frequency.

union (3 sources)

Each leg below queries one source; the rule matches if any leg does. Sources: security_disabled_events, assign_self_as_manager_events, assign_self_to_position_events

Leg 1: security_disabled_events

Leg 2: assign_self_as_manager_events

Leg 3: assign_self_to_position_events

Applied to the combined result

| extend
    CloudAppId = int(32780),
    AccountName = tostring(split(UserId, '@')[0]),
    UPNSuffix = tostring(split(UserId, '@')[1]) | project
    TimeGenerated,
    UserId,
    ClientIp,
    InstanceUrl,
    Message,
    PositionId,
    CloudAppId,
    AccountName,
    UPNSuffix

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
EntityNameeq
  • organization transforms: cased corpus 4 (kusto 4)
  • systemuser transforms: cased corpus 3 (kusto 3)
Messageeq
  • Update transforms: cased corpus 5 (kusto 5)
Nameeq
  • ishierarchicalsecuritymodelenabled transforms: cased
  • parentsystemuserid transforms: cased
  • positionid transforms: cased
  • systemuserid transforms: cased
SystemUserIdeq
  • ModifiedManager transforms: cased
  • UserAssigned transforms: cased
Valueeq
  • False transforms: cased corpus 3 (kusto 3)
Valuein
  • [] transforms: tostring, cased

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
AccountNameproject
ClientIpproject
CloudAppIdproject
InstanceUrlproject
Messageproject
PositionIdproject
TimeGeneratedproject
UPNSuffixproject
UserIdproject