Detection rules › Kusto
Detect non-admin requesting token for admin applications
This rule detects sign-in attempts from non-admin users to admin applications in Entra ID.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | T1651 Cloud Administration Command |
References
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Many Failed SignIns (Panther)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Discovery Using AzureHound (Sigma)
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.
| Field | Kind | Excluded values |
|---|---|---|
Department | match | ict |
Department | match | it |
Department | match | operations |
AccountDisplayName | contains | ITAccounts |
AccountUPN | contains | ITAccounts |
AppDisplayName | match | PowerShell, CLI, Command Line, Management Shell |
AssignedRoles | eq | 0 |
JobTitle | ne | Service Account |
ResultType | in | 0, 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.
| Field | Kind | Values |
|---|---|---|
AppDisplayName | match |
|
AssignedRoles | eq |
|
Department | match |
|
JobTitle | ne |
|
ResultType | in |
|
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.
| Field | Source |
|---|---|
AppDisplayName | summarize |
IPAddress | summarize |
ResultType | summarize |
UserId | summarize |
UserPrincipalName | summarize |