Detection rules › Kusto

Detect non-admin requesting token for admin applications

Group by
AccountObjectId, AppDisplayName, IPAddress, ResultType, UserId, UserPrincipalName
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This rule detects sign-in attempts from non-admin users to admin applications in Entra ID.

MITRE ATT&CK coverage

TacticTechniques
ExecutionT1651 Cloud Administration Command

References

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body yaml

let ITAccounts=(_GetWatchlist('ITAccounts') | summarize make_set(ITAccounts));
// Materialize Dataset
let DataSetMat= materialize (SigninLogs
| where TimeGenerated > ago(1h)
| where AppDisplayName has_any ("PowerShell", "CLI", "Command Line", "Management Shell")
// Get successful and failed due to no assignment logins
| where ResultType in ("0", "50105")
| summarize max(TimeGenerated) by UserPrincipalName, AppDisplayName, IPAddress, UserId, ResultType
// join IdentityInfo to get more information
| join kind=leftouter (IdentityInfo | where TimeGenerated > ago(14d) | summarize arg_max(TimeGenerated, *) by AccountObjectId ) on $left.UserId == $right.AccountObjectId
// exclude Accounts with Assigned Roles
| where array_length(AssignedRoles) == 0
// exclude known IT personnel Departments
| where Department !has "it" and Department !has "ict" and Department !has "operations"
// exclude service accounts
| where JobTitle != "Service Account");
// exclude IT accounts
let FIL= (DataSetMat
| extend ITAccounts= toscalar(ITAccounts)
| mv-expand ITAccounts
| where AccountUPN contains ITAccounts or AccountDisplayName contains ITAccounts);
DataSetMat
// exclude service accounts
| join kind=leftanti FIL on AccountUPN
| distinct  max_TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, JobTitle, Department, UserId, ResultType

Stages and Predicates

Let binding: ITAccounts

let ITAccounts = (_GetWatchlist('ITAccounts') | summarize make_set(ITAccounts));

Let binding: FIL

let FIL = (DataSetMat
| extend ITAccounts= toscalar(ITAccounts)
| mv-expand ITAccounts
| where AccountUPN contains ITAccounts or AccountDisplayName contains ITAccounts);

Derived from ITAccounts, DataSetMat.

The stages below define let DataSetMat (the rule's main pipeline source).

Stage 1: source

SigninLogs

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where AppDisplayName has_any ("PowerShell", "CLI", "Command Line", "Management Shell")

Stage 4: where

| where ResultType in ("0", "50105")

Stage 5: summarize

| summarize max(TimeGenerated) by UserPrincipalName, AppDisplayName, IPAddress, UserId, ResultType

Stage 6: join

| join kind=leftouter (IdentityInfo | where TimeGenerated > ago(14d) | summarize arg_max(TimeGenerated, *) by AccountObjectId ) on $left.UserId == $right.AccountObjectId

Stage 7: where

| where array_length(AssignedRoles) == 0

Stage 8: where

| where Department !has "it" and Department !has "ict" and Department !has "operations"

Stage 9: where

| where JobTitle != "Service Account"

The stages below run on DataSetMat (the outer pipeline).

Stage 10: join (negated)

DataSetMat
| join kind=leftanti FIL on AccountUPN

Stage 11: distinct

| distinct  max_TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, JobTitle, Department, UserId, ResultType

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
Departmentmatchict
Departmentmatchit
Departmentmatchoperations
AccountDisplayNamecontainsITAccounts
AccountUPNcontainsITAccounts
AppDisplayNamematchPowerShell, CLI, Command Line, Management Shell
AssignedRoleseq0
JobTitleneService Account
ResultTypein0, 50105

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
AppDisplayNamematch
  • CLI
  • Command Line
  • Management Shell
  • PowerShell
AssignedRoleseq
  • 0 transforms: array_length
Departmentmatch
  • ict transforms: term
  • it transforms: term
  • operations transforms: term
JobTitlene
  • Service Account transforms: cased
ResultTypein
  • 0 transforms: cased
  • 50105 transforms: 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
AppDisplayNamesummarize
IPAddresssummarize
ResultTypesummarize
UserIdsummarize
UserPrincipalNamesummarize