One of the most compelling use cases for Surface Command is to provide tool-agnostic Asset Risk Assessments by computing asset risk scores that are independent of the data sources. This way, even as the your selection of VM tools changes, the asset risk score basis remains stable. Additionally, you can fine tune the computation to meet our organization’s specific needs.
EXAMPLE A
The following query takes into account some common factors, such as:
- CVSS score
- EPSS number
- Vulnerability age
- Public exposure
Most customers will have an opinion about how they want to compute asset risk, so the following query could serve as the starting point, and then be tuned as needed.
// ===================================================
// TITLE: Rapid7 Surface Command Asset Risk Assessment
// ===================================================
// Identify "active" devices seen in the past 30 days with exploitable vulns
MATCH (c:CisaKnownExploitedVulnerability)<--(v:Vulnerability)<--(a:Machine)
WHERE SINCE(TOP(a.endpoint_last_seen), "DAYS") <= 30
// Pass on just what we need
WITH DISTINCT
a,
a.public AS IsPublic,
c.`samos:id` AS CID,
c.cveID AS CVE,
COALESCE(v.cvss, 0.0) AS CvssNumber,
COALESCE(v.`FirstEpssVulnerability:epss_number`, 0.0) AS EpssNumber,
c.pubdate AS PubDate,
c.duedate AS DueDate
// Gather the component numbers for risk score
WITH DISTINCT
a, IsPublic, CVE, PubDate, DueDate,
DURATION(UTCNOW() - PubDate, "DAYS") AS Age,
CASE
WHEN CvssNumber > 0.0 THEN CvssNumber
ELSE 1.0
END AS CvssMultiplier,
CASE
WHEN EpssNumber > 0.0 THEN EpssNumber * 2.0
ELSE 1.0
END AS EpssMultiplier,
CASE
WHEN DURATION(UTCNOW() - PubDate, "DAYS") > 30 THEN 2.0
WHEN DURATION(UTCNOW() - PubDate, "DAYS") > 7 THEN 1.5
ELSE 1.0
END AS AgeMultiplier,
CASE
WHEN DURATION(UTCNOW() - DueDate, "DAYS") > 0 THEN 1.5
ELSE 1.0
END AS PastDueMultiplier,
CASE
WHEN IsPublic THEN 2.0
ELSE 1.0
END AS PublicMultiplier
// Some pre computation, per finding
WITH
a, IsPublic,
CVE,
(CvssMultiplier * EpssMultiplier * AgeMultiplier * PastDueMultiplier) AS BaseScore,
PublicMultiplier
// Compute the score, per machine
WITH
a, IsPublic,
(SUM(BaseScore) * PublicMultiplier) AS RS,
COUNT(CVE) AS NCV
// 2nd pass aggregation !!!
WITH
a, IsPublic,
SUM(RS) AS RiskScore,
SUM(NCV) AS NumCritVulns
WITH
a, IsPublic,
RiskScore,
NumCritVulns
WITH
a, IsPublic,
RiskScore,
NumCritVulns
WITH
a,
RiskScore,
NumCritVulns
RETURN
COALESCE(a.name, a.hostnames[0], "--") AS Name,
ROUND(RiskScore) AS `Risk Score`,
RiskScore AS `RAW Risk Score`,
NumCritVulns AS `# of Crit Vulns`,
CASE
WHEN TOP(a.virtual) = True THEN "True"
WHEN TOP(a.virtual) = False THEN "False"
ELSE "Unknown"
END AS `Virtualized?`,
a
ORDER BY `RAW Risk Score` DESC
One can then put together an Asset Risk Assessment dashboard like this:
EXAMPLE 2
Here’s another example from one of our own Field Engineers. It relies on some some popular VM data sources. This example does make references to:
- Crowdstrike
- ServiceNow
//--------------------------------------------------------
//|Vendor Agnostic Custom Prioritization Score for Assets|
//| Cypher generated by ScoreBuilder.py |
//| Created by Chaney Edwards |
//--------------------------------------------------------
//SELECTED OPTIONS
//Consider Detected Mitigations? 1
//Consider Vulnerability Counts? 1
//Consider Crowdstrike Status? 1
//Consider Public IP Detection? 1
//Consider ServiceNow CI Detection? 1
//BEGIN BUILDING CYPHER
//First we MATCH on the Asset Data Type
//To drop poorly coorelated assets, for demo purposes only, we drop ones without IPs defined
//To drop poorly coorelated assets, for demo purposes only, we drop ones where the only source is LansweeperAsset
MATCH (a:Machine) WHERE (a.ips IS NOT NULL AND NOT 'LansweeperAsset' IN a.sources)
//We then perform an OPTIONAL MATCH to get the ServiceNowMachineCI data in but allow assets not in SNOW
OPTIONAL MATCH (a)-[:`x_rels.sys_id`]-(s:ServiceNowServiceCI)
//Begin building scores by passing MATCH data in
WITH a,s,
//EXAMINE DETECTED MITIGATIONS
//This section will examine all found mitigations on an asset and score according to those found to be missing
//List of Mitigators: Antivirus/Antimalware, Endpoint Protection, Vulnerability Scan
//Detection Weights: 25, 35, 50
CASE
WHEN NOT ANY(x IN EVERY(a.mitigations) WHERE x STARTS WITH 'Antivirus/Antimalware') THEN 25
ELSE 0
END AS AVAM_mitigators_score,
CASE
WHEN NOT ANY(x IN EVERY(a.mitigations) WHERE x STARTS WITH 'Endpoint Protection') THEN 35
ELSE 0
END AS EDR_mitigators_score,
CASE
WHEN NOT ANY(x IN EVERY(a.mitigations) WHERE x STARTS WITH 'Vulnerability Scan') THEN 50
ELSE 0
END AS VM_mitigators_score,
//EXAMINE VULNERABILITY COUNTS
//This section will build thresholds and thier associated weights for asset vulnerability counts
//Vulnerability Thresholds: 100, 250, 500, 1000
//Detection Weights: 5, 15, 25, 35
//Maximum Weight: 50
CASE
WHEN LENGTH(a.vulnerabilities) = 0 THEN 0
WHEN LENGTH(a.vulnerabilities) < 100 THEN 5
WHEN LENGTH(a.vulnerabilities) < 250 THEN 15
WHEN LENGTH(a.vulnerabilities) < 500 THEN 25
WHEN LENGTH(a.vulnerabilities) < 1000 THEN 35
ELSE 50
END AS vulnerabilities_score,
//EXAMINE CROWDSTRIKE STATUS
//This section will assign a weight to each possible Crowdstrike Status
//Detected Status: Normal, Contained, Containment Pending, Lift Containment Pending, OK, UNKNOWN, CHECK, VERIFY
//Detection Weights: 5, 15, 25, 35
CASE
WHEN a.`CrowdstrikeDevice:status` = 'Normal' THEN 0
WHEN a.`CrowdstrikeDevice:status` = 'Contained' THEN 15
WHEN a.`CrowdstrikeDevice:status` = 'Containment Pending' THEN 25
WHEN a.`CrowdstrikeDevice:status` = 'Lift Containment Pending' THEN 10
WHEN a.`CrowdstrikeDevice:status` = 'OK' THEN 0
WHEN a.`CrowdstrikeDevice:status` = 'UNKNOWN' THEN 50
WHEN a.`CrowdstrikeDevice:status` = 'CHECK' THEN 50
WHEN a.`CrowdstrikeDevice:status` = 'VERIFY' THEN 50
ELSE 0
END AS crowdstrike_score,
//EXAMINE PUBLIC IP STATUS
//This section will assign a weight to assets found to have a Public IP
//Detection Weight: 100
CASE
WHEN LENGTH(a.ips) = 0 THEN 50
WHEN LENGTH(a.ips) > 0 AND NOT ANY (x IN EVERY(a.ips) WHERE (x STARTS WITH '10.' OR x STARTS WITH '192.168.' OR (x STARTS WITH '172.' AND toInteger(SPLIT(x, '.')[1]) >= 16 AND toInteger(SPLIT(x, '.')[1]) <= 31))) THEN 100
ELSE 0
END AS pubip_score,
//EXAMINE SNOW CI STATUS
//This section will assign a weight to assets found to have an absent or aged ServiceNow CI
//Date Thresholds: 30, 60, 90
//Detection Weights: 10, 15, 20
//Missing CI Weight: 30
CASE
WHEN s.sys_updated_on IS NULL THEN 30
WHEN SINCE(s.sys_updated_on, 'days') > 30 THEN 10
WHEN SINCE(s.sys_updated_on, 'days') > 60 THEN 15
WHEN SINCE(s.sys_updated_on, 'days') > 90 THEN 20
ELSE 0
END AS snowci_score
//GENERATE RETURN STATEMENT
RETURN
a.name AS `Asset Name`,
a.mitigations AS `Detected Mitigations`,
(AVAM_mitigators_score + EDR_mitigators_score + VM_mitigators_score) AS `Mitigators Score`,
a.vulnerabilities AS `Detected Vulnerabilities`,
vulnerabilities_score AS `Vulnerabilities Score`,
a.`CrowdstrikeDevice:status` AS `Detected CrowdStrike Status`,
crowdstrike_score AS `CrowdStrike Score`,
a.ips AS `Detected IP Addresses`,
pubip_score AS `IP Addresses Score`,
s.sys_updated_on AS `ServiceNOW CI Updated`,
snowci_score AS `ServiceNOW CI Score`,
(AVAM_mitigators_score + EDR_mitigators_score + VM_mitigators_score + vulnerabilities_score + crowdstrike_score + pubip_score + snowci_score) AS `Prioritization Score`