Detection rules › Kusto

Hunt for public remotly exploitable devices (with high EPSS)

Group by
CveId, DeviceId, DeviceName
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This query searches for devices that comply with the following criteria: - Incomming connections from public IP addresses in last 7 days (internet exposed) - High or Critical severity CVE's - CVE's must have known exploits - CVE's are remotely exploitable over the network - No user interaction is required to exploit the CVE's - EPSS score of CVE must by above 10% (likelihood of exploitation) > If devices are placed behind a proxy, they will not be returned in this query by default

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Telemetry coverage

Rule body

// Flag remotly exploitable, no user interaction, CVE's with a EPSS score above a certain threshold (likelyhood of exploitation)
// For devices with incomming public connections
// See efficiency research on https://www.first.org/epss/model
let epss_threshold = 0.1;
let exploit_statusses = dynamic(["ExploitIsPublic","ExploitIsInKit","ExploitIsVerified"]);
// Xspm base query we materialize since we need these results multiple times
let xspm_base = materialize (
    ExposureGraphNodes
    // Get device nodes with their inventory ID
    | mv-expand EntityIds
    | where EntityIds.type == "DeviceInventoryId"
    // Get first important properties
    | extend DeviceId = tostring(parse_json(EntityIds)["id"]),
        ExposureScore = tostring(parse_json(NodeProperties)["rawData"]["exposureScore"]),
        HasHighOrCriticalCve = tostring(parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["hasHighOrCritical"])
    // Focus on devices with high exposure
    | where ExposureScore == "High"
    // Get vulnerability exploit information
    | extend RceExploitLevels = parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["vulnerableToRemoteCodeExecution"]["explotabilityLevels"]
    | extend PrivEscExploitLevels = parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["vulnerableToPrivilegeEscalation"]["explotabilityLevels"]
    // Focus on devices where cve has known epxloits
    | where RceExploitLevels has_any (exploit_statusses) or PrivEscExploitLevels has_any (exploit_statusses)
    // Focus on devices that are public exposed
    | join kind=inner (
        DeviceNetworkEvents
        | where TimeGenerated > ago(7d)
        | where ActionType contains "InboundConnection"
        | where RemoteIPType == "Public"
        // Exclude MacOS Rapportd and ControlCenter
        | where InitiatingProcessFileName != "rapportd" and InitiatingProcessFileName != "controlcenter"
        | distinct DeviceName, DeviceId, LocalPort, InitiatingProcessFolderPath, InitiatingProcessVersionInfoProductName, InitiatingProcessFileName
    ) on $left.DeviceId == $right.DeviceId
    // Save all the open ports and their process in a JSON
    | extend OpenPortJson = bag_pack_columns(LocalPort, InitiatingProcessFolderPath, InitiatingProcessFileName)
    // Save open ports by Device ID
    | summarize PublicOpenPortList = make_set(OpenPortJson) by DeviceId
);
// Save flagged device IDs in list to limit results of CVE's we need to search later
let flagged_devices = toscalar(
    xspm_base
    | summarize make_set(DeviceId)
);
// CVE base query we materialize since we need these results multiple times
let cve_base = materialize (
    DeviceTvmSoftwareVulnerabilities
    | where VulnerabilitySeverityLevel in ("High", "Critical")
    | where DeviceId in ( flagged_devices )
);
// Save flagged CVE IDs in list to limit results of CVE database we need to search later
let flagged_cves = toscalar(
    cve_base
    | summarize make_set(CveId)
);
// Query the CVE's of the flagged devices
cve_base
// Enrich the CVE data with their EPSS and CVSS Score
| join kind=inner (
    DeviceTvmSoftwareVulnerabilitiesKB
    // Focus on flagged CVE's
    | where CveId in ( flagged_cves )
    // Focus on CVE's tagged with Attack Vector being over the Network
    // 'Vulnerabilities with this rating are remotely exploitable, from one or more hops away, up to and including remote exploitation over the Internet.'
    // 'Does not require user interaction'
    | where CvssVector contains "/AV:N" and CvssVector contains "/UI:N"
    // Focus on CVE's where an exploit is available
    | where IsExploitAvailable != 0
    | distinct CveId, EpssScore, CvssScore, CvssVector, IsExploitAvailable, AffectedSoftwareList=tostring(AffectedSoftware)
) on CveId
// Continue with only relevant data
| project DeviceId, DeviceName, OSPlatform, OSVersion, OSArchitecture, SoftwareName, SoftwareVendor, SoftwareVersion, CveId, VulnerabilitySeverityLevel, EpssScore, CvssScore, CvssVector, IsExploitAvailable, AffectedSoftwareList
// Now flag CVE's with a EPSS score above a certain threshold
// See efficiency research on https://www.first.org/epss/model
| where EpssScore >= epss_threshold
// Save all the CVE data in a JSON column
| extend CveJson = bag_pack_columns(SoftwareName, SoftwareVendor, SoftwareVersion, CveId, EpssScore, CvssScore, CvssVector, IsExploitAvailable, AffectedSoftwareList)
// Group the CVE data for each device per device
| summarize CveList = make_list(CveJson) by DeviceId, DeviceName
// Add xspm data again
| join kind=inner xspm_base on DeviceId
| project-away DeviceId1
// Sort by CVE amount
| extend CveCount = array_length(CveList)
| sort by CveCount desc

