From 27938d0fcc78cad0ecd6d12f8c26db98ec5c92d2 Mon Sep 17 00:00:00 2001 From: rileydakota Date: Wed, 5 Jul 2023 01:04:37 -0400 Subject: [PATCH] Adds CISA KEV managed enrichment table (#162) --- .../enrichment/cisa_kev/enrichment.yml | 41 +++++++++++++ infra/lib/enrichment.ts | 1 + infra/lib/log-puller.ts | 3 + lib/rust/log_puller/src/pullers/cisa_kev.rs | 58 +++++++++++++++++++ lib/rust/log_puller/src/pullers/mod.rs | 6 ++ 5 files changed, 109 insertions(+) create mode 100644 data/managed/enrichment/cisa_kev/enrichment.yml create mode 100644 lib/rust/log_puller/src/pullers/cisa_kev.rs diff --git a/data/managed/enrichment/cisa_kev/enrichment.yml b/data/managed/enrichment/cisa_kev/enrichment.yml new file mode 100644 index 00000000..3f5fae6e --- /dev/null +++ b/data/managed/enrichment/cisa_kev/enrichment.yml @@ -0,0 +1,41 @@ +enrichment_type: dynamic +write_mode: overwrite + +transform: | + .event.kind = "enrichment" + .event.category = ["vulnerability"] + + .vulnerability.category = [del(.json.product), del(.json.vendorProject)] + .vulnerability.classification = "CVSS" + .vulnerability.description = del(.json.shortDescription) + .vulnerability.enumeration = "CVE" + .vulnerability.id = del(.json.cveID) + .cisa_kev.dateAdded = del(.json.dateAdded) + .cisa_kev.requiredAction = del(.json.requiredAction) + .cisa_kev.dueDate = del(.json.dueDate) + .cisa_kev.notes = del(.json.notes) + +schema: + ecs_field_names: + - ecs.version + - event.kind + - event.category + - vulnerability.category + - vulnerability.classification + - vulnerability.description + - vulnerability.enumeration + - vulnerability.id + + fields: + - name: cisa_kev + type: + type: struct + fields: + - name: dateAdded + type: string + - name: requiredAction + type: string + - name: dueDate + type: string + - name: notes + type: string \ No newline at end of file diff --git a/infra/lib/enrichment.ts b/infra/lib/enrichment.ts index 94a5438f..6e2c1d62 100644 --- a/infra/lib/enrichment.ts +++ b/infra/lib/enrichment.ts @@ -49,6 +49,7 @@ const MANAGED_ENRICHMENT_PREFIX_MAP: Record = { "abusech_malwarebazaar": "abusech_malwarebazaar", "abusech_threatfox": "abusech_threatfox", "otx": "otx", + "cisa_kev": "cisa_kev" } export class EnrichmentTable extends Construct { diff --git a/infra/lib/log-puller.ts b/infra/lib/log-puller.ts index 003bec71..31528653 100644 --- a/infra/lib/log-puller.ts +++ b/infra/lib/log-puller.ts @@ -30,6 +30,7 @@ export const PULLER_LOG_SOURCE_TYPES: string[] = [ "enrich_abusech_malwarebazaar", "enrich_abusech_threatfox", "enrich_otx", + "enrich_cisa_kev" ]; /** Some puller log sources don't need secrets. */ const NO_SECRET_LOG_SOURCES: string[] = [ @@ -37,6 +38,7 @@ const NO_SECRET_LOG_SOURCES: string[] = [ "enrich_abusech_urlhaus", "enrich_abusech_malwarebazaar", "enrich_abusech_threatfox", + "enrich_cisa_kev" ]; const LOG_SOURCE_RATES: Record = { @@ -52,6 +54,7 @@ const LOG_SOURCE_RATES: Record = { enrich_abusech_malwarebazaar: cdk.Duration.hours(1), enrich_abusech_threatfox: cdk.Duration.hours(1), enrich_otx: cdk.Duration.minutes(5), + enrich_cisa_kev: cdk.Duration.hours(1) }; const LOG_SOURCE_PLACEHOLDER_MAP: Record = { diff --git a/lib/rust/log_puller/src/pullers/cisa_kev.rs b/lib/rust/log_puller/src/pullers/cisa_kev.rs new file mode 100644 index 00000000..0583eafe --- /dev/null +++ b/lib/rust/log_puller/src/pullers/cisa_kev.rs @@ -0,0 +1,58 @@ +use anyhow::{anyhow, Context as AnyhowContext, Error, Result}; +use async_trait::async_trait; +use chrono::{DateTime, FixedOffset}; +use log::{debug, error, info}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + io::{Read, Write}, +}; + +use super::{PullLogs, PullLogsContext}; + +#[derive(Clone)] +pub struct CisaKevPuller; + + +const CISA_KEV_URL: &str = "https://www.cisa.gov/sites/default/files/csv/known_exploited_vulnerabilities.csv"; +const CISA_KEV_HEADERS: [&str; 9] = [ + "cveID", + "vendorProject", + "product", + "vulnerabilityName", + "dateAdded", + "shortDescription", + "requiredAction", + "dueDate", + "notes", +]; + +#[async_trait] +impl PullLogs for CisaKevPuller { + async fn pull_logs( + self, + client: reqwest::Client, + ctx: &PullLogsContext, + start_dt: DateTime, + end_dt: DateTime, + ) -> Result> { + info!("Pulling CISA KEV..."); + let resp = client.get(CISA_KEV_URL).send().await?.text().await?; + + let mut json_bytes = vec![]; + + let mut csv_reader = csv::ReaderBuilder::new() + .comment(Some(b'#')) + .from_reader(resp.as_bytes()); + + //csv_reader.set_headers(csv::StringRecord::from(CISA_KEV_HEADERS.to_vec())); + for result in csv_reader.deserialize() { + let record: HashMap = result?; + let bytes = serde_json::to_vec(&record)?; + json_bytes.write(bytes.as_slice())?; + json_bytes.write(b"\n")?; + } + + return Ok(json_bytes); + } +} diff --git a/lib/rust/log_puller/src/pullers/mod.rs b/lib/rust/log_puller/src/pullers/mod.rs index f5fe6d32..817ff35e 100644 --- a/lib/rust/log_puller/src/pullers/mod.rs +++ b/lib/rust/log_puller/src/pullers/mod.rs @@ -22,6 +22,7 @@ mod okta; mod onepassword; mod otx; mod snyk; +mod cisa_kev; #[derive(Clone)] pub struct PullerCache { @@ -205,6 +206,7 @@ pub enum LogSource { OnePasswordPuller(onepassword::OnePasswordPuller), Otx(otx::OtxPuller), Snyk(snyk::SnykPuller), + CisaKevPuller(cisa_kev::CisaKevPuller), AbuseChUrlhausPuller(abusech::AbuseChUrlhausPuller), AbuseChMalwareBazaarPuller(abusech::AbuseChMalwareBazaarPuller), AbuseChThreatfoxPuller(abusech::AbuseChThreatfoxPuller), @@ -239,6 +241,9 @@ impl LogSource { "abusech_threatfox" => Some(LogSource::AbuseChThreatfoxPuller( abusech::AbuseChThreatfoxPuller {}, )), + "cisa_kev" => Some(LogSource::CisaKevPuller( + cisa_kev::CisaKevPuller {}, + )), _ => None, } } @@ -256,6 +261,7 @@ impl LogSource { LogSource::AbuseChUrlhausPuller(_) => "abusech_urlhaus", LogSource::AbuseChMalwareBazaarPuller(_) => "abusech_malwarebazaar", LogSource::AbuseChThreatfoxPuller(_) => "abusech_threatfox", + LogSource::CisaKevPuller(_) => "cisa_kev" } } }