Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.metaeffekt.mirror.contents.advisory.MsrcAdvisorEntry Maven / Gradle / Ivy
/*
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.metaeffekt.mirror.contents.advisory;
import com.metaeffekt.artifact.analysis.utils.CustomCollectors;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.utils.TimeUtils;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.enrichment.InventoryEnricher;
import com.metaeffekt.mirror.contents.base.CvssConditionAttributes;
import com.metaeffekt.mirror.contents.base.DescriptionParagraph;
import com.metaeffekt.mirror.contents.base.Reference;
import com.metaeffekt.mirror.contents.msrcdata.MsThreat;
import com.metaeffekt.mirror.contents.msrcdata.MsrcRemediation;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeStore;
import com.metaeffekt.mirror.contents.store.VulnerabilityTypeStore;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.AdvisoryMetaData;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.report.model.AdvisoryUtils;
import org.metaeffekt.core.security.cvss.CvssSource;
import org.metaeffekt.core.security.cvss.CvssVector;
import org.metaeffekt.core.security.cvss.KnownCvssEntities;
import org.metaeffekt.core.security.cvss.v3.Cvss3P1;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.*;
import java.util.stream.Collectors;
public class MsrcAdvisorEntry extends AdvisoryEntry {
private final static Logger LOG = LoggerFactory.getLogger(MsrcAdvisorEntry.class);
protected final static Set CONVERSION_KEYS_AMB = new HashSet(AdvisoryEntry.CONVERSION_KEYS_AMB) {{
add(InventoryAttribute.MS_AFFECTED_PRODUCTS.getKey());
add(InventoryAttribute.MS_THREATS.getKey());
add(InventoryAttribute.MS_REMEDIATIONS.getKey());
}};
protected final static Set CONVERSION_KEYS_MAP = new HashSet(AdvisoryEntry.CONVERSION_KEYS_MAP) {{
add("affectedProducts");
add("msThreats");
add("msRemediations");
}};
protected final Set affectedProducts = new HashSet<>();
protected final Set msThreats = new HashSet<>();
protected final Set msrcRemediations = new HashSet<>();
public MsrcAdvisorEntry() {
super(AdvisoryTypeStore.MSRC);
}
public MsrcAdvisorEntry(String id) {
super(AdvisoryTypeStore.MSRC, id);
}
public void addAffectedProduct(String product) {
affectedProducts.add(product);
}
public void addAffectedProducts(Collection products) {
affectedProducts.addAll(products);
}
public void addMsThreat(MsThreat threat) {
msThreats.add(threat);
}
public void addMsThreats(Collection threats) {
msThreats.addAll(threats);
}
private void addMsThreats(JSONArray jsonArray) {
for (int i = 0; i < jsonArray.length(); i++) {
final JSONObject json = jsonArray.getJSONObject(i);
final MsThreat threat = MsThreat.fromJson(json);
msThreats.add(threat);
}
}
private void addMsThreats(List> maps) {
for (Map map : maps) {
final MsThreat threat = MsThreat.fromMap(map);
msThreats.add(threat);
}
}
public void addMsRemediation(MsrcRemediation remediation) {
msrcRemediations.add(remediation);
}
public void addMsRemediations(Collection remediations) {
msrcRemediations.addAll(remediations);
}
private void addMsRemediations(JSONArray jsonArray) {
for (int i = 0; i < jsonArray.length(); i++) {
final JSONObject json = jsonArray.getJSONObject(i);
final MsrcRemediation remediation = MsrcRemediation.fromJson(json);
msrcRemediations.add(remediation);
}
}
private void addMsRemediations(List> maps) {
for (Map map : maps) {
final MsrcRemediation remediation = MsrcRemediation.fromMap(map);
msrcRemediations.add(remediation);
}
}
public Set getAffectedProducts() {
return affectedProducts;
}
public Set getMsThreats() {
return msThreats;
}
public Set getMsRemediations() {
return msrcRemediations;
}
public Set getMsRemediationsByAffectedProduct(String productId) {
return msrcRemediations.stream()
.filter(r -> r.getAffectedProductIds().contains(productId))
.collect(Collectors.toSet());
}
public Set getMsRemediationsByDescriptionEquals(String description) {
if (description.charAt(0) == 'K' && description.charAt(1) == 'B') {
LOG.warn("MSRC remediation description starts with KB, which is most likely a mistake: {}", description);
}
return msrcRemediations.stream()
.filter(r -> Objects.equals(description, r.getDescription()))
.collect(Collectors.toSet());
}
public String getCveFromId() {
if (StringUtils.isEmpty(super.id) || super.id.contains("ADV")) return null;
return super.id.replace("MSRC-", "");
}
@Override
public String getUrl() {
return "https://msrc.microsoft.com/update-guide/en-US/vulnerability/" + id;
}
@Override
public String getType() {
return AdvisoryUtils.normalizeType("alert");
}
/* TYPE CONVERSION METHODS */
@Override
protected Set conversionKeysAmb() {
return CONVERSION_KEYS_AMB;
}
@Override
protected Set conversionKeysMap() {
return CONVERSION_KEYS_MAP;
}
@Override
public CertSeiAdvisorEntry constructDataClass() {
return new CertSeiAdvisorEntry();
}
public static MsrcAdvisorEntry fromAdvisoryMetaData(AdvisoryMetaData amd) {
return AdvisoryEntry.fromAdvisoryMetaData(amd, MsrcAdvisorEntry::new);
}
public static MsrcAdvisorEntry fromInputMap(Map map) {
return AdvisoryEntry.fromInputMap(map, MsrcAdvisorEntry::new);
}
public static MsrcAdvisorEntry fromJson(JSONObject json) {
return AdvisoryEntry.fromJson(json, MsrcAdvisorEntry::new);
}
public static MsrcAdvisorEntry fromDocument(Document document) {
return AdvisoryEntry.fromDocument(document, MsrcAdvisorEntry::new);
}
@Override
public void appendFromDataClass(AdvisoryEntry dataClass) {
super.appendFromDataClass(dataClass);
if (!(dataClass instanceof MsrcAdvisorEntry)) {
return;
}
final MsrcAdvisorEntry msrcAdvisorEntry = (MsrcAdvisorEntry) dataClass;
this.addAffectedProducts(msrcAdvisorEntry.getAffectedProducts());
this.addMsThreats(msrcAdvisorEntry.getMsThreats());
this.addMsRemediations(msrcAdvisorEntry.getMsRemediations());
}
@Override
public void appendFromBaseModel(AdvisoryMetaData amd) {
super.appendFromBaseModel(amd);
if (amd.get(InventoryAttribute.MS_AFFECTED_PRODUCTS) != null) {
this.addAffectedProducts(Arrays.stream(amd.get(InventoryAttribute.MS_AFFECTED_PRODUCTS).split(", ")).collect(Collectors.toSet()));
}
if (amd.get(InventoryAttribute.MS_THREATS) != null) {
this.addMsThreats(new JSONArray(amd.get(InventoryAttribute.MS_THREATS)));
}
if (amd.get(InventoryAttribute.MS_REMEDIATIONS) != null) {
this.addMsRemediations(new JSONArray(amd.get(InventoryAttribute.MS_REMEDIATIONS)));
}
}
@Override
public void appendToBaseModel(AdvisoryMetaData amd) {
super.appendToBaseModel(amd);
amd.set(InventoryAttribute.MS_AFFECTED_PRODUCTS, String.join(", ", affectedProducts));
amd.set(InventoryAttribute.MS_THREATS, new JSONArray(msThreats.stream().map(MsThreat::toJson).collect(Collectors.toList())).toString());
amd.set(InventoryAttribute.MS_REMEDIATIONS, new JSONArray(msrcRemediations.stream().map(MsrcRemediation::toJson).collect(Collectors.toList())).toString());
}
@Override
public void appendFromMap(Map map) {
super.appendFromMap(map);
try {
if (map.containsKey("affectedProducts")) {
this.addAffectedProducts(((List) map.get("affectedProducts")).stream().map(Object::toString).collect(Collectors.toSet()));
}
if (map.containsKey("msThreats")) {
this.addMsThreats(((List>) map.get("msThreats")));
}
if (map.containsKey("msRemediations")) {
this.addMsRemediations(((List>) map.get("msRemediations")));
}
} catch (Exception e) {
LOG.error("Error parsing MSRC Advisor entry from map:\n" + map, e);
}
}
@Override
public void appendToJson(JSONObject json) {
super.appendToJson(json);
json.put("affectedProducts", affectedProducts.stream().collect(CustomCollectors.toJsonArray()));
json.put("msThreats", msThreats.stream().map(MsThreat::toJson).collect(CustomCollectors.toJsonArray()));
json.put("msRemediations", msrcRemediations.stream().map(MsrcRemediation::toJson).collect(CustomCollectors.toJsonArray()));
}
@Override
public void appendFromDocument(Document document) {
super.appendFromDocument(document);
this.addAffectedProducts(new JSONArray(document.get("affectedProducts")).toList().stream().map(Object::toString).collect(Collectors.toSet()));
this.addMsThreats(new JSONArray(document.get("msThreats")));
this.addMsRemediations(new JSONArray(document.get("msRemediations")));
}
@Override
public void appendToDocument(Document document) {
super.appendToDocument(document);
document.add(new TextField("affectedProducts", affectedProducts.stream().collect(CustomCollectors.toJsonArray()).toString(), Field.Store.YES));
document.add(new TextField("msThreats", msThreats.stream().map(MsThreat::toJson).collect(CustomCollectors.toJsonArray()).toString(), Field.Store.YES));
document.add(new TextField("msRemediations", msrcRemediations.stream().map(MsrcRemediation::toJson).collect(CustomCollectors.toJsonArray()).toString(), Field.Store.YES));
}
/* PARSING OF MSRC ADVISORIES */
public static List fromDownloadXml(org.w3c.dom.Document document) {
final List entries = new ArrayList<>();
final NodeList vulnerabilityElements = document.getElementsByTagName("vuln:Vulnerability");
for (int i = 0; i < vulnerabilityElements.getLength(); i++) {
final Element vulnerabilityElement = (Element) vulnerabilityElements.item(i);
final MsrcAdvisorEntry entry = fromDownloadXmlVulnerabilityElement(vulnerabilityElement);
entries.add(entry);
}
return entries;
}
public static MsrcAdvisorEntry fromDownloadXmlVulnerabilityElement(Element vulnerabilityElement) {
final MsrcAdvisorEntry entry = new MsrcAdvisorEntry();
final String title = vulnerabilityElement.getElementsByTagName("vuln:Title").item(0).getTextContent();
entry.setSummary(title);
final String cve = vulnerabilityElement.getElementsByTagName("vuln:CVE").item(0).getTextContent();
if (VulnerabilityTypeStore.CVE.patternMatchesId(cve)) {
entry.addReferencedVulnerability(VulnerabilityTypeStore.CVE, cve);
}
final boolean isAdv = cve.contains("ADV");
String msrcId = cve.replaceAll("(MSRC-)?(CVE|CAN)-?", "")
.replaceAll(".*?(\\d{1,4})-(\\d+).*", "$1-$2")
.replace("ADV", "");
msrcId = isAdv ? "ADV" + msrcId : "MSRC-CVE-" + msrcId;
entry.setId(msrcId);
final Element notesElement = (Element) vulnerabilityElement.getElementsByTagName("vuln:Notes").item(0);
final NodeList noteElements = notesElement.getElementsByTagName("vuln:Note");
for (int j = 0; j < noteElements.getLength(); j++) {
final Element noteElement = (Element) noteElements.item(j);
final String noteText = Arrays.stream(noteElement.getTextContent().split("\n"))
.map(e -> e.replaceAll("\\s{2,}", " "))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.joining("\n"));
if (StringUtils.hasText(noteText)) {
final String noteType = noteElement.getAttribute("Type");
final String noteTitle = noteElement.getAttribute("Title");
final String noteTitleJoined = noteTitle.equals(noteType) ? noteTitle : noteType + ": " + noteTitle;
entry.addDescription(DescriptionParagraph.fromTitleAndContent(noteTitleJoined, noteText));
}
}
final Element productStatusesElement = (Element) vulnerabilityElement.getElementsByTagName("vuln:ProductStatuses").item(0);
final NodeList statusElements = productStatusesElement.getElementsByTagName("vuln:Status");
for (int j = 0; j < statusElements.getLength(); j++) {
final Element statusElement = (Element) statusElements.item(j);
if (statusElement.getAttribute("Type").equals("Known Affected")) {
final NodeList productIdElements = statusElement.getElementsByTagName("vuln:ProductID");
for (int k = 0; k < productIdElements.getLength(); k++) {
final Element productIdElement = (Element) productIdElements.item(k);
final String productId = productIdElement.getTextContent();
entry.addAffectedProduct(productId);
}
} else {
LOG.info("Unknown status type: {}", statusElement.getAttribute("Type"));
}
}
final Element threatsElement = (Element) vulnerabilityElement.getElementsByTagName("vuln:Threats").item(0);
final NodeList threatElements = threatsElement.getElementsByTagName("vuln:Threat");
for (int j = 0; j < threatElements.getLength(); j++) {
final Element threatElement = (Element) threatElements.item(j);
final String threatType = threatElement.getAttribute("Type");
final String threatDescription = threatElement.getElementsByTagName("vuln:Description").item(0).getTextContent()
.replace("\n", " ")
.replaceAll(" +", " ")
.trim();
final Node productIdItem = threatElement.getElementsByTagName("vuln:ProductID").item(0);
final String threatProductId;
if (productIdItem == null) {
threatProductId = null;
} else {
threatProductId = productIdItem.getTextContent();
}
entry.addMsThreat(new MsThreat(threatType, threatProductId, threatDescription));
}
for (MsThreat msThreat : entry.getMsThreats()) {
if (StringUtils.isEmpty(msThreat.getProductId())) {
entry.setThreat((msThreat.getType() == null ? "" : msThreat.getType() + ": ") + msThreat.getDescription().replaceAll("([;:])(?! )", "$1 "));
}
}
final Element remediationsElement = (Element) vulnerabilityElement.getElementsByTagName("vuln:Remediations").item(0);
final NodeList remediationElements = remediationsElement.getElementsByTagName("vuln:Remediation");
for (int i = 0; i < remediationElements.getLength(); i++) {
final Element remediationElement = (Element) remediationElements.item(i);
final String remediationType = remediationElement.getAttribute("Type");
final String remediationDescription = remediationElement.getElementsByTagName("vuln:Description").item(0).getTextContent()
.replace("\n", " ")
.replaceAll(" +", " ")
.trim();
final String remediationUrl = remediationElement.getElementsByTagName("vuln:URL").item(0) == null ?
null : remediationElement.getElementsByTagName("vuln:URL").item(0).getTextContent();
final String remediationSupercedence = remediationElement.getElementsByTagName("vuln:Supercedence").item(0) == null ?
null : remediationElement.getElementsByTagName("vuln:Supercedence").item(0).getTextContent();
final Set remediationProductIds = new HashSet<>();
final NodeList remediationProductIdElements = remediationElement.getElementsByTagName("vuln:ProductID");
for (int j = 0; j < remediationProductIdElements.getLength(); j++) {
final Element remediationProductIdElement = (Element) remediationProductIdElements.item(j);
remediationProductIds.add(remediationProductIdElement.getTextContent());
}
final String remediationSubType = remediationElement.getElementsByTagName("vuln:SubType").item(0) == null ?
null : remediationElement.getElementsByTagName("vuln:SubType").item(0).getTextContent();
final String remediationFixedBuild = remediationElement.getElementsByTagName("vuln:FixedBuild").item(0) == null ?
null : remediationElement.getElementsByTagName("vuln:FixedBuild").item(0).getTextContent();
final MsrcRemediation remediation = new MsrcRemediation(remediationType, remediationSubType, remediationDescription, StringUtils.isEmpty(remediationUrl) ? null : Reference.fromTitleAndUrl(remediationDescription, remediationUrl), remediationProductIds, remediationFixedBuild, remediationSupercedence);
entry.addMsRemediation(remediation);
}
final Element cvssScoreSetsElement = (Element) vulnerabilityElement.getElementsByTagName("vuln:CVSSScoreSets").item(0);
final NodeList scoreSetElements = cvssScoreSetsElement.getElementsByTagName("vuln:ScoreSet");
final Map> identicalProductVectors = new HashMap<>();
for (int j = 0; j < scoreSetElements.getLength(); j++) {
final Element scoreSetElement = (Element) scoreSetElements.item(j);
final String vector = scoreSetElement.getElementsByTagName("vuln:Vector").item(0).getTextContent();
final String productId = scoreSetElement.getElementsByTagName("vuln:ProductID").item(0).getTextContent();
identicalProductVectors.computeIfAbsent(new Cvss3P1(vector), k -> new HashSet<>()).add(productId);
}
final CvssSource msrcCvss3Source = new CvssSource(KnownCvssEntities.MSRC, Cvss3P1.class);
for (Map.Entry> cvssProductsEntry : identicalProductVectors.entrySet()) {
final Cvss3P1 cvssVector = cvssProductsEntry.getKey();
final Set productIds = cvssProductsEntry.getValue();
final JSONObject applicabilityCondition = new JSONObject()
.put(CvssConditionAttributes.MATCHES_ON_MS_PRODUCT_ID, new JSONArray(productIds));
final CvssVector effectiveVector = cvssVector.deriveAddSource(msrcCvss3Source);
effectiveVector.putAllApplicabilityCondition(applicabilityCondition);
entry.getCvssVectors().addCvssVector(effectiveVector);
}
final Element revisionHistoryElement = (Element) vulnerabilityElement.getElementsByTagName("vuln:RevisionHistory").item(0);
final NodeList revisionElements = revisionHistoryElement.getElementsByTagName("vuln:Revision");
final List revisionDates = new ArrayList<>();
for (int j = 0; j < revisionElements.getLength(); j++) {
final Element revisionElement = (Element) revisionElements.item(j);
final String dateString = revisionElement.getElementsByTagName("cvrf:Date").item(0).getTextContent();
final Date date = TimeUtils.tryParse(dateString);
if (date != null) {
revisionDates.add(date);
}
}
revisionDates.sort(Comparator.comparing(Date::getTime));
entry.setCreateDate(!revisionDates.isEmpty() ? revisionDates.get(0) : null);
entry.setUpdateDate(!revisionDates.isEmpty() ? revisionDates.get(revisionDates.size() - 1) : null);
for (DescriptionParagraph description : entry.getDescription()) {
entry.addReferencedSecurityAdvisories(AdvisoryTypeStore.MSRC, AdvisoryTypeStore.MSRC.fromFreeText(description.getContent()));
}
if (StringUtils.isEmpty(entry.getId())) {
LOG.warn("No id found for MSRC entry {}", entry.toJson());
}
return entry;
}
public static Set getAllMsrcProductIds(Collection artifacts) {
return artifacts.stream()
.filter(a -> StringUtils.hasText(a.get(InventoryAttribute.MS_PRODUCT_ID.getKey())))
.map(a -> a.get(InventoryAttribute.MS_PRODUCT_ID.getKey()))
.map(InventoryEnricher::splitVulnerabilitiesCsv)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}
}