diff --git a/entity/src/version_scheme.rs b/entity/src/version_scheme.rs index c3f934960..7ad6fd692 100644 --- a/entity/src/version_scheme.rs +++ b/entity/src/version_scheme.rs @@ -28,4 +28,5 @@ pub enum VersionScheme { Python, Maven, Golang, + Npm, } diff --git a/etc/test-data/cyclonedx/ghsa_test.json b/etc/test-data/cyclonedx/ghsa_test.json index ae441b403..4fe400dd7 100644 --- a/etc/test-data/cyclonedx/ghsa_test.json +++ b/etc/test-data/cyclonedx/ghsa_test.json @@ -39,6 +39,13 @@ "version": "1.1.0", "description": "1 vulnerability", "purl": "pkg:golang/code.gitea.io/gitea@1.1.0" + }, + { + "type": "library", + "name": "passport", + "version": "2.1.0", + "description": "1 vulnerability", + "purl": "pkg:npm/%40fastify/passport@2.1.0" } ] -} \ No newline at end of file +} diff --git a/etc/test-data/osv/GHSA-2ccf-ffrj-m4qw.json b/etc/test-data/osv/GHSA-2ccf-ffrj-m4qw.json new file mode 100644 index 000000000..8b3b9eeaf --- /dev/null +++ b/etc/test-data/osv/GHSA-2ccf-ffrj-m4qw.json @@ -0,0 +1,92 @@ +{ + "schema_version": "1.4.0", + "id": "GHSA-2ccf-ffrj-m4qw", + "modified": "2023-04-24T15:56:27Z", + "published": "2023-04-21T22:32:47Z", + "aliases": [ + "CVE-2023-29020" + ], + "summary": "CSRF token fixation in fastify-passport", + "details": "The [CSRF](https://owasp.org/www-community/attacks/csrf) protection enforced by the `@fastify/csrf-protection` library, when combined with `@fastify/passport`, can be bypassed by network and same-site attackers.\n\n## Details\n`fastify/csrf-protection` implements the [synchronizer token pattern](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern) (using plugins `@fastify/session` and `@fastify/secure-session`) by storing a random value used for CSRF token generation in the `_csrf` attribute of a user's session.\n\nThe `@fastify/passport` library does not clear the session object upon authentication, preserving the `_csrf` attribute between pre-login and authenticated sessions. Consequently, CSRF tokens generated before authentication are still valid. Network and [same-site attackers](https://canitakeyoursubdomain.name/) can thus obtain a CSRF token for their pre-session, fixate that pre-session in the victim's browser via cookie tossing, and then perform a CSRF attack after the victim authenticates.\n\n## Fix\nAs a solution, newer versions of `@fastify/passport` include the configuration options\n\n* `clearSessionOnLogin (default: true)` and\n* `clearSessionIgnoreFields (default: ['session'])`\n\nto clear all the session attributes by default, preserving those explicitly defined in `clearSessionIgnoreFields`.\n\n## Credits\n* Pedro Adão (@pedromigueladao), [Instituto Superior Técnico, University of Lisbon](https://tecnico.ulisboa.pt/)\n* Marco Squarcina (@lavish), [Security & Privacy Research Unit, TU Wien](https://secpriv.wien/)", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N" + } + ], + "affected": [ + { + "package": { + "ecosystem": "npm", + "name": "@fastify/passport" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.1.0" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "npm", + "name": "@fastify/passport" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.0.0" + }, + { + "fixed": "2.3.0" + } + ] + } + ] + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/fastify/fastify-passport/security/advisories/GHSA-2ccf-ffrj-m4qw" + }, + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-29020" + }, + { + "type": "WEB", + "url": "https://github.com/fastify/fastify-passport/commit/07c90feab9cba0dd4779e47cfb0717a7e2f01d3d" + }, + { + "type": "WEB", + "url": "https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern" + }, + { + "type": "PACKAGE", + "url": "https://github.com/fastify/fastify-passport" + }, + { + "type": "WEB", + "url": "https://owasp.org/www-community/attacks/csrf" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-352" + ], + "severity": "MODERATE", + "github_reviewed": true, + "github_reviewed_at": "2023-04-21T22:32:47Z", + "nvd_published_at": "2023-04-21T23:15:20Z" + } +} \ No newline at end of file diff --git a/modules/fundamental/tests/sbom/details.rs b/modules/fundamental/tests/sbom/details.rs index 784c7be5e..39e450292 100644 --- a/modules/fundamental/tests/sbom/details.rs +++ b/modules/fundamental/tests/sbom/details.rs @@ -20,7 +20,7 @@ async fn sbom_details_cyclonedx_osv(ctx: &TrustifyContext) -> Result<(), anyhow: Some("urn:uuid:a5ddee00-4b86-498c-b7fd-b001b77479d1".to_string()) ); - // ingest the advisory + // ingest the advisories let pypi = ctx.ingest_document("osv/GHSA-45c4-8wx5-qw6w.json").await?; assert_eq!(pypi.document_id, Some("GHSA-45c4-8wx5-qw6w".to_string())); @@ -35,13 +35,16 @@ async fn sbom_details_cyclonedx_osv(ctx: &TrustifyContext) -> Result<(), anyhow: let go = ctx.ingest_document("osv/GHSA-4h4p-553m-46qh.json").await?; assert_eq!(go.document_id, Some("GHSA-4h4p-553m-46qh".to_string())); + let npm = ctx.ingest_document("osv/GHSA-2ccf-ffrj-m4qw.json").await?; + assert_eq!(npm.document_id, Some("GHSA-2ccf-ffrj-m4qw".to_string())); + let sbom1 = sbom .fetch_sbom_details(result1.id, vec![], &ctx.db) .await? .expect("SBOM details must be found"); log::info!("SBOM1: {sbom1:?}"); - assert_eq!(3, sbom1.advisories.len()); + assert_eq!(4, sbom1.advisories.len()); check_advisory( &sbom1, "GHSA-45c4-8wx5-qw6w", @@ -60,6 +63,12 @@ async fn sbom_details_cyclonedx_osv(ctx: &TrustifyContext) -> Result<(), anyhow: "CVE-2024-6886", Severity::Critical, ); + check_advisory( + &sbom1, + "GHSA-2ccf-ffrj-m4qw", + "CVE-2023-29020", + Severity::High, + ); Ok(()) } diff --git a/modules/ingestor/src/service/advisory/osv/loader.rs b/modules/ingestor/src/service/advisory/osv/loader.rs index 3f3993e94..09ff1dcf7 100644 --- a/modules/ingestor/src/service/advisory/osv/loader.rs +++ b/modules/ingestor/src/service/advisory/osv/loader.rs @@ -193,6 +193,16 @@ impl<'g> OsvLoader<'g> { ) .await?; } + (RangeType::Ecosystem, Ecosystem::Npm) => { + create_package_status( + &advisory_vuln, + &purl, + range, + &VersionScheme::Npm, + &tx, + ) + .await?; + } (_, _) => { create_package_status_versions( &advisory_vuln, diff --git a/modules/ingestor/src/service/advisory/osv/translate.rs b/modules/ingestor/src/service/advisory/osv/translate.rs index 572d3cb46..7dd5f9190 100644 --- a/modules/ingestor/src/service/advisory/osv/translate.rs +++ b/modules/ingestor/src/service/advisory/osv/translate.rs @@ -19,7 +19,7 @@ fn translate<'a>(ecosystem: &Ecosystem, name: &'a str) -> Option> match ecosystem { Ecosystem::CRAN => PackageUrl::new("cran", name).ok(), Ecosystem::CratesIO => PackageUrl::new("cargo", name).ok(), - Ecosystem::Npm => PackageUrl::new("npm", name).ok(), + Ecosystem::Npm => split_name(name, "npm", "/"), Ecosystem::Maven(repo) => { let split = name.split(':').collect::>(); if split.len() == 2 { @@ -39,29 +39,29 @@ fn translate<'a>(ecosystem: &Ecosystem, name: &'a str) -> Option> } } Ecosystem::PyPI => PackageUrl::new("pypi", name).ok(), - Ecosystem::Go => { - let ty = "golang"; - let separator = "/"; - let split = name.split(separator).collect::>(); - match split.len() { - 0 => None, - 1 => PackageUrl::new(ty, split[0]).ok(), - _ => { - let namespace = split[0]; - let name = split[1..].join(separator); - PackageUrl::new(ty, name) - .map(|mut purl| { - purl.with_namespace(namespace); - purl - }) - .ok() - } - } - } + Ecosystem::Go => split_name(name, "golang", "/"), _ => None, } } +fn split_name<'a>(name: &'a str, ty: &'a str, separator: &str) -> Option> { + let split = name.split(separator).collect::>(); + match split.len() { + 0 => None, + 1 => PackageUrl::new(ty, split[0]).ok(), + _ => { + let namespace = split[0]; + let name = split[1..].join(separator); + PackageUrl::new(ty, name) + .map(|mut purl| { + purl.with_namespace(namespace); + purl + }) + .ok() + } + } +} + #[cfg(test)] mod test { use super::*; @@ -91,6 +91,11 @@ mod test { "github.com/minio/minio", Some("pkg:golang/github.com/minio/minio") )] + #[case( + Ecosystem::Npm, + "@fastify/passport", + Some("pkg:npm/%40fastify/passport") + )] fn test_translate( #[case] ecosystem: Ecosystem, #[case] name: &str,