Detection rules › Kusto
Suspicious Network Connections - Supply Chain Attack
Below query detects unusual network conenctions from servers that have 3rd party software installed.
You can further improve the query by using a list of servers that have privileges across the whole domain.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Command & Control | No specific technique |
Telemetry coverage
Rule body
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Query parameters:
let lookback = 14d;
// Generate list of all Servers
let server_list =
DeviceInfo
| where Timestamp > ago(lookback)
| where isnotempty(OSPlatform)
| where DeviceType <> "Workstation" and OSPlatform <> "macOS"
| summarize make_set(DeviceName)
;
// Generate list of servers that have 3rd party software installed.
// Criteria: if a software is installed on less than 10 servers, it's probably a 3rd party software.
// There are probably(hopefully) just a few servers or server groups that have privileges across the whole domain.
// You can change the threshold according to your environment.
let ServersWithThirdPartyApps = materialize (
DeviceNetworkEvents
| where Timestamp > ago(lookback)
| where DeviceName in (server_list)
| where ActionType <> "ListeningConnectionCreated"
| where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
| summarize dcount(DeviceName) by InitiatingProcessVersionInfoCompanyName
| where dcount_DeviceName < 10
| join kind=inner (
DeviceNetworkEvents
| where Timestamp > ago(lookback)
| where DeviceName in (server_list)
| where ActionType <> "ListeningConnectionCreated"
| where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
)
on InitiatingProcessVersionInfoCompanyName
| summarize make_set(DeviceName)
)
;
// Get network connection statistics
let baseline = materialize (
DeviceNetworkEvents
| where Timestamp > ago(lookback)
| where DeviceName in (ServersWithThirdPartyApps)
| where ActionType <> "ListeningConnectionCreated"
| where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
| summarize hint.strategy=shuffle Count=count(), starttime = min(Timestamp), endtime = max(Timestamp) by DeviceName, RemoteIP, RemotePort
)
;
// Get destination IP
let Destinations = baseline | summarize make_set(RemoteIP);
// Filter connections that was not seen before last 1d
// Generate prevalence info, URL info(if available) and enrich results
// Filter based on prevalence and URL information and display everything by hostname
baseline
| where starttime > ago(1d)
| lookup kind=leftouter (
DeviceNetworkEvents
| where Timestamp > ago(5d)
| where RemoteIP in (Destinations)
| summarize hint.strategy=shuffle Prevalence = dcount(DeviceId), URLs=make_set(RemoteUrl) by RemoteIP
)
on RemoteIP
| where Prevalence < 6 or isempty( Prevalence)
// If you want to see all the events in distinct rows, remove the below 2 lines
// filter out results based on trusted URLs if you like.
| extend Details = pack('RemoteIP',RemoteIP, 'RemotePort',RemotePort, 'Count',Count, 'Prevalence',Prevalence, 'URLs',URLs)
| summarize make_set(Details) by DeviceName
Stages and Predicates
Parameters
let lookback = 14d;
let baseline is inlined into the numbered stages below.
Let binding: server_list
let server_list = DeviceInfo
| where Timestamp > ago(lookback)
| where isnotempty(OSPlatform)
| where DeviceType <> "Workstation" and OSPlatform <> "macOS"
| summarize make_set(DeviceName);
Let binding: ServersWithThirdPartyApps
let ServersWithThirdPartyApps = materialize (
DeviceNetworkEvents
| where Timestamp > ago(lookback)
| where DeviceName in (server_list)
| where ActionType <> "ListeningConnectionCreated"
| where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
| summarize dcount(DeviceName) by InitiatingProcessVersionInfoCompanyName
| where dcount_DeviceName < 10
| join kind=inner (
DeviceNetworkEvents
| where Timestamp > ago(lookback)
| where DeviceName in (server_list)
| where ActionType <> "ListeningConnectionCreated"
| where RemoteIPType !in ("Private","Loopback") and (not (RemoteIPType == "FourToSixMapping" and RemoteIP startswith "::ffff:"))
)
on InitiatingProcessVersionInfoCompanyName
| summarize make_set(DeviceName)
);
Let binding: Destinations
let Destinations = baseline | summarize make_set(RemoteIP);
Stage 1: source
let server_list
Stage 2: source
let ServersWithThirdPartyApps
Stage 3: source
let baseline
Stage 4: source
let Destinations
Stage 5: source
DeviceNetworkEvents
Stage 6: where
where Timestamp > ago(1209600s)
Stage 7: where
where DeviceName =~ "ServersWithThirdPartyApps"
Stage 8: where
where ActionType !~ "ListeningConnectionCreated"
Stage 9: where
where not ((RemoteIP startswith "::ffff:" and RemoteIPType =~ "FourToSixMapping")) and not (RemoteIPType in~ ("Loopback", "Private"))
Stage 10: summarize
summarize Count, endtime, starttime by DeviceName, RemoteIP, RemotePort
Stage 11: where
where starttime > ago(86400s)
Stage 12: kusto:lookup
lookup kind=leftouter (DeviceNetworkEvents) on RemoteIP
Stage 13: where
where (isempty(Prevalence) or Prevalence < 6)
Stage 14: extend
extend Details
Stage 15: summarize
summarize by DeviceName
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RemoteIP | starts_with | ::ffff: | excludes:RemoteIP field:"RemoteIP" value:"::ffff:" |
RemoteIPType | eq | FourToSixMapping | excludes:RemoteIPType field:"RemoteIPType" value:"FourToSixMapping" |
RemoteIPType | in | Loopback, Private | excludes:RemoteIPType field:"RemoteIPType" value:"Loopback" field:"RemoteIPType" value:"Private" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | ne |
| field:"ActionType" kind:ne value:"ListeningConnectionCreated" |
DeviceName | in |
| field:"SourceHostname" kind:in value:"ServersWithThirdPartyApps" |
Prevalence | is_null | field:"Prevalence" kind:is_null | |
Prevalence | lt |
| field:"Prevalence" kind:lt value:"6" |
RemoteIP | in |
| field:"DestinationIp" kind:in value:"Destinations" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceName | summarize |