Measuring days vulnerability remained open before remediation

Trying to figure out the best way of getting the first_found_date of a vulnerability into the fact_asset_vulnerability_remediation_date table. I have a work-around for measuring vulnerability_age by using either the published_date or modified_date from the dim_vulnerability table, but would really like to use the date field from the fact_asset_vulnerability_finding table. I think the issue comes down to the many-to-many relationship between the fact_finding and fact_remediation tables. Is there anyway to reduce the fact_asset_vulnerability_finding table to a single row for each vulnerability/asset pair?

This is one approach I took, however it is very slow, especially when trying to include data from more than a few months. Joining the 2 fact tables on asset_id and vulnerability_id keys is much faster, but introduces many duplicates.

WITH 
a AS(
    SELECT  day, dat.asset_id, favf.vulnerability_id, favf.date "first_found_date",
    CONCAT(dat.asset_id, '-', favf.vulnerability_id) AS "unique_key"
    FROM dim_asset_tag dat
    JOIN fact_asset_vulnerability_finding_date favf USING(asset_id)
    WHERE tag_id IN (1447) AND day > '1-1-21'
    ),
    
b AS (
    SELECT favrd.day AS remediation_date, favrd.asset_id, favrd.vulnerability_id, CONCAT(dat.asset_id, '-', favrd.vulnerability_id) AS "unique_key"
    FROM dim_asset_tag dat
    JOIN fact_asset_vulnerability_remediation_date favrd USING(asset_id)
    WHERE tag_id IN (1447) AND day > '1-1-21'
    ),

c AS ( -- cannot use age function on hist tables, use DATE_PART
    SELECT b.*, a.first_found_date, DATE_PART('day', remediation_date::timestamp - first_found_date::timestamp) AS "vulnerability_age"
    FROM b b
    LEFT JOIN a a USING(unique_key)
    ),

d AS (
    SELECT c.*, da.os_system, dv.risk_score, dv.nexpose_id, dv.title, dv.cvss_score, dv.cvss_v3_score,
    CASE
    WHEN dv.cvss_v3_score IS NOT NULL THEN 
        CASE WHEN cvss_v3_score >= 9 OR dv.nexpose_id = 'tlsv1_0-enabled' THEN 'Critical'
             WHEN cvss_v3_score >= 7 AND cvss_v3_score < 9 THEN 'High'
             WHEN cvss_v3_score >= 4 AND cvss_v3_score < 7 THEN 'Medium'
             ELSE 'Low'
        END
    WHEN dv.cvss_v3_score IS NULL THEN
        CASE WHEN cvss_score >= 9 OR dv.nexpose_id = 'tlsv1_0-enabled' THEN 'Critical'
             WHEN cvss_score >= 7 AND cvss_score < 9 THEN 'High'
             WHEN cvss_score >= 4 AND cvss_score < 7 THEN 'Medium'
             ELSE 'Low'
        END
    END as "dlx_severity",
    CASE 
        WHEN dv.risk_score >= 900 OR dv.nexpose_id = 'tlsv1_0-enabled' OR (dvc.category_name = 'Rapid7 Critical' AND dv.nexpose_id NOT LIKE '%sweet32%') THEN 'Critical'
        WHEN dv.risk_score >= 700 AND dv.risk_score < 900 THEN 'High'
        WHEN dv.risk_score >= 400 AND dv.risk_score < 700 THEN 'Medium'
        ELSE 'Low'
    END AS "dlx_risk_based_severity"
    FROM c c
    JOIN dim_vulnerability dv USING(vulnerability_id)
    JOIN dim_vulnerability_category dvc USING(vulnerability_id)
    JOIN dim_asset da USING(asset_id)
    ),
    
e AS (
    SELECT d.*,
    CASE
        WHEN dlx_severity = 'Medium' AND os_system = 'CentOS Linux' THEN 120
        WHEN dlx_severity = 'High' AND os_system = 'CentOS Linux' THEN 90 
        WHEN dlx_severity = 'Critical' AND os_system = 'CentOS Linux' THEN 60
        WHEN dlx_severity = 'Medium' THEN 90 
        WHEN dlx_severity = 'High' THEN 60 
        WHEN dlx_severity = 'Critical' THEN 30 
        ELSE NULL
    END as "sla_target",
    CASE
        WHEN dlx_risk_based_severity = 'Medium' AND os_system = 'CentOS Linux' THEN 120
        WHEN dlx_risk_based_severity = 'High' AND os_system = 'CentOS Linux' THEN 90 
        WHEN dlx_risk_based_severity = 'Critical' AND os_system = 'CentOS Linux' THEN 60
        WHEN dlx_risk_based_severity = 'Medium' THEN 90 
        WHEN dlx_risk_based_severity = 'High' THEN 60 
        WHEN dlx_risk_based_severity = 'Critical' THEN 30 
        ELSE NULL
    END as "sla_target_risk_based"
    FROM d d
    )
SELECT DISTINCT unique_key, remediation_date, asset_id, vulnerability_id, first_found_date, 
vulnerability_age, os_system, nexpose_id, title, risk_score, cvss_score, cvss_v3_score, dlx_severity, dlx_risk_based_severity, sla_target, sla_target_risk_based,
CASE
    WHEN vulnerability_age <= sla_target THEN 'Within SLA'
    WHEN vulnerability_age > sla_target THEN 'Past SLA'
    ELSE 'No SLA'
END AS "dlx_sla",
CASE
    WHEN vulnerability_age <= sla_target_risk_based THEN 'Within SLA'
    WHEN vulnerability_age > sla_target_risk_based THEN 'Past SLA'
    ELSE 'No SLA'
END AS "dlx_risk_based_sla"
FROM e

what is a “dim_asset_tag” ?

I am on the same boat. Tried using this, but it resulted an error. See below.

Error:relation “dim_asset_tag” does not exist
Character:175

This is a query for the data warehouse, I probably should have mentioned that. dim_asset_tag is a table that provides the link between a tag_id and an asset_id. Based on your error, you are trying to run this query in the console, which will not work. The table that is similar to the warehouse dim_asset_tag is dim_tag_asset in the console.

Thanks for the info Trevor. Yes, I was trying to run this on console>report>sqlqueryexport and it didn’t work.

Update:
I think I’m close, just not sure if the 2nd CTE actually returns a single row for each asset/vulnerability pair. I’m also not sure how/if an authentication failure during a scan that marks vulnerabilities as “remediated” and a subsequent scan then “re-opens” the vulnerabilities is being handled appropriately.

WITH
remediation AS (
	SELECT *
	FROM fact_asset_vulnerability_remediation_date
	WHERE day > '5-1-21'
),

findings AS ( --return a single row for each asset/vulnerability pair?
	SELECT DISTINCT ON (asset_id, vulnerability_id) asset_id, vulnerability_id, MAX(day) "day", date
	FROM fact_asset_vulnerability_finding_date
	WHERE day > '5-1-21'
	GROUP BY asset_id, vulnerability_id, date
)


SELECT r.asset_id, r.vulnerability_id, r.day "remediation_date", 
f.date "first_found_date", DATE_PART('days', r.day - f.date) "age"
FROM remediation r
JOIN findings f USING(asset_id, vulnerability_id)