Detection rules › Kusto

External user added and removed in short timeframe

Status
available
Severity
low
Time window
1h
Group by
MemberAdded, MemberRemoved
Source
github.com/Azure/Azure-Sentinel

This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour.

MITRE ATT&CK coverage

TacticTechniques
Persistence

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

id: bff093b2-500e-4ae5-bb49-a5b1423cbd5b
name: External user added and removed in short timeframe
description: |
  'This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (Teams)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1136
query: |
  let TeamsAddDel = (Op:string){
  OfficeActivity
  | where OfficeWorkload =~ "MicrosoftTeams"
  | where Operation == Op
  | where Members has ("#EXT#")
  | mv-expand Members
  | extend UPN = tostring(Members.UPN)
  | where UPN has ("#EXT#")
  | project TimeGenerated, Operation, UPN, UserId, TeamName, ClientIP
  };
  let TeamsAdd = TeamsAddDel("MemberAdded")
  | project TimeAdded=TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;
  let TeamsDel = TeamsAddDel("MemberRemoved")
  | project TimeDeleted=TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;
  TeamsAdd
  | join kind=inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved
  | where TimeDeleted > TimeAdded
  | project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, ClientIP
  | extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, "@")[0]), MemberAdded_RemovedAccountUPNSuffix = tostring(split(MemberAdded_Removed, "@")[1])
  | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1])
  | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: MemberAdded_Removed
      - identifier: Name
        columnName: MemberAdded_RemovedAccountName
      - identifier: UPNSuffix
        columnName: MemberAdded_RemovedAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserWhoAdded
      - identifier: Name
        columnName: UserWhoAddedAccountName
      - identifier: UPNSuffix
        columnName: UserWhoAddedAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserWhoDeleted
      - identifier: Name
        columnName: UserWhoDeletedAccountName
      - identifier: UPNSuffix
        columnName: UserWhoDeletedAccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
version: 2.1.3
kind: Scheduled

Stages and Predicates

let TeamsAddDel and let TeamsAdd are inlined into the numbered stages below.

Let binding: TeamsDel used in Stage 10

let TeamsDel = TeamsAddDel("MemberRemoved")
| project TimeDeleted=TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;

Stages 1 to 9 define let TeamsAdd (the rule's main pipeline source); stages 10 to 13 run on it.

Stage 1: source

OfficeActivity

Stage 2: where

| where OfficeWorkload =~ "MicrosoftTeams"

Stage 3: where

| where Operation == Op

Stage 4: where

| where Members has ("#EXT#")

Stage 5: mv-expand

| mv-expand Members

Stage 6: extend

| extend UPN = tostring(Members.UPN)

Stage 7: where

| where UPN has ("#EXT#")

Stage 8: project

| project TimeGenerated, Operation, UPN, UserId, TeamName, ClientIP

Stage 9: project

| project TimeAdded=TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP

Stage 10: join

TeamsAdd
| join kind=inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved

Stage 11: where

| where TimeDeleted > TimeAdded

Stage 12: project

| project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, ClientIP

Stage 13: extend (3 consecutive steps)

| extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, "@")[0]), MemberAdded_RemovedAccountUPNSuffix = tostring(split(MemberAdded_Removed, "@")[1])
| extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1])
| extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1])

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
ClientIPproject
MemberAdded_Removedproject
TeamNameproject
TimeAddedproject
TimeDeletedproject
UserWhoAddedproject
UserWhoDeletedproject
MemberAdded_RemovedAccountNameextend
MemberAdded_RemovedAccountUPNSuffixextend
UserWhoAddedAccountNameextend
UserWhoAddedAccountUPNSuffixextend
UserWhoDeletedAccountNameextend
UserWhoDeletedAccountUPNSuffixextend