From ff0fdd0557a6b12260d0f4fec0efabb4c67dc36f Mon Sep 17 00:00:00 2001 From: Bob McWhirter Date: Thu, 5 Sep 2024 15:45:58 -0400 Subject: [PATCH] Apply heuristics to divine pURLs from CVERecords iff possible. Fixes #763 --- etc/test-data/cve/CVE-2024-26308.json | 165 ++++++++++++++++++ .../src/service/advisory/cve/divination.rs | 33 ++++ .../src/service/advisory/cve/loader.rs | 99 ++++++++++- .../ingestor/src/service/advisory/cve/mod.rs | 2 + 4 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 etc/test-data/cve/CVE-2024-26308.json create mode 100644 modules/ingestor/src/service/advisory/cve/divination.rs diff --git a/etc/test-data/cve/CVE-2024-26308.json b/etc/test-data/cve/CVE-2024-26308.json new file mode 100644 index 00000000..437cf628 --- /dev/null +++ b/etc/test-data/cve/CVE-2024-26308.json @@ -0,0 +1,165 @@ +{ + "dataType": "CVE_RECORD", + "dataVersion": "5.1", + "cveMetadata": { + "cveId": "CVE-2024-26308", + "assignerOrgId": "f0158376-9dc2-43b6-827c-5f631a4d8d09", + "state": "PUBLISHED", + "assignerShortName": "apache", + "dateReserved": "2024-02-17T22:08:44.423Z", + "datePublished": "2024-02-19T08:31:50.192Z", + "dateUpdated": "2024-08-02T00:07:19.215Z" + }, + "containers": { + "cna": { + "affected": [ + { + "collectionURL": "https://repo.maven.apache.org/maven2/", + "defaultStatus": "unaffected", + "packageName": "org.apache.commons:commons-compress", + "product": "Apache Commons Compress", + "vendor": "Apache Software Foundation", + "versions": [ + { + "lessThan": "1.26.0", + "status": "affected", + "version": "1.21", + "versionType": "semver" + } + ] + } + ], + "credits": [ + { + "lang": "en", + "type": "reporter", + "value": "Yakov Shafranovich, Amazon Web Services" + } + ], + "descriptions": [ + { + "lang": "en", + "supportingMedia": [ + { + "base64": false, + "type": "text/html", + "value": "Allocation of Resources Without Limits or Throttling vulnerability in Apache Commons Compress.

This issue affects Apache Commons Compress: from 1.21 before 1.26.

Users are recommended to upgrade to version 1.26, which fixes the issue.

" + } + ], + "value": "Allocation of Resources Without Limits or Throttling vulnerability in Apache Commons Compress.This issue affects Apache Commons Compress: from 1.21 before 1.26.\n\nUsers are recommended to upgrade to version 1.26, which fixes the issue.\n\n" + } + ], + "metrics": [ + { + "other": { + "content": { + "text": "moderate" + }, + "type": "Textual description of severity" + } + } + ], + "problemTypes": [ + { + "descriptions": [ + { + "cweId": "CWE-770", + "description": "CWE-770 Allocation of Resources Without Limits or Throttling", + "lang": "en", + "type": "CWE" + } + ] + } + ], + "providerMetadata": { + "orgId": "f0158376-9dc2-43b6-827c-5f631a4d8d09", + "shortName": "apache", + "dateUpdated": "2024-02-19T08:31:50.192Z" + }, + "references": [ + { + "tags": [ + "vendor-advisory" + ], + "url": "https://lists.apache.org/thread/ch5yo2d21p7vlqrhll9b17otbyq4npfg" + }, + { + "url": "http://www.openwall.com/lists/oss-security/2024/02/19/2" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20240307-0009/" + } + ], + "source": { + "discovery": "EXTERNAL" + }, + "title": "Apache Commons Compress: OutOfMemoryError unpacking broken Pack200 file", + "x_generator": { + "engine": "Vulnogram 0.1.0-dev" + } + }, + "adp": [ + { + "metrics": [ + { + "other": { + "type": "ssvc", + "content": { + "id": "CVE-2024-26308", + "role": "CISA Coordinator", + "options": [ + { + "Exploitation": "none" + }, + { + "Automatable": "no" + }, + { + "Technical Impact": "partial" + } + ], + "version": "2.0.3", + "timestamp": "2024-02-22T17:49:36.910764Z" + } + } + } + ], + "title": "CISA ADP Vulnrichment", + "providerMetadata": { + "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0", + "shortName": "CISA-ADP", + "dateUpdated": "2024-07-05T17:21:56.918Z" + } + }, + { + "providerMetadata": { + "orgId": "af854a3a-2127-422b-91ae-364da2661108", + "shortName": "CVE", + "dateUpdated": "2024-08-02T00:07:19.215Z" + }, + "title": "CVE Program Container", + "references": [ + { + "tags": [ + "vendor-advisory", + "x_transferred" + ], + "url": "https://lists.apache.org/thread/ch5yo2d21p7vlqrhll9b17otbyq4npfg" + }, + { + "url": "http://www.openwall.com/lists/oss-security/2024/02/19/2", + "tags": [ + "x_transferred" + ] + }, + { + "url": "https://security.netapp.com/advisory/ntap-20240307-0009/", + "tags": [ + "x_transferred" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/modules/ingestor/src/service/advisory/cve/divination.rs b/modules/ingestor/src/service/advisory/cve/divination.rs new file mode 100644 index 00000000..7bc10c3b --- /dev/null +++ b/modules/ingestor/src/service/advisory/cve/divination.rs @@ -0,0 +1,33 @@ +//! Helpers to try to divine pURLs from arbitrary bits of information. + +use cve::common::Product; +use trustify_common::purl::Purl; + +pub fn divine_purl(product: &Product) -> Option { + divine_maven(product) + // add more here as we determine the correct heuristics +} + +fn divine_maven(product: &Product) -> Option { + if matches!( &product.collection_url, Some(url) if url == "https://repo.maven.apache.org/maven2/" ) + { + if let Some(package_name) = &product.package_name { + let parts = package_name.split(':').collect::>(); + + if parts.len() == 2 { + let group_id = parts[0]; + let artifact_id = parts[1]; + + return Some(Purl { + ty: "maven".to_string(), + namespace: Some(group_id.to_string()), + name: artifact_id.to_string(), + version: None, + qualifiers: Default::default(), + }); + } + } + } + + None +} diff --git a/modules/ingestor/src/service/advisory/cve/loader.rs b/modules/ingestor/src/service/advisory/cve/loader.rs index c0c6cf51..9f553c73 100644 --- a/modules/ingestor/src/service/advisory/cve/loader.rs +++ b/modules/ingestor/src/service/advisory/cve/loader.rs @@ -1,3 +1,5 @@ +use crate::graph::advisory::advisory_vulnerability::{Version, VersionInfo, VersionSpec}; +use crate::service::advisory::cve::divination::divine_purl; use crate::{ graph::{ advisory::{AdvisoryInformation, AdvisoryVulnerabilityInformation}, @@ -7,6 +9,7 @@ use crate::{ model::IngestResult, service::Error, }; +use cve::common::{Status, VersionRange}; use cve::{Cve, Timestamp}; use std::fmt::Debug; use tracing::instrument; @@ -51,7 +54,7 @@ impl<'g> CveLoader<'g> { .date_updated .map(Timestamp::assume_utc); - let (title, assigned, withdrawn, descriptions, cwe, org_name) = match &cve { + let (title, assigned, withdrawn, descriptions, cwe, org_name, affected) = match &cve { Cve::Rejected(rejected) => ( None, None, @@ -65,6 +68,7 @@ impl<'g> CveLoader<'g> { .provider_metadata .short_name .as_ref(), + None, ), Cve::Published(published) => ( published.containers.cna.title.as_ref(), @@ -97,6 +101,7 @@ impl<'g> CveLoader<'g> { .provider_metadata .short_name .as_ref(), + Some(&published.containers.cna.affected), ), }; @@ -130,13 +135,14 @@ impl<'g> CveLoader<'g> { modified, withdrawn, }; + let advisory = self .graph .ingest_advisory(id, labels, digests, information, &tx) .await?; // Link the advisory to the backing vulnerability - advisory + let advisory_vuln = advisory .link_to_vulnerability( id, Some(AdvisoryVulnerabilityInformation { @@ -151,6 +157,59 @@ impl<'g> CveLoader<'g> { ) .await?; + if let Some(affected) = affected { + for product in affected { + if let Some(purl) = divine_purl(product) { + // okay! we have a purl, now + // sort out version bounds & status + for version in &product.versions { + let (version_spec, version_type, status) = match version { + cve::common::Version::Single(version) => ( + VersionSpec::Exact(version.version.clone()), + version.version_type.clone(), + &version.status, + ), + cve::common::Version::Range(range) => match &range.range { + VersionRange::LessThan(upper) => ( + VersionSpec::Range( + Version::Inclusive(range.version.clone()), + Version::Exclusive(upper.clone()), + ), + Some(range.version_type.clone()), + &range.status, + ), + VersionRange::LessThanOrEqual(upper) => ( + VersionSpec::Range( + Version::Inclusive(range.version.clone()), + Version::Inclusive(upper.clone()), + ), + Some(range.version_type.clone()), + &range.status, + ), + }, + }; + + advisory_vuln + .ingest_package_status( + None, + &purl, + match status { + Status::Affected => "affected", + Status::Unaffected => "not_affected", + Status::Unknown => "unknown", + }, + VersionInfo { + scheme: version_type.unwrap_or("generic".to_string()), + spec: version_spec, + }, + &tx, + ) + .await? + } + } + } + } + vulnerability .drop_descriptions_for_advisory(advisory.advisory.id, &tx) .await?; @@ -174,16 +233,17 @@ mod test { use super::*; use crate::graph::Graph; use hex::ToHex; + use std::str::FromStr; use test_context::test_context; use test_log::test; use trustify_common::db::Transactional; + use trustify_common::purl::Purl; use trustify_test_context::{document, TrustifyContext}; #[test_context(TrustifyContext)] #[test(tokio::test)] async fn cve_loader(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { - let db = &ctx.db; - let graph = Graph::new(db.clone()); + let graph = Graph::new(ctx.db.clone()); let (cve, digests): (Cve, _) = document("mitre/CVE-2024-28111.json").await?; @@ -216,4 +276,35 @@ mod test { Ok(()) } + + #[test_context(TrustifyContext)] + #[test(tokio::test)] + async fn divine_purls(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let graph = Graph::new(ctx.db.clone()); + + let (cve, digests): (Cve, _) = document("cve/CVE-2024-26308.json").await?; + + let loader = CveLoader::new(&graph); + loader + .load(("file", "CVE-2024-26308.json"), cve, &digests) + .await?; + + let purl = graph + .get_package( + &Purl::from_str("pkg://maven/org.apache.commons/commons-compress")?, + Transactional::None, + ) + .await?; + + assert!(purl.is_some()); + + let purl = purl.unwrap(); + let purl = purl.base_purl; + + assert_eq!(purl.r#type, "maven"); + assert_eq!(purl.namespace, Some("org.apache.commons".to_string())); + assert_eq!(purl.name, "commons-compress"); + + Ok(()) + } } diff --git a/modules/ingestor/src/service/advisory/cve/mod.rs b/modules/ingestor/src/service/advisory/cve/mod.rs index 186dcf60..126e18ec 100644 --- a/modules/ingestor/src/service/advisory/cve/mod.rs +++ b/modules/ingestor/src/service/advisory/cve/mod.rs @@ -1 +1,3 @@ pub mod loader; + +pub mod divination;