public/xspm/Test-MtXspmPublicRemotelyExploitableHighExposureDevices.ps1
|
<# .SYNOPSIS Test to find public exposed devices with remotely exploitable, highly likely to be exploited, high or critical severity CVE's .DESCRIPTION Test to find devices that comply to the following: - Incoming connections from public IP addresses in the 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 required to exploit CVE's - EPSS score of CVE must be above 10% (likelihood of exploitation) .OUTPUTS [bool] - Returns $true if no devices are found, $false if any are found, $null if skipped or prerequisites not met. .EXAMPLE Test-MtXspmPublicRemotelyExploitableHighExposureDevices .LINK https://maester.dev/docs/commands/Test-MtXspmPublicRemotelyExploitableHighExposureDevices #> function Test-MtXspmPublicRemotelyExploitableHighExposureDevices { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'This test checks for devices with remotely exploitable, highly likely to be exploited, high or critical severity CVEs')] [OutputType([bool])] param() Write-Verbose "Get raw data from Exposure Management..." $Query = @" // Flag remotely exploitable, no user interaction, CVE's with a EPSS score above a certain threshold (likelihood of exploitation) // For devices with incoming 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 exploits | 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 open ports by Device ID | summarize PublicOpenPortList = make_set(LocalPort) 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 | summarize MaxEpssScore = max(EpssScore), MaxCvssScore = max(CvssScore), CveList = make_set(CveId) by DeviceId, DeviceName // Add xspm data again | join kind=inner xspm_base on DeviceId // Sort and remove data | extend CveCount = array_length(CveList) | sort by CveCount desc | project-away DeviceId1, DeviceId, CveCount "@ $Devices = Invoke-MtGraphSecurityQuery -Query $Query -Timespan "P1D" $Severity = "High" if ($return -or [string]::IsNullOrEmpty($Devices)) { $testResultMarkdown = "Well done. No public exposed devices with high or critical CVE's and high changes of exploitation were found." } else { $testResultMarkdown = "At least one public exposed device with high or critical CVE's and high changes of exploitation was found.`n`n%TestResult%" Write-Verbose "Found $($Devices.Count) public exposed devices with high or critical CVE's and high changes of exploitation." $result = "| DeviceName | MaxEpssScore | MaxCvssScore | CveList | PublicOpenPortList | `n" $result += "| --- | --- | --- | --- | --- |`n" foreach ($Device in $Devices) { $CveList = $($Device.CveList) -join ', ' # "user1, user2, user3" $PublicOpenPortList = $($Device.PublicOpenPortList) -join ', ' # "user1, user2, user3" $result += "| $($Device.DeviceName) | $($Device.MaxEpssScore) | $($Device.MaxCvssScore) | $($CveList) | $($PublicOpenPortList) |`n" } } $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result Add-MtTestResultDetail -Result $testResultMarkdown -Severity $Severity $result = [string]::IsNullOrEmpty($Devices) return $result } |