Detection rules › Kusto

Gain Code Execution on ADFS Server via SMB + Remote Service or Scheduled Task

Status
available
Severity
medium
Time window
7d
Group by
SubjectLogonId, TargetLogonId
Source
github.com/Azure/Azure-Sentinel

This query detects instances where an attacker has gained the ability to execute code on an ADFS Server through SMB and Remote Service or Scheduled Task.

MITRE ATT&CK coverage

TacticTechniques
Lateral Movement

Telemetry coverage

Rule body

id: 12dcea64-bec2-41c9-9df2-9f28461b1295
name: Gain Code Execution on ADFS Server via SMB + Remote Service or Scheduled Task
description: |
   'This query detects instances where an attacker has gained the ability to execute code on an ADFS Server through SMB and Remote Service or Scheduled Task.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - LateralMovement
relevantTechniques:
  - T1210
tags:
  - Solorigate
  - NOBELIUM
query: |
  let timeframe = 1d;
  // Adjust for a longer timeframe for identifying ADFS Servers
  let lookback = 6d;
  // Identify ADFS Servers
  let ADFS_Servers = (
  SecurityEvent
  | where TimeGenerated > ago(timeframe+lookback)
  | where EventID == 4688 and SubjectLogonId != "0x3e4"
  | where NewProcessName has "Microsoft.IdentityServer.ServiceHost.exe"
  | distinct Computer
  );
  SecurityEvent
  | where TimeGenerated > ago(timeframe)
  | where Computer in~ (ADFS_Servers)
  | where Account !endswith "$"
  // Check for scheduled task events
  | where EventID in (4697, 4698, 4699, 4700, 4701, 4702)
  | extend EventDataParsed = parse_xml(EventData)
  | extend SubjectLogonId = tostring(EventDataParsed.EventData.Data[3]["#text"])
  // Check specifically for access to IPC$ share and PIPE\svcctl and PIPE\atsvc for Service Control Services and Schedule Control Services
  | union (
      SecurityEvent
      | where TimeGenerated > ago(timeframe)
      | where Computer in~ (ADFS_Servers)
      | where Account !endswith "$"
      | where EventID == 5145
      | where RelativeTargetName =~ "svcctl" or RelativeTargetName  =~ "atsvc"
  )
  // Check for lateral movement
  | join kind=inner
  (SecurityEvent
  | where TimeGenerated > ago(timeframe)
  | where Account !endswith "$"
  | where EventID == 4624 and LogonType == 3
  ) on $left.SubjectLogonId == $right.TargetLogonId
  | project TimeGenerated, Account, Computer, EventID, RelativeTargetName
  | extend timestamp = TimeGenerated
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.2.1
kind: Scheduled

Stages and Predicates

Parameters

let timeframe = 1d;
let lookback = 6d;

Let binding: ADFS_Servers used in Stages 1, 4, 12

let ADFS_Servers = (
SecurityEvent
| where TimeGenerated > ago(timeframe+lookback)
| where EventID == 4688 and SubjectLogonId != "0x3e4"
| where NewProcessName has "Microsoft.IdentityServer.ServiceHost.exe"
| distinct Computer
);

Stage 1: source

let ADFS_Servers

Stage 2: source

SecurityEvent

Stage 3: where

| where TimeGenerated > ago(timeframe)

Stage 4: where

| where Computer in~ (ADFS_Servers)

Stage 5: where

| where Account !endswith "$"

Stage 6: where

| where EventID in (4697, 4698, 4699, 4700, 4701, 4702)

Stage 7: extend

| extend EventDataParsed = parse_xml(EventData)

Stage 8: extend

| extend SubjectLogonId = tostring(EventDataParsed.EventData.Data[3]["#text"])

Stage 9: union

| union

Stage 10: source

SecurityEvent

Stage 11: where

| where TimeGenerated > ago(timeframe)

Stage 12: where

| where Computer in~ (ADFS_Servers)

Stage 13: where

| where Account !endswith "$"

Stage 14: where

| where EventID == 5145

Stage 15: where

| where RelativeTargetName =~ "svcctl" or RelativeTargetName  =~ "atsvc"

Stage 16: join

| join kind=inner
(SecurityEvent
| where TimeGenerated > ago(timeframe)
| where Account !endswith "$"
| where EventID == 4624 and LogonType == 3
) on $left.SubjectLogonId == $right.TargetLogonId

Stage 17: project

| project TimeGenerated, Account, Computer, EventID, RelativeTargetName

Stage 18: extend (4 consecutive steps)

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

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
Accountends_with$excludes:Account field:"Account" value:"$"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Computerin
  • ADFS_Servers corpus 5 (kusto 5)
field:"Computer" kind:in value:"ADFS_Servers"
EventIDeq
  • 4624 corpus 29 (splunk 13, kusto 11, chronicle 4, elastic 1)
  • 5145 corpus 23 (splunk 16, elastic 5, kusto 2)
field:"EventID" kind:eq
EventIDin
  • 4697 corpus 3 (splunk 2, elastic 1)
  • 4698 corpus 13 (splunk 13)
  • 4699
  • 4700
  • 4701
  • 4702
field:"EventID" kind:in
LogonTypeeq
  • 3 corpus 41 (splunk 13, sigma 12, elastic 9, kusto 7)
field:"LogonType" kind:eq value:"3"
RelativeTargetNameeq
  • atsvc corpus 4 (sigma 3, kusto 1)
  • svcctl corpus 6 (sigma 5, kusto 1)
field:"RelativeTargetName" kind:eq

Output fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
Computerproject
EventIDproject
RelativeTargetNameproject
TimeGeneratedproject
timestampextend
DomainIndexextend
HostNameextend
HostNameDomainextend
AccountNTDomainextend
AccountNameextend