com.metaeffekt.artifact.enrichment.matching.MsrcVulnerabilitiesByProductEnrichment 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.artifact.enrichment.matching;
import com.metaeffekt.artifact.analysis.utils.LazySupplier;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.enrichment.InventoryEnricher;
import com.metaeffekt.artifact.enrichment.configurations.MsrcVulnerabilitiesByProductEnrichmentConfiguration;
import com.metaeffekt.mirror.contents.advisory.MsrcAdvisorEntry;
import com.metaeffekt.mirror.contents.base.DataSourceIndicator;
import com.metaeffekt.mirror.contents.base.VulnerabilityContextInventory;
import com.metaeffekt.mirror.contents.msrcdata.MsrcProduct;
import com.metaeffekt.mirror.download.documentation.EnricherMetadata;
import com.metaeffekt.mirror.download.documentation.InventoryEnrichmentPhase;
import com.metaeffekt.mirror.query.MsrcAdvisorIndexQuery;
import com.metaeffekt.mirror.query.MsrcKbChainIndexQuery;
import com.metaeffekt.mirror.query.MsrcProductIndexQuery;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* The MSRC (Microsoft Security Response Center) Vulnerabilities by Products enrichment step is responsible for identifying vulnerabilities affecting Microsoft products within an inventory.
* This process works by linking artifacts (products) to their associated vulnerabilities using Microsoft’s product and advisory databases.
* The enrichment step uses various Microsoft data sources, including product IDs, advisories, and Knowledge Base (KB) chains, to ensure that relevant vulnerabilities, based on the product and its applied updates, are considered.
* What Happens During This Step
* When this step begins, the system first processes each artifact in the inventory to identify those associated with Microsoft products.
* It does this by checking for a Microsoft Product ID or name within the artifact's metadata.
* If an artifact contains such information, it is further analyzed to determine the specific Microsoft product it represents.
* Once the product is identified, the system extracts any relevant KB identifiers from the artifact.
* These KB identifiers represent updates or patches applied to the product and are relevant when determining whether a given vulnerability has already been resolved and should not be added to the inventory.
* The system then queries the Microsoft advisory data source to retrieve potential vulnerabilities for the product.
* First, it queries Microsoft advisories using the MSRC advisory index to find vulnerabilities linked to the product.
* This part is simple, as advisories directly contain the affected products, with them being a unique numerical id.
* Now the system can check these for any superseding KB identifiers, making sure that if the product has received newer updates, the patched vulnerabilities are filtered out.
* This is done by following the KB chain, which is a list of KBs that are superseded by other KBs.
* If any of the superseding KBs are found in the applied KB identifiers, the vulnerability is considered fixed.
* Using this, vulnerabilities are classified into three categories:
* those that remain applicable, those that are applicable advisories (Microsoft-internal ADV
-identifiers), and those that have been fixed by the applied KB updates.
* For applicable vulnerabilities, the system links them directly to the product (artifact) and stores them in the inventory.
* For fixed vulnerabilities, the system marks them as resolved and notes the KB that addressed them.
*/
@EnricherMetadata(
name = "MSRC Vulnerabilities by Products", phase = InventoryEnrichmentPhase.VULNERABILITY_MATCHING,
intermediateFileSuffix = "msrc-cve-from-product", mavenPropertyName = "msVulnerabilitiesByProductEnrichment"
)
public class MsrcVulnerabilitiesByProductEnrichment extends InventoryEnricher {
private final static Logger LOG = LoggerFactory.getLogger(MsrcVulnerabilitiesByProductEnrichment.class);
private final LazySupplier msrcProductQuery;
private final LazySupplier msrcAdvisorQuery;
private final LazySupplier msrcKbChainQuery;
private MsrcVulnerabilitiesByProductEnrichmentConfiguration configuration = new MsrcVulnerabilitiesByProductEnrichmentConfiguration();
public MsrcVulnerabilitiesByProductEnrichment(File baseMirrorDirectory) {
msrcProductQuery = new LazySupplier<>(() -> new MsrcProductIndexQuery(baseMirrorDirectory));
msrcAdvisorQuery = new LazySupplier<>(() -> new MsrcAdvisorIndexQuery(baseMirrorDirectory));
msrcKbChainQuery = new LazySupplier<>(() -> new MsrcKbChainIndexQuery(baseMirrorDirectory));
}
public void setConfiguration(MsrcVulnerabilitiesByProductEnrichmentConfiguration configuration) {
this.configuration = configuration;
}
@Override
public MsrcVulnerabilitiesByProductEnrichmentConfiguration getConfiguration() {
return configuration;
}
@Override
protected void performEnrichment(Inventory inventory) {
final MsrcAdvisorIndexQuery msrcAdvisorQuery = this.msrcAdvisorQuery.get();
final MsrcKbChainIndexQuery msrcKbChainQuery = this.msrcKbChainQuery.get();
final VulnerabilityContextInventory vInventory = VulnerabilityContextInventory.fromInventory(inventory);
for (final Artifact artifact : inventory.getArtifacts()) {
// find Microsoft product from artifact
final String artifactMsProductIdOrName = artifact.get(InventoryAttribute.MS_PRODUCT_ID.getKey());
if (artifactMsProductIdOrName == null) {
continue;
}
final List productList = Arrays.stream(artifactMsProductIdOrName.split(", ")).map(String::trim).collect(Collectors.toList());
if (productList.size() >= 2) {
LOG.warn("Multiple products found for artifact [{}], usually an artifact is only affected by a single product. Products: [{}]", artifact.getId(), artifactMsProductIdOrName);
}
for (String msProductIdOrName : productList) {
final MsrcProduct msrcProduct = findProductFromIdOrName(msProductIdOrName);
if (msrcProduct == null) {
LOG.error("Skipping over MS Product ID [{}] from Artifact [{}] as it cannot be found in local index, make sure that the ID is correct", msProductIdOrName, artifact.getId());
continue;
}
// extract KB Ids from artifact
final Set appliedMsKbIdentifiers = extractKbIdentifiers(artifact);
// find Microsoft vulnerabilities affected by product
final List msVulnerabilitiesOnProductFromAdvisors = msrcAdvisorQuery.findByProduct(msrcProduct).stream()
.map(MsrcAdvisorEntry::getId)
.map(id -> id.replace("MSRC-", ""))
.collect(Collectors.toList());
final List msVulnerabilitiesOnProductFromKbChains = msrcKbChainQuery.findVulnerabilitiesByProductId(msrcProduct.getId());
final Set allPotentialVulnerabilities = new HashSet<>();
allPotentialVulnerabilities.addAll(msVulnerabilitiesOnProductFromAdvisors);
allPotentialVulnerabilities.addAll(msVulnerabilitiesOnProductFromKbChains);
msrcKbChainQuery.collectSupersedingKbIdentifiers(allPotentialVulnerabilities, msProductIdOrName, appliedMsKbIdentifiers);
final Set msVulnerabilitiesNotFixed = new HashSet<>();
final Set msAdvisoriesNotFixed = new HashSet<>();
final Set msVulnerabilitiesAndAdvisoriesFixed = new HashSet<>();
for (String vulnerabilityId : allPotentialVulnerabilities) {
final boolean isFixed = msrcKbChainQuery.isVulnerabilityFixed(vulnerabilityId, msrcProduct.getId(), appliedMsKbIdentifiers);
if (isFixed) {
msVulnerabilitiesAndAdvisoriesFixed.add(vulnerabilityId);
} else {
if (vulnerabilityId.startsWith("ADV")) {
msAdvisoriesNotFixed.add(vulnerabilityId);
} else {
msVulnerabilitiesNotFixed.add(vulnerabilityId);
}
}
}
// convert to vulnerability instances
final DataSourceIndicator msrcProductDataSourceIndicator = DataSourceIndicator.msrcProduct(artifact, msrcProduct, appliedMsKbIdentifiers);
for (String v : msVulnerabilitiesNotFixed) {
vInventory.findOrCreateVulnerabilityByName(v)
.addMatchingSource(msrcProductDataSourceIndicator);
}
for (String v : msAdvisoriesNotFixed) {
vInventory.findOrCreateAdvisoryEntryByName(v, MsrcAdvisorEntry::new)
.addMatchingSource(msrcProductDataSourceIndicator);
}
for (String v : msVulnerabilitiesAndAdvisoriesFixed) {
vInventory.findOrCreateWithoutAddingVulnerabilityByName(v)
.manuallyAffectsArtifact(InventoryAttribute.VULNERABILITIES_FIXED_BY_KB, artifact);
}
artifact.set(InventoryAttribute.MS_SUPERSEDED_KB_IDENTIFIER.getKey(), String.join(", ", appliedMsKbIdentifiers));
LOG.info("Artifact [{}] with product [{}] ([{}]) and [{}] KB has [{} vulnerabilities] [{} advisories] [{} fixed by KB] from [{} & {} -> {}] vulnerabilities/advisories",
artifact.getId(), msProductIdOrName, msrcProduct.getId(), appliedMsKbIdentifiers.size(),
msVulnerabilitiesNotFixed.size(), msAdvisoriesNotFixed.size(), msVulnerabilitiesAndAdvisoriesFixed.size(),
msVulnerabilitiesOnProductFromAdvisors.size(), msVulnerabilitiesOnProductFromKbChains.size(), allPotentialVulnerabilities.size());
}
}
vInventory.writeBack();
}
private Set extractKbIdentifiers(Artifact artifact) {
if (StringUtils.hasText(artifact.get(InventoryAttribute.MS_KB_IDENTIFIER.getKey()))) {
return Arrays.stream(artifact.get(InventoryAttribute.MS_KB_IDENTIFIER.getKey()).split(", "))
.map(e -> e.replace("KB", ""))
.filter(StringUtils::hasText)
.collect(Collectors.toSet());
}
return new HashSet<>();
}
private MsrcProduct findProductFromIdOrName(String idOrName) {
if (StringUtils.isEmpty(idOrName)) {
return null;
}
final MsrcProductIndexQuery msrcProductQuery = this.msrcProductQuery.get();
final MsrcProduct msrcProduct = msrcProductQuery.findProductById(idOrName);
if (msrcProduct == null) {
return msrcProductQuery.findProductByName(idOrName);
}
return msrcProduct;
}
}