Detection rules › Kusto
Solorigate Named Pipe
Identifies a match across various data feeds for named pipe IOCs related to the Solorigate incident. For the sysmon events required for this detection, logging for Named Pipe Events needs to be configured in Sysmon config (Event ID 17 and Event ID 18) Reference: https://techcommunity.microsoft.com/t5/azure-sentinel/solarwinds-post-compromise-hunting-with-azure-sentinel/ba-p/1995095
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | T1055 Process Injection |
| Stealth | T1055 Process Injection |
Event coverage
| Provider | Event | Title |
|---|---|---|
| Sysmon | Event ID 17 | PipeEvent (Pipe Created) |
| Sysmon | Event ID 18 | PipeEvent (Pipe Connected) |
| Security-Auditing | Event ID 5145 | A network share object was checked to see whether client can be granted desired access. |
Rule body kusto
id: 11b4c19d-2a79-4da3-af38-b067e1273dee
name: Solorigate Named Pipe
description: |
'Identifies a match across various data feeds for named pipe IOCs related to the Solorigate incident.
For the sysmon events required for this detection, logging for Named Pipe Events needs to be configured in Sysmon config (Event ID 17 and Event ID 18)
Reference: https://techcommunity.microsoft.com/t5/azure-sentinel/solarwinds-post-compromise-hunting-with-azure-sentinel/ba-p/1995095'
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- PrivilegeEscalation
relevantTechniques:
- T1055
tags:
- Solorigate
- NOBELIUM
query: |
(union isfuzzy=true
(Event
| where Source == "Microsoft-Windows-Sysmon"
| where EventID in (17,18)
| where EventData has '583da945-62af-10e8-4902-a8f205c72b2e'
| extend EventData = parse_xml(EventData).DataItem.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, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, MG, ManagementGroupName, _ResourceId)
| extend PipeName = column_ifexists("PipeName", "")
| extend Account = User
| extend AccountName = tostring(split(User, @"\")[1]), AccountNTDomain = tostring(split(User, @"\")[0])
),
(
SecurityEvent
| where EventID == '5145'
// %%4418 looks for presence of CreatePipeInstance value
| where AccessList has '%%4418'
| where RelativeTargetName has '583da945-62af-10e8-4902-a8f205c72b2e'
| extend AccountName = SubjectUserName, AccountNTDomain = SubjectDomainName
),
(
WindowsEvent
| where EventID == '5145' and EventData has '%%4418' and EventData has '583da945-62af-10e8-4902-a8f205c72b2e'
// %%4418 looks for presence of CreatePipeInstance value
| extend AccessList= tostring(EventData.AccessList)
| where AccessList has '%%4418'
| extend RelativeTargetName= tostring(EventData.RelativeTargetName)
| where RelativeTargetName has '583da945-62af-10e8-4902-a8f205c72b2e'
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountName = tostring(EventData.SubjectUserName), AccountNTDomain = tostring(EventData.SubjectDomainName)
)
)
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
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: NTDomain
columnName: HostNameDomain
version: 1.2.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - 0-day Vulnerability" ]
Stages and Predicates
Stage 1: union
union isfuzzy=true
Stage 2: source time_window=86400s
Event
Stage 3: where
| where Source == "Microsoft-Windows-Sysmon"
Stage 4: where
| where EventID in (17,18)
Stage 5: where
| where EventData has '583da945-62af-10e8-4902-a8f205c72b2e'
Stage 6: extend
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
Stage 7: mv-expand
| mv-expand bagexpansion=array EventData
Stage 8: evaluate
| evaluate bag_unpack(EventData)
Stage 9: extend
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
Stage 10: evaluate
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, MG, ManagementGroupName, _ResourceId)
Stage 11: extend
| extend PipeName = column_ifexists("PipeName", "")
Stage 12: extend
| extend Account = User
Stage 13: extend
| extend AccountName = tostring(split(User, @"\")[1]), AccountNTDomain = tostring(split(User, @"\")[0])
Stage 14: source
SecurityEvent
Stage 15: where
| where EventID == '5145'
Stage 16: where
| where AccessList has '%%4418'
Stage 17: where
| where RelativeTargetName has '583da945-62af-10e8-4902-a8f205c72b2e'
Stage 18: extend
| extend AccountName = SubjectUserName, AccountNTDomain = SubjectDomainName
Stage 19: source
WindowsEvent
Stage 20: where
| where EventID == '5145' and EventData has '%%4418' and EventData has '583da945-62af-10e8-4902-a8f205c72b2e'
Stage 21: extend
| extend AccessList= tostring(EventData.AccessList)
Stage 22: where
| where AccessList has '%%4418'
Stage 23: extend
| extend RelativeTargetName= tostring(EventData.RelativeTargetName)
Stage 24: where
| where RelativeTargetName has '583da945-62af-10e8-4902-a8f205c72b2e'
Stage 25: extend
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
Stage 26: extend
| extend AccountName = tostring(EventData.SubjectUserName), AccountNTDomain = tostring(EventData.SubjectDomainName)
Stage 27: extend
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
Stage 28: extend
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
Stage 29: project-away
| project-away DomainIndex
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 |
|---|---|---|
AccessList | match |
|
EventData | match |
|
EventID | eq |
|
EventID | in |
|
RelativeTargetName | match |
|
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 |
|---|---|
EventData | extend |
Key | extend |
Value | extend |
PipeName | extend |
Account | extend |
AccountNTDomain | extend |
AccountName | extend |
AccessList | extend |
RelativeTargetName | extend |
HostName | extend |
HostNameDomain | extend |