Stages and Predicates

Parameters

let epss_threshold = 0.1;
let exploit_statusses = dynamic(["ExploitIsPublic","ExploitIsInKit","ExploitIsVerified"]);

let cve_base is inlined into the numbered stages below.

Let binding: xspm_base used in Stages 1, 11

let xspm_base = materialize (
    ExposureGraphNodes
    | mv-expand EntityIds
    | where EntityIds.type == "DeviceInventoryId"
    | extend DeviceId = tostring(parse_json(EntityIds)["id"]),
        ExposureScore = tostring(parse_json(NodeProperties)["rawData"]["exposureScore"]),
        HasHighOrCriticalCve = tostring(parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["hasHighOrCritical"])
    | where ExposureScore == "High"
    | extend RceExploitLevels = parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["vulnerableToRemoteCodeExecution"]["explotabilityLevels"]
    | extend PrivEscExploitLevels = parse_json(NodeProperties)["rawData"]["highRiskVulnerabilityInsights"]["vulnerableToPrivilegeEscalation"]["explotabilityLevels"]
    | where RceExploitLevels has_any (exploit_statusses) or PrivEscExploitLevels has_any (exploit_statusses)
    | join kind=inner (
        DeviceNetworkEvents
        | where TimeGenerated > ago(7d)
        | where ActionType contains "InboundConnection"
        | where RemoteIPType == "Public"
        | where InitiatingProcessFileName != "rapportd" and InitiatingProcessFileName != "controlcenter"
        | distinct DeviceName, DeviceId, LocalPort, InitiatingProcessFolderPath, InitiatingProcessVersionInfoProductName, InitiatingProcessFileName
    ) on $left.DeviceId == $right.DeviceId
    | extend OpenPortJson = bag_pack_columns(LocalPort, InitiatingProcessFolderPath, InitiatingProcessFileName)
    | summarize PublicOpenPortList = make_set(OpenPortJson) by DeviceId
);

Let binding: flagged_devices used in Stage 5

let flagged_devices = toscalar(
    xspm_base
    | summarize make_set(DeviceId)
);

Let binding: flagged_cves

let flagged_cves = toscalar(
    cve_base
    | summarize make_set(CveId)
);

Stage 1: source

let xspm_base

Stage 2: source

let cve_base

Stage 3: source

DeviceTvmSoftwareVulnerabilities

Stage 4: where

where VulnerabilitySeverityLevel in~ ("Critical", "High")

Stage 5: where

where DeviceId =~ "flagged_devices"

Stage 6: join

join kind=inner (DeviceTvmSoftwareVulnerabilitiesKB) on CveId

Stage 7: project

project AffectedSoftwareList, CveId, CvssScore, CvssVector, DeviceId, DeviceName, EpssScore, IsExploitAvailable, OSArchitecture, OSPlatform, OSVersion, SoftwareName, SoftwareVendor, SoftwareVersion, VulnerabilitySeverityLevel

Stage 8: where

where EpssScore >= 0.1

Stage 9: extend

extend CveJson

Stage 10: summarize

summarize CveList by DeviceId, DeviceName

Stage 11: join

join kind=inner (xspm_base) on DeviceId

Stage 12: project-away

project-away DeviceId1

Stage 13: extend

extend CveCount

Stage 14: sort

sort by CveCount

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
CveListsummarize
DeviceIdsummarize
DeviceNamesummarize
CveCountextend