Detection rules › Kusto
Cisco - firewall block but success logon to Microsoft Entra ID
Correlate IPs blocked by a Cisco firewall appliance with successful Microsoft Entra ID signins. Because the IP was blocked by the firewall, that same IP logging on successfully to Entra ID is potentially suspect and could indicate credential compromise for the user account.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry coverage
Rules detecting the same action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- 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 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)
- Detect non-admin requesting token for admin applications (Kusto)
Rule body
id: 157c0cfc-d76d-463b-8755-c781608cdc1a
name: Cisco - firewall block but success logon to Microsoft Entra ID
description: |
'Correlate IPs blocked by a Cisco firewall appliance with successful Microsoft Entra ID signins.
Because the IP was blocked by the firewall, that same IP logging on successfully to Entra ID is potentially suspect and could indicate credential compromise for the user account.'
severity: Medium
requiredDataConnectors:
- connectorId: CiscoASA
dataTypes:
- CommonSecurityLog
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let aadFunc = (tableName:string){
CommonSecurityLog
| where DeviceVendor =~ "Cisco"
| where DeviceAction =~ "denied"
| where ipv4_is_private(SourceIP) == false
| summarize count() by SourceIP
| join (
// Successful signins from IPs blocked by the firewall solution are suspect
// Include fully successful sign-ins, but also ones that failed only at MFA stage
// as that supposes the password was sucessfully guessed.
table(tableName)
| where ResultType in ("0", "50074", "50076")
) on $left.SourceIP == $right.IPAddress
| extend AccountName = tostring(split(Account, "@")[0]), AccountUPNSuffix = tostring(split(Account, "@")[1])
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
union isfuzzy=true aadSignin, aadNonInt
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
version: 1.0.6
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Resarch
support:
tier: Community
categories:
domains: [ "Security - Network" ]
Stages and Predicates
Stage 0: let
let aadFunc = (tableName:string){
CommonSecurityLog
| where DeviceVendor =~ "Cisco"
| where DeviceAction =~ "denied"
| where ipv4_is_private(SourceIP) == false
| summarize count() by SourceIP
| join (
table(tableName)
| where ResultType in ("0", "50074", "50076")
) on $left.SourceIP == $right.IPAddress
| extend AccountName = tostring(split(Account, "@")[0]), AccountUPNSuffix = tostring(split(Account, "@")[1])
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
Stage 1: union
union of 2 branches
Stage 2: source
CommonSecurityLog
Stage 3: where
where DeviceVendor =~ "Cisco"
Stage 4: where
where DeviceAction =~ "denied"
Stage 5: where
where not ((ipv4_is_in_range(SourceIP, "10.0.0.0/8") or ipv4_is_in_range(SourceIP, "172.16.0.0/12") or ipv4_is_in_range(SourceIP, "192.168.0.0/16") or ipv4_is_in_range(SourceIP, "169.254.0.0/16") or ipv4_is_in_range(SourceIP, "127.0.0.0/8")))
Stage 6: summarize
summarize by SourceIP
Stage 7: join
join (...) on SourceIP, IPAddress
Stage 8: extend
extend AccountName, AccountUPNSuffix
Stage 9: source
CommonSecurityLog
Stage 10: where
where DeviceVendor =~ "Cisco"
Stage 11: where
where DeviceAction =~ "denied"
Stage 12: where
where not ((ipv4_is_in_range(SourceIP, "10.0.0.0/8") or ipv4_is_in_range(SourceIP, "172.16.0.0/12") or ipv4_is_in_range(SourceIP, "192.168.0.0/16") or ipv4_is_in_range(SourceIP, "169.254.0.0/16") or ipv4_is_in_range(SourceIP, "127.0.0.0/8")))
Stage 13: summarize
summarize by SourceIP
Stage 14: join
join (...) on SourceIP, IPAddress
Stage 15: extend
extend AccountName, AccountUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceAction | eq |
| field:"DeviceAction" kind:eq value:"denied" |
DeviceVendor | eq |
| field:"DeviceVendor" kind:eq value:"Cisco" |
ResultType | in |
| field:"ResultType" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SourceIP | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:SourceIP |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SourceIP | summarize |
AccountName | extend |
AccountUPNSuffix | extend |