Detection rules › Kusto

AD FS Remote Auth Sync Connection

Status
available
Severity
medium
Time window
1d
Group by
Computer, InstanceId
Source
github.com/Azure/Azure-Sentinel

This detection uses Security events from the "AD FS Auditing" provider to detect suspicious authentication events on an AD FS server. The results then get correlated with events from the Windows Filtering Platform (WFP) to detect suspicious incoming network traffic on port 80 on the AD FS server. This could be a sign of a threat actor trying to use replication services on the AD FS server to get its configuration settings and extract sensitive information such as AD FS certificates. In order to use this query you need to enable AD FS auditing on the AD FS Server. References: https://docs.microsoft.com/windows-server/identity/ad-fs/troubleshooting/ad-fs-tshoot-logging https://twitter.com/OTR_Community/status/1387038995016732672

MITRE ATT&CK coverage

TacticTechniques
Collection

Telemetry coverage

Rule body

id: 2f4165a6-c4fb-4e94-861e-37f1b4d6c0e6
name: AD FS Remote Auth Sync Connection
description: |
  'This detection uses Security events from the "AD FS Auditing" provider to detect suspicious authentication events on an AD FS server. The results then get correlated with events from the Windows Filtering Platform (WFP) to detect suspicious incoming network traffic on port 80 on the AD FS server.
  This could be a sign of a threat actor trying to use replication services on the AD FS server to get its configuration settings and extract sensitive information such as AD FS certificates. In order to use this query you need to enable AD FS auditing on the AD FS Server.
  References:
  https://docs.microsoft.com/windows-server/identity/ad-fs/troubleshooting/ad-fs-tshoot-logging
  https://twitter.com/OTR_Community/status/1387038995016732672'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Collection
relevantTechniques:
  - T1005
tags:
  - SimuLand
