Detection rules › Kusto
Server Network Connection Anomalies
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.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RemoteIPType | in | Loopback, Private | excludes:RemoteIPType field:"RemoteIPType" value:"Loopback" field:"RemoteIPType" value:"Private" |
RemoteIP | starts_with | 169.254. | excludes:RemoteIP field:"RemoteIP" value:"169.254." |
set_RemoteUrl | match | .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 | excludes:set_RemoteUrl |
DestinationIp | regex_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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ConnectionSuccess" |
Computer | in |
| field:"Computer" kind:in |
DeviceName | in |
| field:"SourceHostname" kind:in |
EventID | eq |
| field:"EventID" kind:eq value:"3" |
count_ | lt |
| field:"count_" kind:lt value:"50" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
RemoteIP | summarize |