diff --git a/Cargo.lock b/Cargo.lock index 30bae19..de43166 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1392,6 +1392,7 @@ dependencies = [ "indicatif", "log", "reqwest", + "serde", "serde_json", "tokio", ] diff --git a/README.md b/README.md index e19aeaa..572ac17 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ binaries or to symlink `~/.cargo/bin/vulner` to some place covered by PATH $ ./scripts/check-runtime-deps.sh $ vulner --help $ RUST_LOG=debug vulner sync -$ RUST_LOG=info vulner scan +$ RUST_LOG=info vulner scan -o ~/vulner/scan-results ``` diff --git a/crates/cli/src/command/scan.rs b/crates/cli/src/command/scan.rs index 1670c30..d75b407 100644 --- a/crates/cli/src/command/scan.rs +++ b/crates/cli/src/command/scan.rs @@ -10,8 +10,9 @@ use cpe_tag::package::Package; use cpe_tag::query_builder::{get_grep_patterns, query}; use os_adapter::adapter::get_adapter; use reqwest::Client; +use security_advisories::cve_summary::CveSummary; use security_advisories::http::get_client; -use security_advisories::service::{fetch_cves_by_cpe, get_cve_summary, CPE_MATCH_FEED}; +use security_advisories::service::{fetch_cves_by_cpe, get_cves_summary, CPE_MATCH_FEED}; use std::error::Error; use std::fs::create_dir_all; use std::fs::File; @@ -81,7 +82,7 @@ async fn handle_cves( let mut already_notified = false; for cpe in matches { let cves = fetch_cves_by_cpe(client, cpe).await?; - let cves = get_cve_summary(&cves); + let cves = get_cves_summary(&cves); if cves.is_empty() { continue; @@ -97,13 +98,13 @@ async fn handle_cves( Ok(()) } -fn write_report(cwd: &Path, cpe: &str, cves: &[String]) -> Result<(), Box> { +fn write_report(cwd: &Path, cpe: &str, cves: &[CveSummary]) -> Result<(), Box> { log::info!("saving report in {:?} ...", cwd.as_os_str()); create_dir_all(cwd)?; let mut f = File::create(cwd.join(format!("{}.txt", cpe)))?; for cve in cves { - log::debug!("{}", cve); + log::debug!("{}", cve.id); writeln!(f, "{}", cve)?; } Ok(()) diff --git a/crates/security-advisories/Cargo.toml b/crates/security-advisories/Cargo.toml index bca717b..8274b32 100644 --- a/crates/security-advisories/Cargo.toml +++ b/crates/security-advisories/Cargo.toml @@ -14,4 +14,5 @@ indicatif = "0.16.2" log = "0.4.14" reqwest = { version = "0.11.9", features = ["gzip", "json", "stream"] } serde_json = "1.0.78" +serde = "1.0.136" tokio = { version = "1.15.0", features = ["fs"] } diff --git a/crates/security-advisories/src/cve_summary.rs b/crates/security-advisories/src/cve_summary.rs new file mode 100644 index 0000000..609d592 --- /dev/null +++ b/crates/security-advisories/src/cve_summary.rs @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use serde::Serialize; +use serde_json::to_string; +use std::fmt; + +#[derive(Serialize, Debug)] +pub struct CveSummary { + pub id: String, + pub description: String, + pub urls: Vec, +} + +impl CveSummary { + pub fn new(id: String, description: String, urls: Vec) -> Self { + Self { + id, + description, + urls, + } + } +} + +impl fmt::Display for CveSummary { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + to_string(&self).unwrap_or_else(|_| "{}".to_owned()) + ) + } +} diff --git a/crates/security-advisories/src/lib.rs b/crates/security-advisories/src/lib.rs index c17a930..1de6686 100644 --- a/crates/security-advisories/src/lib.rs +++ b/crates/security-advisories/src/lib.rs @@ -5,6 +5,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +pub mod cve_summary; pub mod http; pub mod service; mod utils; diff --git a/crates/security-advisories/src/service.rs b/crates/security-advisories/src/service.rs index c659777..2c27df6 100644 --- a/crates/security-advisories/src/service.rs +++ b/crates/security-advisories/src/service.rs @@ -5,6 +5,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::cve_summary::CveSummary; use reqwest::Client; use serde_json::Value; use std::error::Error; @@ -24,8 +25,8 @@ pub async fn fetch_feed_checksum(client: &Client) -> Result Vec { - nvd::get_cve_summary(full_cve_resp) +pub fn get_cves_summary(full_cve_resp: &Value) -> Vec { + nvd::get_cves_summary(full_cve_resp) } pub async fn download_cpe_match_feed( diff --git a/crates/security-advisories/src/service/nvd.rs b/crates/security-advisories/src/service/nvd.rs index 31d286b..5b28610 100644 --- a/crates/security-advisories/src/service/nvd.rs +++ b/crates/security-advisories/src/service/nvd.rs @@ -5,6 +5,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::cve_summary::CveSummary; use crate::utils::get_progress_bar; use futures_util::StreamExt; use reqwest::Client; @@ -34,12 +35,13 @@ pub async fn fetch_cves_by_cpe(client: &Client, cpe: &str) -> Result Vec { +pub fn get_cves_summary(full_cve_resp: &Value) -> Vec { let mut ids = vec![]; if let Some(items) = full_cve_resp["result"]["CVE_Items"].as_array() { for item in items { - if let Some(id) = item["cve"]["CVE_data_meta"]["ID"].as_str() { - ids.push(id.to_owned()); + let cve_data = &item["cve"]; + if let Some(summary) = get_cve_summary(cve_data) { + ids.push(summary); } } } @@ -107,6 +109,41 @@ fn get_checksum(meta: String) -> Result { } } +fn get_cve_summary(cve_data: &Value) -> Option { + if let Some(id) = cve_data["CVE_data_meta"]["ID"].as_str() { + return Some(CveSummary::new( + id.to_owned(), + get_cve_desc(cve_data), + get_cve_urls(id, cve_data), + )); + } + None +} + +fn get_cve_desc(cve_data: &Value) -> String { + if let Some(descriptions) = cve_data["description"]["description_data"].as_array() { + if let Some(desc) = descriptions.iter().find(|x| x["lang"] == "en") { + if let Some(value) = desc["value"].as_str() { + return value.to_owned(); + } + } + } + "".to_owned() +} + +fn get_cve_urls(id: &str, cve_data: &Value) -> Vec { + let nvd_url = "https://nvd.nist.gov/vuln/detail"; + let mut urls = vec![format!("{}/{}", nvd_url, id)]; + + if let Some(ref_data) = cve_data["references"]["reference_data"].as_array() { + for url in ref_data.iter().map(|x| x["url"].as_str()).flatten() { + urls.push(url.to_owned()); + } + } + + urls +} + #[cfg(test)] mod tests { use super::*; diff --git a/docs/COOKBOOK.md b/docs/COOKBOOK.md index c0c3cb9..81507b6 100644 --- a/docs/COOKBOOK.md +++ b/docs/COOKBOOK.md @@ -36,11 +36,22 @@ $ tree ~/vulner/scan-results/ ``` Report for particular package: ```bash -$ cat ~/vulner/scan-results/2022-01-30UTC/*/app-emulation/*containerd*.txt +$ cat ~/vulner/scan-results/2022-01-30UTC/*/app-emulation/*containerd*.txt | jq '.' ``` ``` -CVE-2021-43816 -CVE-2021-41103 +{ + "id": "CVE-2021-41103", + "description": "A bug was found in containerd where container root directories and some plugins had insufficiently restricted permissions, allowing otherwise unprivileged Linux users to traverse directory contents and execute programs.", +, + "urls": [ + "https://nvd.nist.gov/vuln/detail/CVE-2021-41103", + "https://github.com/containerd/containerd/commit/5b46e404f6b9f661a205e28d59c982d3634148f8", + "https://github.com/containerd/containerd/security/advisories/GHSA-c2h3-6mxw-7mvq", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZNFADTCHHYWVM6W4NJ6CB4FNFM2VMBIB/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/B5Q6G6I4W5COQE25QMC7FJY3I3PAYFBB/", + "https://www.debian.org/security/2021/dsa-5002" + ] +} ```