query: |
  // Adjust this to use a longer timeframe to identify ADFS servers
  //let lookback = 0d;
  // Adjust this to adjust detection timeframe
  //let timeframe = 1d;
  // SamAccountName of AD FS Service Account. Filter on the use of a specific AD FS user account
  //let adfsuser = 'adfsadmin';
  // Identify ADFS Servers
  let ADFS_Servers = (
      SecurityEvent
      //| where TimeGenerated > ago(timeframe+lookback)
      | where EventSourceName == 'AD FS Auditing'
      | distinct Computer
  );
  SecurityEvent
      //| where TimeGenerated > ago(timeframe)
      | where Computer in~ (ADFS_Servers)
      // A token of type 'http://schemas.microsoft.com/ws/2006/05/servicemodel/tokens/SecureConversation'
      // for relying party '-' was successfully authenticated.
      | where EventID == 412
      | extend EventData = parse_xml(EventData).EventData.Data
      | extend InstanceId = tostring(EventData[0])
  | join kind=inner
  (
      SecurityEvent
      //| where TimeGenerated > ago(timeframe)
      | where Computer in~ (ADFS_Servers)
      // Events to identify caller identity from event 412
      | where EventID == 501
      | extend EventData = parse_xml(EventData).EventData.Data
      | where tostring(EventData[1]) contains 'identity/claims/name'
      | extend InstanceId = tostring(EventData[0])
      | extend ClaimsName = tostring(EventData[2])
      // Filter on the use of a specific AD FS user account
      //| where ClaimsName contains adfsuser
  )
  on $left.InstanceId == $right.InstanceId
  | join kind=inner
  (
      SecurityEvent
      | where EventID == 5156
      | where Computer in~ (ADFS_Servers)
      | extend EventData = parse_xml(EventData).EventData.Data
      | mv-expand bagexpansion=array EventData
      | evaluate bag_unpack(EventData)
      | extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
      | evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
      | extend DestPort = column_ifexists("DestPort", ""),
            Direction = column_ifexists("Direction", ""),
            Application = column_ifexists("Application", ""),
            DestAddress = column_ifexists("DestAddress", ""),
            SourceAddress = column_ifexists("SourceAddress", ""),
            SourcePort = column_ifexists("SourcePort", "")
      // Look for inbound connections from endpoints on port 80
      | where DestPort == 80 and Direction == '%%14592' and Application == 'System'
      | where DestAddress !in ('::1','0:0:0:0:0:0:0:1')
  )
  on $left.Computer == $right.Computer
  | project TimeGenerated, Computer, ClaimsName, SourceAddress, SourcePort
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend AccountName = tostring(split(ClaimsName, @'\')[1]), AccountNTDomain = tostring(split(ClaimsName, @'\')[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ClaimsName
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceAddress
version: 1.0.4
kind: Scheduled

Stages and Predicates

Let binding: ADFS_Servers used in Stages 2, 6, 7

let ADFS_Servers = (
    SecurityEvent
    | where EventSourceName == 'AD FS Auditing'
    | distinct Computer
);

Stage 1: source

SecurityEvent

Stage 2: where

| where Computer in~ (ADFS_Servers)

Stage 3: where

| where EventID == 412

Stage 4: extend

| extend EventData = parse_xml(EventData).EventData.Data

Stage 5: extend

| extend InstanceId = tostring(EventData[0])

Stage 6: join

| join kind=inner
(
    SecurityEvent
    | where Computer in~ (ADFS_Servers)
    | where EventID == 501
    | extend EventData = parse_xml(EventData).EventData.Data
    | where tostring(EventData[1]) contains 'identity/claims/name'
    | extend InstanceId = tostring(EventData[0])
    | extend ClaimsName = tostring(EventData[2])
)
on $left.InstanceId == $right.InstanceId

Stage 7: join

| join kind=inner
(
    SecurityEvent
    | where EventID == 5156
    | where Computer in~ (ADFS_Servers)
    | extend EventData = parse_xml(EventData).EventData.Data
    | mv-expand bagexpansion=array EventData
    | evaluate bag_unpack(EventData)
    | extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
    | evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
    | extend DestPort = column_ifexists("DestPort", ""),
          Direction = column_ifexists("Direction", ""),
          Application = column_ifexists("Application", ""),
          DestAddress = column_ifexists("DestAddress", ""),
          SourceAddress = column_ifexists("SourceAddress", ""),
          SourcePort = column_ifexists("SourcePort", "")
    | where DestPort == 80 and Direction == '%%14592' and Application == 'System'
    | where DestAddress !in ('::1','0:0:0:0:0:0:0:1')
)
on $left.Computer == $right.Computer

Stage 8: project

| project TimeGenerated, Computer, ClaimsName, SourceAddress, SourcePort

Stage 9: extend (3 consecutive steps)

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(ClaimsName, @'\')[1]), AccountNTDomain = tostring(split(ClaimsName, @'\')[0])

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DestAddressin0:0:0:0:0:0:0:1, ::1excludes:DestAddress field:"DestAddress" value:"0:0:0:0:0:0:0:1" field:"DestAddress" value:"::1"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Applicationeq
  • System corpus 3 (sigma 2, kusto 1)
field:"Image" kind:eq value:"System"
Computerin
  • ADFS_Servers corpus 5 (kusto 5)
field:"Computer" kind:in value:"ADFS_Servers"
DestPorteq
  • 80 corpus 11 (sigma 6, elastic 3, kusto 2)
field:"DestinationPort" kind:eq value:"80"
Directioneq
  • %%14592
field:"Direction" kind:eq value:"%%14592"
EventData[1]contains
  • identity/claims/name transforms: tostring
field:"EventData[1]" kind:contains value:"identity/claims/name"
EventIDeq
  • 412
  • 501 corpus 2 (kusto 2)
  • 5156 corpus 15 (splunk 13, kusto 2)
field:"EventID" kind:eq

Output fields

These fields are emitted when the rule matches.

FieldSource
ClaimsNameproject
Computerproject
SourceAddressproject
SourcePortproject
TimeGeneratedproject
DomainIndexextend
HostNameextend
HostNameDomainextend
AccountNTDomainextend
AccountNameextend