Detection rules › Kusto
Hunt for public facing devices and exposed ports over time
Find public facing devices over time via the public device tag in the DeviceInfo table.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
References
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceInfo action any: Device information |
Rule body
// Create a base function
let base = (){
DeviceInfo
| where Timestamp > ago(30d)
| extend AdditionalFields = todynamic(AdditionalFields)
| extend InternetFacingLastSeen = todatetime(AdditionalFields.InternetFacingLastSeen)
, InternetFacingReason = tostring(AdditionalFields.InternetFacingReason)
, InternetFacingLocalIp = tostring(AdditionalFields.InternetFacingLocalIp)
, InternetFacingPublicScannedIp = tostring(AdditionalFields.InternetFacingPublicScannedIp)
, InternetFacingLocalPort = tostring(AdditionalFields.InternetFacingLocalPort)
, InternetFacingPublicScannedPort = tostring(AdditionalFields.InternetFacingPublicScannedPort)
, InternetFacingTransportProtocol = tostring(AdditionalFields.InternetFacingTransportProtocol)
};
base()
// Get the latest resport
| summarize arg_max(InternetFacingLastSeen, *) by DeviceName, InternetFacingLocalIp, InternetFacingLocalPort, InternetFacingTransportProtocol
// Join with the earliest report
| join kind=inner ( base()
| summarize arg_min(InternetFacingLastSeen, *) by DeviceName, InternetFacingLocalIp, InternetFacingLocalPort, InternetFacingTransportProtocol
) on DeviceName, InternetFacingLocalIp, InternetFacingLocalPort, InternetFacingTransportProtocol
// Make a data point for each day between earliest and latest report
| extend Range = range(bin(InternetFacingLastSeen1, 1d), bin(InternetFacingLastSeen, 1d), 1d)
// Now expand all datapoints for dates the ports have been active
| mv-expand Range
| where Range != ""
| summarize count() by InternetFacingLocalPort, bin(todatetime(Range), 1d)
| render linechart
Stages and Predicates
let base is inlined into the numbered stages below.
Stage 0: let
let base = DeviceInfo <inlined as stages below>;
Stages 1 to 4 define let base (the rule's main pipeline source); stages 5 to 11 run on it.
Stage 1: source
DeviceInfo
Stage 2: where
| where Timestamp > ago(30d)
Stage 3: extend
| extend AdditionalFields = todynamic(AdditionalFields)
Stage 4: extend
| extend InternetFacingLastSeen = todatetime(AdditionalFields.InternetFacingLastSeen)
, InternetFacingReason = tostring(AdditionalFields.InternetFacingReason)
, InternetFacingLocalIp = tostring(AdditionalFields.InternetFacingLocalIp)
, InternetFacingPublicScannedIp = tostring(AdditionalFields.InternetFacingPublicScannedIp)
, InternetFacingLocalPort = tostring(AdditionalFields.InternetFacingLocalPort)
, InternetFacingPublicScannedPort = tostring(AdditionalFields.InternetFacingPublicScannedPort)
, InternetFacingTransportProtocol = tostring(AdditionalFields.InternetFacingTransportProtocol)
Stage 5: summarize
base
| summarize arg_max(InternetFacingLastSeen, *) by DeviceName, InternetFacingLocalIp, InternetFacingLocalPort, InternetFacingTransportProtocol
Stage 6: join
| join kind=inner ( base()
| summarize arg_min(InternetFacingLastSeen, *) by DeviceName, InternetFacingLocalIp, InternetFacingLocalPort, InternetFacingTransportProtocol
) on DeviceName, InternetFacingLocalIp, InternetFacingLocalPort, InternetFacingTransportProtocol
Stage 7: extend
| extend Range = range(bin(InternetFacingLastSeen1, 1d), bin(InternetFacingLastSeen, 1d), 1d)
Stage 8: mv-expand
| mv-expand Range
Stage 9: where
| where Range != ""
Stage 10: summarize
| summarize count() by InternetFacingLocalPort, bin(todatetime(Range), 1d)
Stage 11: render
| render linechart
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InternetFacingLocalPort | summarize |