diff --git a/cpe/src/lib.rs b/cpe/src/lib.rs index daafa2a..7902c50 100644 --- a/cpe/src/lib.rs +++ b/cpe/src/lib.rs @@ -9,6 +9,7 @@ //! #![doc(html_root_url = "https://emo-crab.github.io/nvd-rs/cpe")] + // Package wfn provides a representation, bindings and matching of the Well-Formed CPE names as per // https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf and // https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7696.pdf @@ -25,6 +26,7 @@ use crate::component::Language; use crate::error::{CPEError, Result}; use component::Component; use part::Part; + // https://csrc.nist.gov/projects/security-content-automation-protocol/specifications/cpe // view-source:https://csrc.nist.gov/schema/cpe/2.3/cpe-dictionary_2.3.xsd // https://scap.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd @@ -56,7 +58,8 @@ pub struct CPEName { // 表示无法归类上上述其他属性的值 pub other: Component, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Hash)] + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default, Hash)] pub struct Product { pub part: String, pub vendor: String, @@ -72,6 +75,7 @@ impl From<&CPEName> for Product { } } } + impl CPEName { // 从uri转CPE属性 pub fn from_uri(uri: &str) -> Result { diff --git a/cve/src/v4/configurations.rs b/cve/src/v4/configurations.rs index c52a47c..6161770 100644 --- a/cve/src/v4/configurations.rs +++ b/cve/src/v4/configurations.rs @@ -1,7 +1,9 @@ //! configurations //! +use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; use std::collections::HashSet; + /// A configuration is a container that holds a set of nodes which then contain CPE Name Match Criteria. Configurations consist of three different types. /// #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] @@ -45,10 +47,11 @@ pub struct Node { #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Match { - // 是否存在漏洞 + // 是否为漏洞组件,如果为假,就是运行环境,在api接口中没有下面的版本信息才是运行环境 + #[serde(default)] pub vulnerable: bool, - #[serde(flatten)] - pub cpe_uri: CPEUri, + #[serde(alias = "criteria")] + pub cpe23_uri: String, // 包括 从版本开始 pub version_start_including: Option, // 排除 从版本开始 @@ -57,21 +60,33 @@ pub struct Match { pub version_end_including: Option, // 排除 到版本结束 pub version_end_excluding: Option, - #[serde(rename = "cpe_name", default)] - pub cpe_name: Vec, + pub created: Option, + pub last_modified: Option, + pub cpe_last_modified: Option, + #[serde(default)] + pub status: MatchStatus, + #[serde(rename = "cpe_name", alias = "matches", default)] + pub matches: Vec, pub match_criteria_id: Option, } -#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Eq)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct CPEUri { - /// A CPE Match string is a single CPE Names string that correlates to one or many CPE Names in the Official CPE Dictionary. When a match string has the bug icon next to it, all matching CPE Names are considered vulnerable. You can click the caret below a CPE Match String to see the CPE Names in the dictionary that match. - #[serde( - serialize_with = "cpe::dictionary::attribute_to_uri", - deserialize_with = "cpe::dictionary::uri_to_attribute", - alias = "criteria" - )] - pub cpe23_uri: cpe::CPEName, +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] +pub enum MatchStatus { + Active, + Inactive, +} + +impl Default for MatchStatus { + fn default() -> Self { + Self::Active + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Matches { + pub cpe_name: String, + pub cpe_name_id: String, } #[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Eq)] @@ -95,7 +110,56 @@ impl Match { || self.version_end_excluding.is_some() } pub fn product(&self) -> cpe::Product { - cpe::Product::from(&self.cpe_uri.cpe23_uri) + match cpe::CPEName::from_uri(&self.cpe23_uri) { + Ok(name) => cpe::Product::from(&name), + Err(_) => cpe::Product::default(), + } + } + pub fn get_version_range(&self) -> String { + let mut version = None; + let mut op_start = None; + let mut op_end = None; + let mut v_start = None; + let mut v_end = None; + if let Ok(name) = cpe::CPEName::from_uri(&self.cpe23_uri) { + if !(name.version.matches("*") || name.version.matches("-")) { + op_start = Some("="); + } + version = Some(name.version.to_string()); + }; + if let Some(start_inc) = &self.version_start_including { + op_start = Some(">="); + v_start = Some(start_inc.as_str()); + } + if let Some(start_exc) = &self.version_start_excluding { + op_start = Some(">"); + v_start = Some(start_exc.as_str()); + } + if let Some(end_inc) = &self.version_end_including { + op_end = Some("<="); + v_end = Some(end_inc.as_str()); + } + if let Some(end_exc) = &self.version_end_excluding { + op_end = Some("<"); + v_end = Some(end_exc.as_str()); + } + // 什么都没有的 + if v_start.is_none() && v_end.is_none() { + return format!( + "{} {}", + op_start.unwrap_or(""), + version.unwrap_or_default() + ); + } else { + return format!( + "{}{} {} {}{}", + v_start.unwrap_or(""), + op_start.unwrap_or(""), + version.unwrap_or_default(), + op_end.unwrap_or(""), + v_end.unwrap_or_default() + ); + } } pub fn match_version_range(&self, ver: &str) -> bool { if let Some(start_inc) = &self.version_start_including { @@ -125,11 +189,13 @@ impl Match { } pub fn is_match(&self, product: &str, version: &str) -> bool { - if self.cpe_uri.cpe23_uri.match_product(product) { - if self.has_version_range() { - return self.match_version_range(version); + if let Ok(name) = cpe::CPEName::from_uri(&self.cpe23_uri) { + if name.match_product(product) { + if self.has_version_range() { + return self.match_version_range(version); + } + return name.match_version(version); } - return self.cpe_uri.cpe23_uri.match_version(version); } false } diff --git a/nvd-api/src/v2/products.rs b/nvd-api/src/v2/products.rs index 94993b8..59ca4dc 100644 --- a/nvd-api/src/v2/products.rs +++ b/nvd-api/src/v2/products.rs @@ -2,6 +2,11 @@ use crate::v2::{Keyword, LastModDate, LimitOffset}; use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; +/// # Products +/// This documentation assumes that you already understand at least one common programming language and are generally familiar with JSON RESTful services. JSON specifies the format of the data returned by the REST service. REST refers to a style of services that allow computers to communicate via HTTP over the Internet. Click here for a list of best practices and additional information on where to start. The NVD is also documenting popular workflows to assist developers working with the APIs. +/// +/// Please note, new users are discouraged from starting with the 1.0 API as it will be retired in 2023 but you may still view documentation for the 1.0 Vulnerability and 1.0 Product APIs. +/// #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase")] pub struct CpeParameters { @@ -16,6 +21,7 @@ pub struct CpeParameters { #[serde(flatten)] pub limit_offset: Option, } + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase")] pub struct CpeMatchParameters { @@ -28,11 +34,13 @@ pub struct CpeMatchParameters { #[serde(flatten)] pub limit_offset: Option, } + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase")] pub struct Products { pub cpe: Cpe, } + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase")] pub struct Cpe { @@ -50,12 +58,14 @@ pub struct Cpe { #[serde(default)] pub deprecates: Vec, } + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase")] pub struct Titles { pub title: String, pub lang: String, } + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase")] pub struct Refs { @@ -63,6 +73,7 @@ pub struct Refs { #[serde(default)] pub r#type: RefType, } + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] pub enum RefType { Advisory, @@ -86,6 +97,7 @@ pub struct DeprecatedBy { pub cpe_name: String, pub cpe_name_id: String, } + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase")] pub struct Deprecates { @@ -93,35 +105,9 @@ pub struct Deprecates { pub cpe_name_id: String, } +/// Match Criteria API #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] #[serde(rename_all = "camelCase")] pub struct MatchStrings { - pub match_string: MatchString, -} -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] -#[serde(rename_all = "camelCase")] -pub struct MatchString { - pub criteria: String, - pub match_criteria_id: String, - pub version_start_excluding: Option, - pub version_start_including: Option, - pub version_end_excluding: Option, - pub version_end_including: Option, - pub created: NaiveDateTime, - pub last_modified: NaiveDateTime, - pub cpe_last_modified: Option, - pub status: Status, - #[serde(default)] - pub matches: Vec, -} -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] -pub enum Status { - Active, - Inactive, -} -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Matches { - pub cpe_name: String, - pub cpe_name_id: String, + pub match_string: cve::v4::configurations::Match, } diff --git a/nvd-yew/src/component/cve_configuration.rs b/nvd-yew/src/component/cve_configuration.rs index 87d8b3f..1c4a69e 100644 --- a/nvd-yew/src/component/cve_configuration.rs +++ b/nvd-yew/src/component/cve_configuration.rs @@ -1,11 +1,14 @@ use crate::component::TooltipPopover; use cve::v4::configurations::Operator; use yew::prelude::*; + #[derive(PartialEq, Clone, Properties)] pub struct CVEConfigurationProps { pub props: Vec, } + pub struct CVEConfiguration; + impl Component for CVEConfiguration { type Message = (); type Properties = CVEConfigurationProps; @@ -46,6 +49,7 @@ impl Component for CVEConfiguration { } } } + fn operator(o: Operator) -> &'static str { match o { Operator::And => "ti-logic-and", @@ -55,22 +59,24 @@ fn operator(o: Operator) -> &'static str { impl CVEConfiguration { fn cpe_match(&self, m: cve::v4::configurations::Match) -> Html { - let vendor = m.cpe_uri.cpe23_uri.vendor.to_string(); - let product = m.cpe_uri.cpe23_uri.product.to_string(); + let version_range = m.get_version_range(); + let name = cpe::CPEName::from_uri(&m.cpe23_uri).unwrap(); + let vendor = name.vendor.to_string(); + let product = name.product.to_string(); html! { - {m.cpe_uri.cpe23_uri.to_string()} - {m.cpe_uri.cpe23_uri.version.to_string()} + {m.cpe23_uri} + {version_range} } } @@ -111,8 +117,8 @@ impl CVEConfiguration { html!() } fn node(&self, nodes: Vec) -> Html { - nodes.into_iter().map(|node|{ - html!{ + nodes.into_iter().map(|node| { + html! { {format!("{:?}",node.operator)} @@ -131,7 +137,7 @@ impl CVEConfiguration { } - }).collect::() + }).collect::() } } // CVE-2023-0056 diff --git a/nvd-yew/src/routes/cve.rs b/nvd-yew/src/routes/cve.rs index 04057d0..408bb1d 100644 --- a/nvd-yew/src/routes/cve.rs +++ b/nvd-yew/src/routes/cve.rs @@ -2,7 +2,6 @@ use crate::component::cvss_tags::{cvss2, cvss3}; use crate::component::{CVEConfiguration, CVEConfigurationProps, CVSS2, CVSS3}; use crate::console_log; use crate::modules::cve::Cve; -use crate::routes::Route; use crate::services::cve::cve_details; use crate::services::FetchState; use yew::prelude::*; @@ -10,51 +9,56 @@ use yew_router::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, Properties)] pub struct CVEProps { - pub id: String, + pub id: String, } pub enum Msg { - SetFetchState(FetchState), - Send, + SetFetchState(FetchState), + Back, + Send, } pub struct CVEDetails { - cve: Option, + cve: Option, } impl Component for CVEDetails { - type Message = Msg; - type Properties = CVEProps; + type Message = Msg; + type Properties = CVEProps; - fn create(_ctx: &Context) -> Self { - Self { cve: None } - } - fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { - match msg { - Msg::SetFetchState(state) => { - match state { - FetchState::Success(data) => self.cve = Some(data), - FetchState::Failed(err) => { - console_log!("{:?}", err); - } + fn create(_ctx: &Context) -> Self { + Self { cve: None } + } + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::SetFetchState(state) => { + match state { + FetchState::Success(data) => self.cve = Some(data), + FetchState::Failed(err) => { + console_log!("{:?}", err); + } + } + return true; + } + Msg::Send => { + let id = ctx.props().id.clone(); + ctx.link().send_future(async { + match cve_details(id).await { + Ok(data) => Msg::SetFetchState(FetchState::Success(data)), + Err(err) => Msg::SetFetchState(FetchState::Failed(err)), + } + }); + } + Msg::Back => { + ctx.link().navigator().unwrap().back(); + } } - return true; - } - Msg::Send => { - let id = ctx.props().id.clone(); - ctx.link().send_future(async { - match cve_details(id).await { - Ok(data) => Msg::SetFetchState(FetchState::Success(data)), - Err(err) => Msg::SetFetchState(FetchState::Failed(err)), - } - }); - } + false } - false - } - fn view(&self, _ctx: &Context) -> Html { - if self.cve.is_none() { - return html! { + fn view(&self, ctx: &Context) -> Html { + let on_back = ctx.link().callback(|_event: MouseEvent| Msg::Back); + if self.cve.is_none() { + return html! {
@@ -67,9 +71,9 @@ impl Component for CVEDetails {
}; - } - let cve = self.cve.clone().unwrap(); - html! { + } + let cve = self.cve.clone().unwrap(); + html! { <>
@@ -79,7 +83,9 @@ impl Component for CVEDetails {
@@ -99,19 +105,19 @@ impl Component for CVEDetails {
} - } - fn rendered(&mut self, ctx: &Context, first_render: bool) { - if first_render { - ctx.link().send_message(Msg::Send); } - } + fn rendered(&mut self, ctx: &Context, first_render: bool) { + if first_render { + ctx.link().send_message(Msg::Send); + } + } } impl CVEDetails { - fn cvss(&self, cve: Cve) -> Html { - let cvss_v3 = cve.metrics.base_metric_v3.inner(); - let cvss_v2 = cve.metrics.base_metric_v2.inner(); - html! { + fn cvss(&self, cve: Cve) -> Html { + let cvss_v3 = cve.metrics.base_metric_v3.inner(); + let cvss_v2 = cve.metrics.base_metric_v2.inner(); + html! { <>
} - } - fn description(&self, description_data: Vec) -> Html { - let description = description_data - .iter() - .map(|d| d.value.clone()) - .collect::(); - let mut description = description.chars(); - html! { + } + fn description(&self, description_data: Vec) -> Html { + let description = description_data + .iter() + .map(|d| d.value.clone()) + .collect::(); + let mut description = description.chars(); + html! {

{description.next().unwrap_or_default()}{description.collect::()}

} - } - fn references(&self, reference: Vec) -> Html { - html! { + } + fn references(&self, reference: Vec) -> Html { + html! {
@@ -198,11 +204,11 @@ impl CVEDetails {
} - } - fn configurations(&self, configuration: Vec) -> Html { - let p = CVEConfigurationProps { - props: configuration.clone(), - }; - html! {} - } + } + fn configurations(&self, configuration: Vec) -> Html { + let p = CVEConfigurationProps { + props: configuration.clone(), + }; + html! {} + } }