com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.VulnerabilityStatus Maven / Gradle / Ivy
The newest version!
/*
* 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.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus;
import com.metaeffekt.artifact.analysis.utils.*;
import com.metaeffekt.artifact.analysis.vulnerability.CommonEnumerationUtil;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.filter.FilterAttribute;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.validation.VulnerabilityStatusValidation;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.validation.VulnerabilityStatusValidationException;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.warnings.InventoryWarningEntry;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.warnings.InventoryWarnings;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeIdentifier;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import com.networknt.schema.SpecVersion;
import lombok.Getter;
import lombok.Setter;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;
import org.metaeffekt.core.inventory.processor.report.configuration.CentralSecurityPolicyConfiguration;
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.MultiScoreCvssVector;
import org.metaeffekt.core.security.cvss.v2.Cvss2;
import org.metaeffekt.core.security.cvss.v3.Cvss3P1;
import org.metaeffekt.core.security.cvss.v4P0.Cvss4P0;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import us.springett.parsers.cpe.Cpe;
import java.io.File;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Setter
@Getter
public class VulnerabilityStatus {
private final static Logger LOG = LoggerFactory.getLogger(VulnerabilityStatus.class);
public static boolean LOG_MATCHING_CRITERIA = false;
private VulnerabilityStatusValidation validation;
private final Set affectedVulnerabilitiesFilters = new HashSet<>();
private final Set affectedVulnerabilities = new HashSet<>();
private final Set affectedCpe = new HashSet<>();
private final Set affectedCwe = new HashSet<>();
private Scope scope = Scope.ARTIFACT;
File originYamlFile;
private String title;
private String acceptedBy, acceptedDate, reportedBy, reportedDate;
private Cvss2 cvss2, cvss2Lower, cvss2Higher;
private Cvss3P1 cvss3, cvss3Lower, cvss3Higher;
private Cvss4P0 cvss4, cvss4Lower, cvss4Higher;
private final List reviewedAdvisories = new ArrayList<>();
private final Set statusHistory = new TreeSet<>();
public VulnerabilityStatus() {
}
public VulnerabilityStatus setCvss2(Cvss2 cvss2) {
this.cvss2 = cvss2;
return this;
}
public VulnerabilityStatus setCvss3P1(Cvss3P1 cvss3) {
this.cvss3 = cvss3;
return this;
}
public void setCvss3P1Lower(Cvss3P1 cvss3Lower) {
this.cvss3Lower = cvss3Lower;
}
public void setCvss3P1Higher(Cvss3P1 cvss3Higher) {
this.cvss3Higher = cvss3Higher;
}
public VulnerabilityStatus setCvss4(Cvss4P0 cvss4) {
this.cvss4 = cvss4;
return this;
}
public VulnerabilityStatus setScope(Scope scope) {
this.scope = scope;
this.statusHistory.forEach(e -> e.setScope(this.scope));
return this;
}
public VulnerabilityStatus setScope(String scope) {
this.scope = Scope.fromString(scope);
this.statusHistory.forEach(e -> e.setScope(this.scope));
return this;
}
public VulnerabilityStatus setAcceptedBy(String acceptedBy) {
this.acceptedBy = acceptedBy;
return this;
}
public VulnerabilityStatus setAcceptedDate(String acceptedDate) {
this.acceptedDate = acceptedDate;
return this;
}
public VulnerabilityStatus setReportedBy(String reportedBy) {
this.reportedBy = reportedBy;
return this;
}
public VulnerabilityStatus setReportedDate(String reportedDate) {
this.reportedDate = reportedDate;
return this;
}
public VulnerabilityStatus addReviewedAdvisoryEntry(String id, String comment) {
return addReviewedAdvisoryEntry(new VulnerabilityStatusReviewedEntry(id, comment));
}
public VulnerabilityStatus addReviewedAdvisoryEntry(String id) {
return addReviewedAdvisoryEntry(new VulnerabilityStatusReviewedEntry(id, null));
}
public VulnerabilityStatus addReviewedAdvisoryEntries(Collection entries) {
entries.forEach(this::addReviewedAdvisoryEntry);
return this;
}
public VulnerabilityStatus addReviewedAdvisoryEntry(VulnerabilityStatusReviewedEntry entry) {
// check if the advisory has already been reviewed. in this case, add the comment to the existing entry
final Optional existingEntry = reviewedAdvisories.stream()
.filter(e -> e.getId().equals(entry.getId()))
.findFirst();
if (existingEntry.isPresent()) {
final String existingComment = existingEntry.get().getComment();
if (StringUtils.hasText(existingComment) && StringUtils.hasText(entry.getComment())) {
existingEntry.get().setComment(existingComment + "; " + entry.getComment());
} else if (StringUtils.hasText(entry.getComment())) {
existingEntry.get().setComment(entry.getComment());
} else {
existingEntry.get().setComment(existingComment);
}
} else {
reviewedAdvisories.add(entry.clone());
}
return this;
}
public VulnerabilityStatus addAffectedCpe(String cpe) {
if (!cpe.startsWith("cpe")) {
LOG.warn("CPE in status file might not be a valid CPE identifier [{}]", affectedVulnerabilities);
}
this.affectedCpe.add(cpe);
return this;
}
public VulnerabilityStatus addAffectedVulnerability(String vulnerability) {
this.affectedVulnerabilities.add(vulnerability);
return this;
}
public VulnerabilityStatus addAffectedVulnerabilitiesFilter(FilterAttribute filter) {
this.affectedVulnerabilitiesFilters.add(filter);
return this;
}
public VulnerabilityStatus addAffectedCwe(String cwe) {
if (!cwe.startsWith("CWE")) {
LOG.warn("CWE in status file might not be a valid CWE identifier [{}]", cwe);
}
this.affectedCwe.add(cwe);
return this;
}
public VulnerabilityStatus addHistoryEntry(VulnerabilityStatusHistoryEntry entry) {
statusHistory.add(entry);
return this;
}
public VulnerabilityStatus addHistoryEntries(Collection entries) {
entries.forEach(this::addHistoryEntry);
return this;
}
public VulnerabilityStatus removeHistoryEntry(VulnerabilityStatusHistoryEntry entry) {
statusHistory.remove(entry);
return this;
}
public VulnerabilityStatus clearHistoryEntries() {
statusHistory.clear();
return this;
}
public List getStatusHistory() {
return Collections.unmodifiableList(new ArrayList<>(statusHistory));
}
public Set getStatusHistorySet() {
return statusHistory;
}
public Set getStatusHistoryModifiable() {
return statusHistory;
}
public VulnerabilityStatusHistoryEntry getLatestActiveStatusHistoryEntry() {
return statusHistory.stream()
.filter(VulnerabilityStatusHistoryEntry::isActive)
.findFirst()
.orElse(null);
}
public boolean isLatestStatusHistoryEntryOfType(String type) {
if (StringUtils.isEmpty(type)) return false;
final VulnerabilityStatusHistoryEntry latestEntry = getLatestActiveStatusHistoryEntry();
if (latestEntry == null) return false;
return type.equals(latestEntry.getStatus());
}
public Set getAffectedVulnerabilitiesWithoutWildcards() {
return affectedVulnerabilities.stream().filter(c -> !WildcardUtilities.isWildcardPattern(c)).collect(Collectors.toSet());
}
public List getReviewedAdvisories(AdvisoryTypeIdentifier> type) {
return reviewedAdvisories.stream()
.filter(a -> a.getAdvisor() == type)
.sorted(Comparator.comparing(e -> e.getAdvisor().name()))
.collect(Collectors.toList());
}
public Date getAcceptedDateAsDate() {
return TimeUtils.tryParse(acceptedDate);
}
public Date getReportedDateAsDate() {
return TimeUtils.tryParse(reportedDate);
}
public boolean hasReviewedAdvisories() {
return !reviewedAdvisories.isEmpty();
}
public boolean hasReportedBy() {
return StringUtils.hasText(reportedBy) || StringUtils.hasText(reportedDate);
}
public boolean hasAcceptedBy() {
return StringUtils.hasText(acceptedBy) || StringUtils.hasText(acceptedDate);
}
public String generateAcceptedByDateString() {
final StringJoiner builder = new StringJoiner(" ");
if (StringUtils.hasText(acceptedBy)) builder.add(acceptedBy);
else builder.add("no author");
if (StringUtils.hasText(acceptedDate)) builder.add("(" + acceptedDate + ")");
return builder.toString();
}
public String generateReportedByDateString() {
final StringJoiner builder = new StringJoiner(" ");
if (StringUtils.hasText(reportedBy)) builder.add(reportedBy);
else builder.add("no author");
if (StringUtils.hasText(reportedDate)) builder.add("(" + reportedDate + ")");
return builder.toString();
}
public boolean hasAcceptedByInformation() {
return StringUtils.hasText(acceptedBy) || StringUtils.hasText(acceptedDate);
}
public boolean hasReportedByInformation() {
return StringUtils.hasText(reportedBy) || StringUtils.hasText(reportedDate);
}
public Cvss3P1 getCvss3P1() {
return cvss3;
}
public Cvss3P1 getCvss3P1Lower() {
return cvss3Lower;
}
public Cvss3P1 getCvss3P1Higher() {
return cvss3Higher;
}
public boolean hasCvss2() {
return isCvssDefined(cvss2);
}
public boolean hasCvss3P1() {
return isCvssDefined(cvss3);
}
public boolean hasCvss4() {
return isCvssDefined(cvss4);
}
public boolean hasCvss2Lower() {
return isCvssDefined(cvss2Lower);
}
public boolean hasCvss2Higher() {
return isCvssDefined(cvss2Higher);
}
public boolean hasCvss3P1Lower() {
return isCvssDefined(cvss3Lower);
}
public boolean hasCvss3P1Higher() {
return isCvssDefined(cvss3Higher);
}
public boolean hasAnyCvss2Information() {
return hasCvss2() || hasCvss2Lower() || hasCvss2Higher();
}
public boolean hasAnyCvss3P1Information() {
return hasCvss3P1() || hasCvss3P1Lower() || hasCvss3P1Higher();
}
public boolean hasAnyCvss4Information() {
return hasCvss4() || hasCvss4Lower() || hasCvss4Higher();
}
public boolean hasCvss4Lower() {
return isCvssDefined(cvss4Lower);
}
public boolean hasCvss4Higher() {
return isCvssDefined(cvss4Higher);
}
private boolean isCvssDefined(MultiScoreCvssVector cvss) {
return cvss != null && (cvss.isAnyBaseDefined() || cvss.isAnyTemporalDefined() || cvss.isAnyEnvironmentalDefined());
}
private boolean isCvssDefined(Cvss4P0 cvss) {
return cvss != null && (cvss.isAnyBaseDefined() || cvss.isAnyThreatDefined() || cvss.isAnyEnvironmentalDefined());
}
private boolean isCvssDefined(CvssVector cvss) {
return cvss != null && cvss.isAnyBaseDefined();
}
public void applyCvss2(Cvss2 cvss2) {
if (cvss2 == null) return;
cvss2.applyVector(this.cvss2);
cvss2.applyVectorPartsIfHigher(cvss2Higher, CvssVector::getOverallScore);
cvss2.applyVectorPartsIfLower(cvss2Lower, CvssVector::getOverallScore);
}
public void applyCvss3P1(Cvss3P1 cvss3) {
if (cvss3 == null) return;
cvss3.applyVector(this.cvss3);
cvss3.applyVectorPartsIfHigher(cvss3Higher, CvssVector::getOverallScore);
cvss3.applyVectorPartsIfLower(cvss3Lower, CvssVector::getOverallScore);
}
public void applyCvss4(Cvss4P0 cvss4) {
if (cvss4 == null) return;
cvss4.applyVector(this.cvss4);
cvss4.applyVectorPartsIfHigher(cvss4Higher, CvssVector::getOverallScore);
cvss4.applyVectorPartsIfLower(cvss4Lower, CvssVector::getOverallScore);
}
public boolean isScope(Scope scope) {
return this.scope == scope;
}
public List getLabelFilteredStatusHistory(Collection activeLabels) {
return statusHistory.stream()
.map(VulnerabilityStatusHistoryEntry::clone)
.peek(entry -> entry.setActive(entry.isIncluded(activeLabels)))
.sorted()
.collect(Collectors.toList());
}
public List getLabelFilteredStatusHistory(String[] activeLabels) {
return getLabelFilteredStatusHistory(Arrays.asList(activeLabels));
}
public MatchType affectsRetainCondition(Vulnerability vulnerability) {
if (vulnerability == null) {
throw new IllegalArgumentException("VulnerabilityMetaData must not be null");
}
return affectsRetainCondition(
vulnerability,
CommonEnumerationUtil.parseCpes(vulnerability.getAdditionalAttribute(VulnerabilityMetaData.Attribute.PRODUCT_URIS)),
vulnerability.getCwes()
);
}
public boolean affects(Vulnerability vulnerability) {
if (vulnerability == null) {
throw new IllegalArgumentException("VulnerabilityMetaData must not be null");
}
return affects(
vulnerability,
CommonEnumerationUtil.parseCpes(vulnerability.getAdditionalAttribute(VulnerabilityMetaData.Attribute.PRODUCT_URIS)),
vulnerability.getCwes()
);
}
public boolean affects(Vulnerability vulnerability, Collection vmdCpe, Collection vmdCwe) {
return affectsRetainCondition(vulnerability, vmdCpe, vmdCwe) != null;
}
public MatchType affectsRetainCondition(Vulnerability vulnerability, Collection vmdCpe, Collection vmdCwe) {
if (scope == Scope.INVENTORY) {
return MatchType.INVENTORY_SCOPE;
}
if (vulnerability.getId() != null) {
if (affectsByVulnerability(vulnerability)) {
return MatchType.VULNERABILITY_NAME;
} else {
if (affectsByVulnerabilityFilter(vulnerability)) {
return MatchType.FILTER_ATTRIBUTE;
}
}
}
if (vmdCpe != null) {
if (affectsByCpe(vmdCpe)) {
return MatchType.CPE;
}
}
if (vmdCwe != null) {
if (affectsByCwe(vmdCwe)) {
return MatchType.CWE;
}
}
return null;
}
private boolean affectsByCpe(Collection vmdCpe) {
for (String cpe : affectedCpe) {
final Cpe compareCpe = CommonEnumerationUtil.parseCpe(cpe).orElse(null);
if (compareCpe == null) {
LOG.warn("CPE [{}] not valid identifier", cpe);
}
for (Cpe vmdCpeEntry : vmdCpe) {
if (CommonEnumerationUtil.compareCpeUsingWildcards(vmdCpeEntry, compareCpe)) {
return true;
}
}
}
return false;
}
private boolean affectsByCwe(Collection vmdCwes) {
for (String cwe : vmdCwes) {
if (affectedCwe.contains(cwe)) {
return true;
}
}
return false;
}
private boolean affectsByVulnerability(Vulnerability vulnerability) {
if (affectedVulnerabilities.contains(vulnerability.getId())) {
if (affectedVulnerabilitiesFilters.isEmpty()) {
return true;
} else {
return affectsByVulnerabilityFilter(vulnerability);
}
}
// cve identifiers might contain wildcards; check again using regex
for (Pattern pattern : affectedVulnerabilities.stream()
.filter(WildcardUtilities::isWildcardPattern)
.map(WildcardUtilities::convertWildcardStringToPattern)
.collect(Collectors.toList())) {
if (pattern.matcher(vulnerability.getId()).matches()) {
if (affectedVulnerabilitiesFilters.isEmpty()) {
return true;
} else {
return affectsByVulnerabilityFilter(vulnerability);
}
}
}
return false;
}
private boolean affectsByVulnerabilityFilter(Vulnerability vulnerability) {
if (affectedVulnerabilitiesFilters.isEmpty()) {
return false;
}
if (LOG_MATCHING_CRITERIA) {
LOG.info("Checking vulnerability [{}] against filter attributes: {}",
vulnerability.getId(),
affectedVulnerabilitiesFilters.stream().map(FilterAttribute::toString).map(fa -> fa.replace("\n", "\\n")).collect(Collectors.joining(", "))
);
}
for (FilterAttribute filter : affectedVulnerabilitiesFilters) {
if (filter.matches(vulnerability)) {
if (LOG_MATCHING_CRITERIA) {
LOG.info("Vulnerability [{}] matches filter attribute: {}", vulnerability.getId(), filter);
}
return true;
}
}
return false;
}
public void reorderChronologically(Vulnerability vulnerability, boolean isInsignificant, double insignificantThreshold) {
final List reordered = VulnerabilityStatusHistoryEntry.reorderChronologically(this, vulnerability, isInsignificant, insignificantThreshold);
this.statusHistory.clear();
this.statusHistory.addAll(reordered);
}
public void checkValidation(Inventory inventory, Vulnerability vulnerability, boolean failOnValidationError) {
if (this.validation != null) {
if (inventory == null) {
LOG.warn("Inventory is null, skipping validation on status file {}", originYamlFile);
}
try {
this.validation.validateInventory(inventory, vulnerability.getId());
} catch (VulnerabilityStatusValidationException e) {
final StringBuilder errorMessage = new StringBuilder();
errorMessage.append("Vulnerability status validation failed on vulnerability ")
.append(vulnerability.getId()).append(": ")
.append(e.getMessage());
if (originYamlFile != null) {
errorMessage.append("\nfrom status file ").append(originYamlFile);
}
if (failOnValidationError) {
throw new RuntimeException(errorMessage.toString(), e);
}
LOG.error(errorMessage.toString(), e);
new InventoryWarnings(inventory).addVulnerabilityWarning(new InventoryWarningEntry<>(vulnerability.toBaseModel(),
"Vulnerability status validation failed: " + e.getMessage(),
originYamlFile.getAbsolutePath()
));
}
}
}
public void applyToVulnerability(Vulnerability vulnerability) {
if (vulnerability == null) {
throw new IllegalArgumentException("Vulnerability must not be null");
}
vulnerability.setVulnerabilityStatus(this);
vulnerability.clearNonTransferableStatusDetails();
// reviewed advisories
if (!this.reviewedAdvisories.isEmpty()) {
final JSONArray outputReviewedAdvisoriesJson = VulnerabilityStatusReviewedEntry.toJsonArray(this.reviewedAdvisories);
if (!outputReviewedAdvisoriesJson.isEmpty()) {
vulnerability.setAdditionalAttribute(InventoryAttribute.REVIEWED_ADVISORIES, outputReviewedAdvisoriesJson.toString());
}
}
/*if (this.hasReviewedAdvisories()) {
VulnerabilityStatusReviewedEntry.appendToVulnerability(vulnerability, this.getReviewedAdvisories());
}*/
// CVSS
setCvssVectorIfPresent(vulnerability, Cvss2.class, this::hasCvss2, this::getCvss2, KnownCvssEntities.ASSESSMENT_ALL);
setCvssVectorIfPresent(vulnerability, Cvss2.class, this::hasCvss2Lower, this::getCvss2Lower, KnownCvssEntities.ASSESSMENT_LOWER);
setCvssVectorIfPresent(vulnerability, Cvss2.class, this::hasCvss2Higher, this::getCvss2Higher, KnownCvssEntities.ASSESSMENT_HIGHER);
setCvssVectorIfPresent(vulnerability, Cvss3P1.class, this::hasCvss3P1, this::getCvss3P1, KnownCvssEntities.ASSESSMENT_ALL);
setCvssVectorIfPresent(vulnerability, Cvss3P1.class, this::hasCvss3P1Lower, this::getCvss3P1Lower, KnownCvssEntities.ASSESSMENT_LOWER);
setCvssVectorIfPresent(vulnerability, Cvss3P1.class, this::hasCvss3P1Higher, this::getCvss3P1Higher, KnownCvssEntities.ASSESSMENT_HIGHER);
setCvssVectorIfPresent(vulnerability, Cvss4P0.class, this::hasCvss4, this::getCvss4, KnownCvssEntities.ASSESSMENT_ALL);
setCvssVectorIfPresent(vulnerability, Cvss4P0.class, this::hasCvss4Lower, this::getCvss4Lower, KnownCvssEntities.ASSESSMENT_LOWER);
setCvssVectorIfPresent(vulnerability, Cvss4P0.class, this::hasCvss4Higher, this::getCvss4Higher, KnownCvssEntities.ASSESSMENT_HIGHER);
// history entries
final List exportHistory = this.getStatusHistory();
if (!exportHistory.isEmpty()) {
final JSONArray historyJson = exportHistory.stream()
.map(VulnerabilityStatusHistoryEntry::toJson)
.collect(CustomCollectors.toJsonArray());
vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_HISTORY.getKey(), historyJson.toString());
}
// accepted / reported by
if (this.acceptedBy != null || this.acceptedDate != null) {
vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_ACCEPTED.getKey(), this.generateAcceptedByDateString());
} else {
vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_ACCEPTED.getKey(), null);
}
if (this.reportedBy != null || this.reportedDate != null) {
vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_REPORTED.getKey(), this.generateReportedByDateString());
} else {
vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_REPORTED.getKey(), null);
}
// title
if (this.title != null) {
vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_TITLE.getKey(), this.title);
} else {
vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_TITLE.getKey(), null);
}
// fill other fields specific to the latest entry, required for report generation
final VulnerabilityStatusHistoryEntry latestHistoryEntry = this.getLatestActiveStatusHistoryEntry();
if (latestHistoryEntry != null) {
vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.STATUS, latestHistoryEntry.getStatus());
vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.RATIONALE, latestHistoryEntry.getRationale());
vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.RISK, latestHistoryEntry.getRisk());
vulnerability.setAdditionalAttribute(InventoryAttribute.MEASURES.getKey(), latestHistoryEntry.getMeasures());
} else {
vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.STATUS, null);
vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.RATIONALE, null);
vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.RISK, null);
vulnerability.setAdditionalAttribute(InventoryAttribute.MEASURES.getKey(), null);
}
}
public void appendToVulnerabilityStatus(VulnerabilityStatus appendToStatus, Collection activeLabels) {
appendAllExceptStatusHistoryToVulnerabilityStatus(appendToStatus);
appendStatusHistoryOnlyToVulnerabilityStatus(appendToStatus, activeLabels);
}
public void appendAllExceptStatusHistoryToVulnerabilityStatus(VulnerabilityStatus appendToStatus) {
// CVSS
setCvssVectorIfPresent(this::hasCvss2, this::getCvss2, appendToStatus::setCvss2);
setCvssVectorIfPresent(this::hasCvss2Lower, this::getCvss2Lower, appendToStatus::setCvss2Lower);
setCvssVectorIfPresent(this::hasCvss2Higher, this::getCvss2Higher, appendToStatus::setCvss2Higher);
setCvssVectorIfPresent(this::hasCvss3P1, this::getCvss3P1, appendToStatus::setCvss3P1);
setCvssVectorIfPresent(this::hasCvss3P1Lower, this::getCvss3P1Lower, appendToStatus::setCvss3P1Lower);
setCvssVectorIfPresent(this::hasCvss3P1Higher, this::getCvss3P1Higher, appendToStatus::setCvss3P1Higher);
setCvssVectorIfPresent(this::hasCvss4, this::getCvss4, appendToStatus::setCvss4);
setCvssVectorIfPresent(this::hasCvss4Lower, this::getCvss4Lower, appendToStatus::setCvss4Lower);
setCvssVectorIfPresent(this::hasCvss4Higher, this::getCvss4Higher, appendToStatus::setCvss4Higher);
// reviewed advisories
if (hasReviewedAdvisories()) {
appendToStatus.addReviewedAdvisoryEntries(reviewedAdvisories);
}
// accepted / reported by
if (acceptedBy != null) {
appendToStatus.setAcceptedBy(acceptedBy);
}
if (acceptedDate != null) {
appendToStatus.setAcceptedDate(acceptedDate);
}
if (reportedBy != null) {
appendToStatus.setReportedBy(reportedBy);
}
if (reportedDate != null) {
appendToStatus.setReportedDate(reportedDate);
}
// title
if (title != null) {
appendToStatus.setTitle(title);
}
// affects fields
appendToStatus.getAffectedCpe().addAll(this.affectedCpe);
appendToStatus.getAffectedVulnerabilities().addAll(this.affectedVulnerabilities);
appendToStatus.getAffectedVulnerabilitiesFilters().addAll(this.affectedVulnerabilitiesFilters);
appendToStatus.getAffectedCwe().addAll(this.affectedCwe);
}
public void appendStatusHistoryOnlyToVulnerabilityStatus(VulnerabilityStatus appendToStatus, Collection activeLabels) {
// history entries
final List exportHistory = getLabelFilteredStatusHistory(activeLabels);
appendToStatus.addHistoryEntries(exportHistory);
}
private static void setCvssVectorIfPresent(Supplier hasCvss, Supplier getCvss, Consumer setStatusCvss) {
if (hasCvss.get()) {
setStatusCvss.accept(getCvss.get());
}
}
private static void setCvssVectorIfPresent(Vulnerability vulnerability, Class clazz, Supplier hasCvss, Supplier getCvss, CvssSource.CvssEntity entity) {
if (hasCvss.get()) {
final T vector = (T) getCvss.get().deriveAddSource(new CvssSource(KnownCvssEntities.ASSESSMENT, entity, clazz));
vulnerability.getCvssVectors().addCvssVector(vector);
}
}
public JSONObject toJson() {
final JSONObject json = new JSONObject();
json.put("cvss2", cvss2 != null ? String.valueOf(cvss2) : null);
json.put("cvss2Lower", cvss2Lower != null ? String.valueOf(cvss2Lower) : null);
json.put("cvss2Higher", cvss2Higher != null ? String.valueOf(cvss2Higher) : null);
json.put("cvss3", cvss3 != null ? String.valueOf(cvss3) : null);
json.put("cvss3Lower", cvss3Lower != null ? String.valueOf(cvss3Lower) : null);
json.put("cvss3Higher", cvss3Higher != null ? String.valueOf(cvss3Higher) : null);
json.put("cvss4", cvss4 != null ? String.valueOf(cvss4) : null);
json.put("cvss4Lower", cvss4Lower != null ? String.valueOf(cvss4Lower) : null);
json.put("cvss4Higher", cvss4Higher != null ? String.valueOf(cvss4Higher) : null);
final JSONArray reviewed = reviewedAdvisories.stream().map(VulnerabilityStatusReviewedEntry::toJson).collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
json.put("reviewedAdvisories", reviewed);
final JSONArray history = statusHistory.stream().map(VulnerabilityStatusHistoryEntry::toJson).collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
json.put("statusHistory", history);
json.put("acceptedBy", acceptedBy);
json.put("acceptedDate", acceptedDate);
json.put("reportedBy", reportedBy);
json.put("reportedDate", reportedDate);
json.put("title", title);
final JSONArray affectedVulnerabilities = this.affectedVulnerabilities.stream().collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
json.put("affectedVulnerabilities", affectedVulnerabilities);
final JSONArray affectedCpe = this.affectedCpe.stream().collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
json.put("affectedCpe", affectedCpe);
final JSONArray affectedCwe = this.affectedCwe.stream().collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
json.put("affectedCwe", affectedCwe);
json.put("scope", scope);
return json;
}
public void appendFromJson(final JSONObject json) {
this.setCvss2(new Cvss2(json.optString("cvss2", null)));
this.setCvss2Lower(new Cvss2(json.optString("cvss2Lower", null)));
this.setCvss2Higher(new Cvss2(json.optString("cvss2Higher", null)));
this.setCvss3P1(new Cvss3P1(json.optString("cvss3", null)));
this.setCvss3P1Lower(new Cvss3P1(json.optString("cvss3Lower", null)));
this.setCvss3P1Higher(new Cvss3P1(json.optString("cvss3Higher", null)));
this.setCvss4(new Cvss4P0(json.optString("cvss4", null)));
this.setCvss4Lower(new Cvss4P0(json.optString("cvss4Lower", null)));
this.setCvss4Higher(new Cvss4P0(json.optString("cvss4Higher", null)));
final JSONArray reviewed = json.optJSONArray("reviewedAdvisories");
if (reviewed != null) {
for (int i = 0; i < reviewed.length(); i++) {
this.getReviewedAdvisories().add(VulnerabilityStatusReviewedEntry.fromMap(reviewed.getJSONObject(i).toMap()));
}
}
final JSONArray history = json.optJSONArray("statusHistory");
if (history != null) {
for (int i = 0; i < history.length(); i++) {
this.addHistoryEntry(VulnerabilityStatusHistoryEntry.fromMap(history.getJSONObject(i).toMap()));
}
}
this.setAcceptedBy(json.optString("acceptedBy", null));
this.setAcceptedDate(json.optString("acceptedDate", null));
this.setReportedBy(json.optString("reportedBy", null));
this.setReportedDate(json.optString("reportedDate", null));
this.setTitle(json.optString("title", null));
final JSONArray affectedVulnerabilities = json.optJSONArray("affectedVulnerabilities");
if (affectedVulnerabilities != null) {
for (int i = 0; i < affectedVulnerabilities.length(); i++) {
this.addAffectedVulnerability(affectedVulnerabilities.getString(i));
}
}
final JSONArray affectedCpe = json.optJSONArray("affectedCpe");
if (affectedCpe != null) {
for (int i = 0; i < affectedCpe.length(); i++) {
this.addAffectedCpe(affectedCpe.getString(i));
}
}
final JSONArray affectedCwe = json.optJSONArray("affectedCwe");
if (affectedCwe != null) {
for (int i = 0; i < affectedCwe.length(); i++) {
this.addAffectedCwe(affectedCwe.getString(i));
}
}
this.setScope(VulnerabilityStatus.Scope.fromString(json.optString("scope", null)));
}
@Override
public String toString() {
return "VulnerabilityStatus{" +
"statusHistory=" + statusHistory +
", validation=" + validation +
", affectedVulnerabilities=" + affectedVulnerabilities +
", affectedCpe=" + affectedCpe +
", affectedCwe=" + affectedCwe +
", reviewedAdvisories=" + reviewedAdvisories +
", title='" + title + '\'' +
", acceptedBy='" + acceptedBy + '\'' +
", acceptedDate='" + acceptedDate + '\'' +
", reportedBy='" + reportedBy + '\'' +
", reportedDate='" + reportedDate + '\'' +
", scope=" + scope +
", originYamlFile=" + originYamlFile +
'}';
}
public static List mergeStatusHistoryEntries(Collection> entries) {
return entries.stream()
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
}
public static Set findAffectedEntries(Collection vulnerabilityStatusHistories, Vulnerability vulnerability) {
return vulnerabilityStatusHistories.stream()
.filter(Objects::nonNull)
.filter(vsh -> vsh.affects(vulnerability))
.collect(Collectors.toSet());
}
public static Map> findAffectedEntriesRetainMatchingCondition(Collection vulnerabilityStatuses, Vulnerability vulnerability) {
final Map> result = new LinkedHashMap<>();
for (VulnerabilityStatus vulnerabilityStatus : vulnerabilityStatuses) {
final MatchType matchType = vulnerabilityStatus.affectsRetainCondition(vulnerability);
if (matchType != null) {
result.computeIfAbsent(matchType, k -> new ArrayList<>()).add(vulnerabilityStatus);
if (LOG_MATCHING_CRITERIA) {
LOG.info("Vulnerability [{}] uses match type [{}] for status [{}]", vulnerability.getId(), matchType,
vulnerabilityStatus.getOriginYamlFile() == null ? "with " + vulnerabilityStatus.statusHistory.size() + " status history entries " : vulnerabilityStatus.getOriginYamlFile().getName()
);
}
}
}
return result;
}
/**
* Attempts to assert that the provided file is a valid vulnerability status file.
* It will use the map and array schema to validate the file, returning if one of them applies and throwing the array schema exception if none applies.
*
* @param file the file to validate
* @param jsonSchemaValidationErrorsHandling Error handling mode.
*/
public static void assertVulnerabilityStatusFileValid(File file,
CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling jsonSchemaValidationErrorsHandling) {
try {
JsonSchemaValidator.assertResourceSchemaAppliesToYamlFile(
file,
"specification/jsonschema/vulnerability-status.json",
SpecVersion.VersionFlag.V201909,
"Vulnerability Status YAML",
jsonSchemaValidationErrorsHandling
);
} catch (RuntimeException mapTypeException) {
JsonSchemaValidator.assertResourceSchemaAppliesToYamlFile(
file,
"specification/jsonschema/vulnerability-status-array.json",
SpecVersion.VersionFlag.V201909,
"Vulnerability Status ARRAY YAML",
jsonSchemaValidationErrorsHandling
);
}
}
public enum Scope {
ARTIFACT,
INVENTORY;
public static Scope fromString(String scope) {
if (scope == null) {
return ARTIFACT;
}
switch (scope.trim().toLowerCase()) {
case "inventory":
return INVENTORY;
case "artifact":
default:
return ARTIFACT;
}
}
}
public enum MatchType {
INVENTORY_SCOPE,
VULNERABILITY_NAME,
CPE,
CWE,
FILTER_ATTRIBUTE;
public static > T findHighestPriority(Map affectedEntries) {
if (affectedEntries.containsKey(INVENTORY_SCOPE)) {
return affectedEntries.get(INVENTORY_SCOPE);
}
if (affectedEntries.containsKey(VULNERABILITY_NAME)) {
return affectedEntries.get(VULNERABILITY_NAME);
}
if (affectedEntries.containsKey(CPE)) {
return affectedEntries.get(CPE);
}
if (affectedEntries.containsKey(CWE)) {
return affectedEntries.get(CWE);
}
if (affectedEntries.containsKey(FILTER_ATTRIBUTE)) {
return affectedEntries.get(FILTER_ATTRIBUTE);
}
return null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy