Skip to content

Commit

Permalink
Apply heuristics to divine pURLs from CVERecords iff possible.
Browse files Browse the repository at this point in the history
Fixes #763
  • Loading branch information
bobmcwhirter committed Sep 5, 2024
1 parent 7c65d51 commit ff0fdd0
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 4 deletions.
165 changes: 165 additions & 0 deletions etc/test-data/cve/CVE-2024-26308.json
Original file line number Diff line number Diff line change
@@ -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.<p>This issue affects Apache Commons Compress: from 1.21 before 1.26.</p><p>Users are recommended to upgrade to version 1.26, which fixes the issue.</p>"
}
],
"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"
]
}
]
}
]
}
}
33 changes: 33 additions & 0 deletions modules/ingestor/src/service/advisory/cve/divination.rs
Original file line number Diff line number Diff line change
@@ -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<Purl> {
divine_maven(product)
// add more here as we determine the correct heuristics
}

fn divine_maven(product: &Product) -> Option<Purl> {
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::<Vec<_>>();

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
}
99 changes: 95 additions & 4 deletions modules/ingestor/src/service/advisory/cve/loader.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -65,6 +68,7 @@ impl<'g> CveLoader<'g> {
.provider_metadata
.short_name
.as_ref(),
None,
),
Cve::Published(published) => (
published.containers.cna.title.as_ref(),
Expand Down Expand Up @@ -97,6 +101,7 @@ impl<'g> CveLoader<'g> {
.provider_metadata
.short_name
.as_ref(),
Some(&published.containers.cna.affected),
),
};

Expand Down Expand Up @@ -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 {
Expand All @@ -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?;
Expand All @@ -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?;

Expand Down Expand Up @@ -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(())
}
}
2 changes: 2 additions & 0 deletions modules/ingestor/src/service/advisory/cve/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
pub mod loader;

pub mod divination;

0 comments on commit ff0fdd0

Please sign in to comment.