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 | T1210 Exploitation of Remote Services |
Event coverage
| Provider | Event | Title |
|---|---|---|
| Security-Auditing | Event ID 4624 | An account was successfully logged on. |
| Security-Auditing | Event ID 4688 | A new process has been created. |
| Security-Auditing | Event ID 4697 | A service was installed in the system. |
| Security-Auditing | Event ID 4698 | A scheduled task was created. |
| Security-Auditing | Event ID 4699 | A scheduled task was deleted. |
| Security-Auditing | Event ID 4700 | A scheduled task was enabled. |
| Security-Auditing | Event ID 4701 | A scheduled task was disabled. |
| Security-Auditing | Event ID 4702 | A scheduled task was updated. |
| Security-Auditing | Event ID 5145 | A network share object was checked to see whether client can be granted desired access. |
Rule body kusto
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
);
Derived from timeframe, lookback.
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)
References ADFS_Servers (defined above).
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)
References ADFS_Servers (defined above).
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
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
Account | ends_with | $ |
Account | ends_with | $ |
Account | ends_with | $ |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
Computer | in |
|
EventID | eq |
|
EventID | in |
|
LogonType | eq |
|
RelativeTargetName | eq |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
Account | project |
Computer | project |
EventID | project |
RelativeTargetName | project |
TimeGenerated | project |
timestamp | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |