Detection rules › Kusto
Gain Code Execution on ADFS Server via SMB + Remote Service or Scheduled Task
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
| Tactic | Techniques |
|---|---|
| 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
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.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Account | ends_with | $ | excludes:Account field:"Account" value:"$" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Computer | in |
| field:"Computer" kind:in value:"ADFS_Servers" |
EventID | eq |
| field:"EventID" kind:eq |
EventID | in |
| field:"EventID" kind:in |
LogonType | eq |
| field:"LogonType" kind:eq value:"3" |
RelativeTargetName | eq |
| field:"RelativeTargetName" kind:eq |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
Computer | project |
EventID | project |
RelativeTargetName | project |
TimeGenerated | project |
timestamp | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |