Detection rules › Kusto

Suspicious Network Connections - Supply Chain Attack

Group by
DeviceName, RemoteIP, RemotePort
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

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

TacticTechniques
Command & ControlNo 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 used in Stage 1

let server_list = DeviceInfo
    | where Timestamp > ago(lookback)
    | where isnotempty(OSPlatform)
    | where DeviceType <> "Workstation" and OSPlatform <> "macOS"
    | summarize make_set(DeviceName);

Let binding: ServersWithThirdPartyApps used in Stages 2, 7

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 used in Stage 4

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.

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
DeviceNamesummarize