145 строки
8.4 KiB
YAML
145 строки
8.4 KiB
YAML
id: 3dc5dc8b-160b-407e-9925-24a91e3599df
|
|
name: Rare firewall rule changes using netsh
|
|
description: |
|
|
This query will show rare firewall rule changes using netsh utility by comparing rule names and program names from the previous day
|
|
with those from the historical chosen time frame.
|
|
- This technique was seen in relation to Solarigate attack but the results can indicate potential malicious activity used in different attacks.
|
|
- The process name in each data source is commented out as an adversary could rename it. It is advisable to keep process name commented but
|
|
if the results show unrelated false positives, users may want to uncomment it.
|
|
- Note also that the queries use the KQL "has_all" operator, which hasn't yet been documented officially, but will be soon.
|
|
In short, "has_all" will only match when the referenced field has all strings in the list.
|
|
Refer to netsh syntax: https://docs.microsoft.com/windows-server/administration/windows-commands/netsh
|
|
Refer to our Microsoft Defender XDR blog for details on use during the Solorigate attack:
|
|
https://www.microsoft.com/security/blog/2021/01/20/deep-dive-into-the-solorigate-second-stage-activation-from-sunburst-to-teardrop-and-raindrop/
|
|
severity: Low
|
|
requiredDataConnectors:
|
|
- connectorId: SecurityEvents
|
|
dataTypes:
|
|
- SecurityEvent
|
|
- connectorId: MicrosoftThreatProtection
|
|
dataTypes:
|
|
- DeviceProcessEvents
|
|
tactics:
|
|
- Execution
|
|
relevantTechniques:
|
|
- T1204
|
|
tags:
|
|
- Solorigate
|
|
- NOBELIUM
|
|
query: |
|
|
let starttime = todatetime('{{StartTimeISO}}');
|
|
let endtime = todatetime('{{EndTimeISO}}');
|
|
// historical time frame
|
|
let lookback = totimespan((endtime-starttime)*7);
|
|
let AccountAllowList = dynamic(['SYSTEM']);
|
|
let tokens = dynamic(["add", "delete", "set"]);
|
|
(union isfuzzy=true
|
|
(
|
|
SecurityEvent
|
|
| where TimeGenerated >= ago(lookback)
|
|
// remove comment below to adjust for noise
|
|
// | where Process =~ "netsh.exe"
|
|
| where CommandLine has_all ("advfirewall", "firewall") and CommandLine has_any (tokens)
|
|
| where AccountType !~ "Machine" and Account !in~ (AccountAllowList)
|
|
| extend KeyValuePairs = extract_all(@'(?P<key>\w+)=(?P<value>[a-zA-Z0-9-\":\\\s$_@()."]+\"|[a-zA-Z0-9-\":$_\\@()."]+)', dynamic(["key","value"]), CommandLine)
|
|
| mv-apply KeyValuePairs on (
|
|
summarize CommandLineParsed = make_bag(pack(tostring(KeyValuePairs[0]), KeyValuePairs[1]))
|
|
)
|
|
| extend RuleName = tostring(parse_json(CommandLineParsed).name), Program = tostring(parse_json(CommandLineParsed).program)
|
|
| join kind=leftanti (
|
|
SecurityEvent
|
|
| where TimeGenerated between (starttime..endtime)
|
|
// remove comment below to adjust for noise
|
|
// | where Process =~ "netsh.exe"
|
|
| where CommandLine has_all ("advfirewall", "firewall") and CommandLine has_any (tokens)
|
|
| where AccountType !~ "Machine" and Account !in~ (AccountAllowList)
|
|
| extend KeyValuePairs = extract_all(@'(?P<key>\w+)=(?P<value>[a-zA-Z0-9-\":\\\s$_@()."]+\"|[a-zA-Z0-9-\":$_\\@()."]+)', dynamic(["key","value"]), CommandLine)
|
|
| mv-apply KeyValuePairs on (
|
|
summarize CommandLineParsed = make_bag(pack(tostring(KeyValuePairs[0]), KeyValuePairs[1]))
|
|
)
|
|
| extend RuleName = tostring(parse_json(CommandLineParsed).name), Program = tostring(parse_json(CommandLineParsed).program)
|
|
) on RuleName, Program
|
|
| summarize count() , StartTime= min(TimeGenerated), EndTime=max(TimeGenerated) by Type, Computer, Account, SubjectDomainName, SubjectUserName, RuleName, Program, CommandLineParsed = tostring(CommandLineParsed), Process, ParentProcessName
|
|
| extend timestamp = StartTime, AccountCustomEntity = Account, HostCustomEntity = Computer
|
|
),
|
|
(
|
|
DeviceProcessEvents
|
|
| where TimeGenerated >= ago(lookback)
|
|
// remove comment below to adjust for noise
|
|
// | where InitiatingProcessFileName =~ "netsh.exe"
|
|
| where InitiatingProcessCommandLine has_all ("advfirewall", "firewall") and InitiatingProcessCommandLine has_any (tokens)
|
|
| where AccountName !in~ (AccountAllowList)
|
|
| extend KeyValuePairs = extract_all(@'(?P<key>\w+)=(?P<value>[a-zA-Z0-9-\":\\\s$_@()."]+\"|[a-zA-Z0-9-\":$_\\@()."]+)', dynamic(["key","value"]), InitiatingProcessCommandLine)
|
|
| mv-apply KeyValuePairs on (
|
|
summarize CommandLineParsed = make_bag(pack(tostring(KeyValuePairs[0]), KeyValuePairs[1]))
|
|
)
|
|
| extend RuleName = tostring(parse_json(CommandLineParsed).name), Program = tostring(parse_json(CommandLineParsed).program)
|
|
| join kind=leftanti (
|
|
DeviceProcessEvents
|
|
| where TimeGenerated between (starttime..endtime)
|
|
// remove comment below to adjust for noise
|
|
// | where InitiatingProcessFileName =~ "netsh.exe"
|
|
| where InitiatingProcessCommandLine has_all ("advfirewall", "firewall") and InitiatingProcessCommandLine has_any (tokens)
|
|
| where AccountName !in~ (AccountAllowList)
|
|
| extend KeyValuePairs = extract_all(@'(?P<key>\w+)=(?P<value>[a-zA-Z0-9-\":\\\s$_@()."]+\"|[a-zA-Z0-9-\":$_\\@()."]+)', dynamic(["key","value"]), InitiatingProcessCommandLine)
|
|
| mv-apply KeyValuePairs on (
|
|
summarize CommandLineParsed = make_bag(pack(tostring(KeyValuePairs[0]), KeyValuePairs[1]))
|
|
)
|
|
| extend RuleName = tostring(parse_json(CommandLineParsed).name), Program = tostring(parse_json(CommandLineParsed).program)
|
|
) on RuleName, Program
|
|
| summarize count() , StartTime= min(TimeGenerated), EndTime=max(TimeGenerated) by Type, DeviceName, AccountName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, RuleName, Program, CommandLineParsed = tostring(CommandLineParsed), InitiatingProcessFileName, InitiatingProcessParentFileName
|
|
| extend timestamp = StartTime, AccountCustomEntity = InitiatingProcessAccountName, HostCustomEntity = DeviceName
|
|
),
|
|
(
|
|
Event
|
|
| where TimeGenerated > ago(lookback)
|
|
| where Source == "Microsoft-Windows-Sysmon"
|
|
| where EventID == 1
|
|
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
|
|
| mv-expand bagexpansion=array EventData
|
|
| evaluate bag_unpack(EventData)
|
|
| extend Key=tostring(['@Name']), Value=['#text']
|
|
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
|
|
// remove comment below to adjust for noise
|
|
// | where OriginalFileName =~ "netsh.exe"
|
|
| where CommandLine has_all ("advfirewall", "firewall") and CommandLine has_any (tokens)
|
|
| where User !in~ (AccountAllowList)
|
|
| extend KeyValuePairs = extract_all(@'(?P<key>\w+)=(?P<value>[a-zA-Z0-9-\":\\\s$_@()."]+\"|[a-zA-Z0-9-\":$_\\@()."]+)', dynamic(["key","value"]), CommandLine)
|
|
| mv-apply KeyValuePairs on (
|
|
summarize CommandLineParsed = make_bag(pack(tostring(KeyValuePairs[0]), KeyValuePairs[1]))
|
|
)
|
|
| extend RuleName = tostring(parse_json(CommandLineParsed).name), Program = tostring(parse_json(CommandLineParsed).program)
|
|
| join kind=leftanti (
|
|
Event
|
|
| where TimeGenerated > ago(lookback)
|
|
| where Source == "Microsoft-Windows-Sysmon"
|
|
| where EventID == 1
|
|
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
|
|
| mv-expand bagexpansion=array EventData
|
|
| evaluate bag_unpack(EventData)
|
|
| extend Key=tostring(['@Name']), Value=['#text']
|
|
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
|
|
// remove comment below to adjust for noise
|
|
// | where OriginalFileName =~ "netsh.exe"
|
|
| where CommandLine has_all ("advfirewall", "firewall") and CommandLine has_any (tokens)
|
|
| where User !in~ (AccountAllowList)
|
|
| extend KeyValuePairs = extract_all(@'(?P<key>\w+)=(?P<value>[a-zA-Z0-9-\":\\\s$_@()."]+\"|[a-zA-Z0-9-\":$_\\@()."]+)', dynamic(["key","value"]), CommandLine)
|
|
| mv-apply KeyValuePairs on (
|
|
summarize CommandLineParsed = make_bag(pack(tostring(KeyValuePairs[0]), KeyValuePairs[1]))
|
|
)
|
|
| extend RuleName = tostring(parse_json(CommandLineParsed).name), Program = tostring(parse_json(CommandLineParsed).program)
|
|
) on RuleName, Program
|
|
| extend Type = strcat(Type, ": ", Source)
|
|
| summarize count() , StartTime= min(TimeGenerated), EndTime=max(TimeGenerated) by Type, Computer, User, Process, RuleName, Program, CommandLineParsed = tostring(CommandLineParsed), ParentImage
|
|
| extend timestamp = StartTime, AccountCustomEntity = User, HostCustomEntity = Computer
|
|
)
|
|
)
|
|
entityMappings:
|
|
- entityType: Account
|
|
fieldMappings:
|
|
- identifier: FullName
|
|
columnName: AccountCustomEntity
|
|
- entityType: Host
|
|
fieldMappings:
|
|
- identifier: FullName
|
|
columnName: HostCustomEntity |