Detection rules › Kusto

Cisco - firewall block but success logon to Microsoft Entra ID

Severity
medium
Time window
1d
Group by
IPAddress, SourceIP
Author
Microsoft Security Resarch
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Initial Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
SourceIPcidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:SourceIP

Output fields

These fields are emitted when the rule matches.

FieldSource
SourceIPsummarize
AccountNameextend
AccountUPNSuffixextend