Detection rules › Kusto

Server Network Connection Anomalies

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

Servers have a specific baseline. This makes it easy to create a baseline and detect anomalies.
Below queries analyze the network connections made by the specified servers and detects the rare/anomalous ones.
You can add process info to the analysis, but it will probably generate more results(different processes for the same IP).

Telemetry coverage

Rule body

// Define servers you want to monitor. 
let Servers = dynamic(["server1","server2","etc."]);
// Get rare connections by RemoteIP and InitiatingProcessFileName
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where DeviceName in (Servers) and ActionType == "ConnectionSuccess"
| where RemoteIPType !in ( "Private", "Loopback" )
| where RemoteIP !startswith "169.254."
| summarize make_set(RemoteUrl), count() by RemoteIP
| where count_ < 50
// Exclude traffic to known destinations.
| where not ( set_RemoteUrl has_any (".microsoft.com",".windowsupdate.com","login.microsoftonline.com","login.live.com","autodiscover-s.outlook.com","ocsp.digicert.com","ocsp.verisign.com","login.windows.net", "outlook.office365.com","accounts.accesscontrol.windows.net"))
// Get details of the connections that were made in the last 5 days.
// If you are going to check the results everyday, change the threshold to 1d. 
| join kind=inner
    (
    DeviceNetworkEvents
    | where Timestamp > ago(5d)
    | where DeviceName in (Servers) and ActionType == "ConnectionSuccess"
    | where RemoteIPType !in ( "Private", "Loopback" )
    | where RemoteIP !startswith "169.254."
    ) on RemoteIP

// Define servers you want to monitor. 
let Servers = dynamic(["server1","server2","etc."]);
// Get rare connections by DestinationIp
let _lookback = 30d;
let _timeframe = 5d;
let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.';
// Parse Sysmon network connections and get only the ones towards the internet.
let parse_sysmon_id3 = (T:(TimeGenerated:datetime,EventID:int, Source:string,RenderedDescription:string, EventData:string))
{
T 
| where TimeGenerated > ago(_lookback)
| where Source == "Microsoft-Windows-Sysmon" and EventID == 3
| extend RenderedDescription = tostring(split(RenderedDescription, ":")[0])
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key=tostring(['@Name']), Value=tostring(['#text'])
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
| extend RuleName = column_ifexists("RuleName", ""), TechniqueId = column_ifexists("TechniqueId", ""),  TechniqueName = column_ifexists("TechniqueName", "")
| parse RuleName with * 'technique_id=' TechniqueId ',' * 'technique_name=' TechniqueName
// Filter connections towards the internet
| where not (DestinationIp matches regex PrivateIPregex)
};
// Get rare connections by DestinationIp
Event
| where TimeGenerated > ago(_lookback)
| where Computer in (Servers)
| invoke parse_sysmon_id3()
| summarize count() by DestinationIp
| where count_ < 50
// get details of the rare connections for further analysis.
| join kind=inner
    (
    Event
    | where TimeGenerated > ago(_timeframe)
    | where Computer in (Servers)
    | invoke parse_sysmon_id3()
    ) on DestinationIp

Stages and Predicates

Parameters

let Servers = dynamic(["server1","server2","etc."]);

Stage 1: source

DeviceNetworkEvents

Stage 2: where

where Timestamp > ago(2592000s)

Stage 3: where

where ActionType =~ "ConnectionSuccess" and DeviceName in~ ("etc.", "server1", "server2")

Stage 4: where

where not (RemoteIPType in~ ("Loopback", "Private"))

Stage 5: where

where not (RemoteIP startswith "169.254.")

Stage 6: summarize

summarize by RemoteIP

Stage 7: where

where count_ < 50

Stage 8: where

where not ((set_RemoteUrl contains ".microsoft.com" or set_RemoteUrl contains ".windowsupdate.com" or set_RemoteUrl contains "login.microsoftonline.com" or set_RemoteUrl contains "login.live.com" or set_RemoteUrl contains "autodiscover-s.outlook.com" or set_RemoteUrl contains "ocsp.digicert.com" or set_RemoteUrl contains "ocsp.verisign.com" or set_RemoteUrl contains "login.windows.net" or set_RemoteUrl contains "outlook.office365.com" or set_RemoteUrl contains "accounts.accesscontrol.windows.net"))

Stage 9: join

join kind=inner (DeviceNetworkEvents) on RemoteIP

Stage 10: source

Event

Stage 11: where

where TimeGenerated > ago(2592000s)

Stage 12: where

where Computer in~ ("etc.", "server1", "server2")

Stage 13: where

where TimeGenerated > ago(2592000s)

Stage 14: where

where EventID == 3 and Source =~ "Microsoft-Windows-Sysmon"

Stage 15: extend

extend RenderedDescription

Stage 16: extend

extend EventData

Stage 17: mv-expand

mv-expand EventData

Stage 18: evaluate

evaluate

Stage 19: extend

extend Key, Value

Stage 20: evaluate

evaluate

Stage 21: extend

extend RuleName, TechniqueId, TechniqueName

Stage 22: parse

parse

Stage 23: where

where not (DestinationIp matches regex @"^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.")

Stage 24: summarize

summarize by DestinationIp

Stage 25: where

where count_ < 50

Stage 26: join

join kind=inner (Event) on DestinationIp

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
RemoteIPTypeinLoopback, Privateexcludes:RemoteIPType field:"RemoteIPType" value:"Loopback" field:"RemoteIPType" value:"Private"
RemoteIPstarts_with169.254.excludes:RemoteIP field:"RemoteIP" value:"169.254."
set_RemoteUrlmatch.microsoft.com, .windowsupdate.com, login.microsoftonline.com, login.live.com, autodiscover-s.outlook.com, ocsp.digicert.com, ocsp.verisign.com, login.windows.net, outlook.office365.com, accounts.accesscontrol.windows.netexcludes:set_RemoteUrl
DestinationIpregex_match^127., ^10., ^172.1[6-9]., ^172.2[0-9]., ^172.3[0-1]., ^192.168.excludes:DestinationIp

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • ConnectionSuccess corpus 11 (kusto 11)
field:"ActionType" kind:eq value:"ConnectionSuccess"
Computerin
  • etc.
  • server1
  • server2
field:"Computer" kind:in
DeviceNamein
  • etc.
  • server1
  • server2
field:"SourceHostname" kind:in
EventIDeq
  • 3 corpus 24 (splunk 14, kusto 8, chronicle 2)
field:"EventID" kind:eq value:"3"
count_lt
  • 50
field:"count_" kind:lt value:"50"

Output fields

These fields are emitted when the rule matches.

FieldSource
RemoteIPsummarize