All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metaeffekt.artifact.analysis.diffmerge.VulnerabilityDiffer Maven / Gradle / Ivy

There is a newer version: 0.132.0
Show 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.diffmerge;

import com.google.common.collect.Maps;
import com.metaeffekt.artifact.analysis.vulnerability.CommonEnumerationUtil;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;
import org.metaeffekt.core.inventory.processor.reader.InventoryReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import us.springett.parsers.cpe.Cpe;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;

public class VulnerabilityDiffer {

    private final static Logger LOG = LoggerFactory.getLogger(VulnerabilityDiffer.class);

    private static final String NA = "n/a";
    private final Inventory inventory1;
    private final Inventory inventory2;

    private VulnerabilityDiffer(Inventory inventory1, Inventory inventory2) {
        this.inventory1 = inventory1;
        this.inventory2 = inventory2;
    }

    public static VulnerabilityDiffer fromInventories(Inventory inventory1, Inventory inventory2) {
        return new VulnerabilityDiffer(inventory1, inventory2);
    }

    public static VulnerabilityDiffer fromFiles(Collection inventories1, Collection inventories2) throws IOException {
        final Map parsed1 = readInventoriesIntoContext(inventories1);
        final Map parsed2 = readInventoriesIntoContext(inventories2);

        final Inventory merged1 = mergeInventories(parsed1);
        final Inventory merged2 = mergeInventories(parsed2);

        return new VulnerabilityDiffer(merged1, merged2);
    }

    public static VulnerabilityDiffer fromMultipleInventories(Collection inventories1, Collection inventories2) {
        final Map inventoryContextMap1 = Maps.newHashMap();
        for (Inventory inventory : inventories1) {
            inventoryContextMap1.put(inventory, "inventory-" + inventoryContextMap1.size());
        }
        final Map inventoryContextMap2 = Maps.newHashMap();
        for (Inventory inventory : inventories2) {
            inventoryContextMap2.put(inventory, "inventory-" + inventoryContextMap2.size());
        }

        final Inventory mergedInventory1 = mergeInventories(inventoryContextMap1);
        final Inventory mergedInventory2 = mergeInventories(inventoryContextMap2);

        return new VulnerabilityDiffer(mergedInventory1, mergedInventory2);
    }

    public static VulnerabilityDiffer empty() {
        return new VulnerabilityDiffer(null, null);
    }

    public DiffResult createDiffFromMergedInventories() {
        final Inventory containedIn1MissingIn2 = findAndMergeDiffVulnerabilities(this.inventory1, this.inventory2);
        final Inventory containedIn2MissingIn1 = findAndMergeDiffVulnerabilities(this.inventory2, this.inventory1);

        return createResult(containedIn1MissingIn2, containedIn2MissingIn1);
    }

    private Inventory findAndMergeDiffVulnerabilities(Inventory before, Inventory after) {
        final Inventory mergedInventory = new Inventory();

        // build a superset of all vulnerability identifiers from both inventories
        final Set vulnerabilitySuperset = new HashSet<>();
        appendVulnerabilityIdentifiers(before, vulnerabilitySuperset);
        appendVulnerabilityIdentifiers(after, vulnerabilitySuperset);

        for (String vulnerability : vulnerabilitySuperset) {
            final VulnerabilityMetaData foundBeforeVmd = findVulnerability(before, vulnerability);
            final VulnerabilityMetaData foundAfterVmd = findVulnerability(after, vulnerability);

            final VulnerabilityMetaData combinedVmd = new VulnerabilityMetaData();

            combinedVmd.set(VulnerabilityMetaData.Attribute.NAME, vulnerability);

            if (this.appendStatusDifference(foundBeforeVmd, foundAfterVmd, combinedVmd)) {
                continue;
            }

            // the below code failed once (and only once so far) in a test case, so, a try-catch is used here:
            try {
                this.appendCvssScores(foundBeforeVmd, foundAfterVmd, combinedVmd);
                this.appendMatchingCpes(foundBeforeVmd, foundAfterVmd, combinedVmd);
            } catch (Exception e) {
                LOG.error("Error while processing vulnerability: {}", vulnerability, e);
            }

            mergedInventory.getVulnerabilityMetaData().add(combinedVmd);
        }

        return mergedInventory;
    }

    private boolean appendStatusDifference(VulnerabilityMetaData beforeVmd, VulnerabilityMetaData afterVmd, VulnerabilityMetaData combinedVmd) {
        final boolean hasBefore = beforeVmd != null;
        final boolean hasAfter = afterVmd != null;

        final String beforeStatus = hasBefore ? beforeVmd.get(VulnerabilityMetaData.Attribute.STATUS) : null;
        final String afterStatus = hasAfter ? afterVmd.get(VulnerabilityMetaData.Attribute.STATUS) : null;

        if (beforeStatus == null && hasBefore) {
            combinedVmd.set(VulnerabilityMetaData.Attribute.STATUS, "in review");
        } else {
            combinedVmd.set(VulnerabilityMetaData.Attribute.STATUS, beforeStatus);
        }
        if (afterStatus == null && hasAfter) {
            combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_NEW_STATUS.getKey(), "in review");
        } else {
            combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_NEW_STATUS.getKey(), afterStatus);
        }

        if (!hasBefore && !hasAfter) {
            throw new IllegalStateException("Vulnerability must be contained in at least one reference inventory: " + combinedVmd.get(VulnerabilityMetaData.Attribute.NAME));

        } else if (hasBefore && !hasAfter) {
            if (beforeStatus == null) {
                combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_STATUS_CHANGE.getKey(), VulnerabilityStatusDiff.REMOVED.getKey());
            } else {
                combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_STATUS_CHANGE.getKey(), VulnerabilityStatusDiff.REMOVED_EXPECTED_VOID.getKey());
            }
            combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_NEW_STATUS.getKey(), NA);

        } else if (!hasBefore) { // && hasAfter
            combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_STATUS_CHANGE.getKey(), VulnerabilityStatusDiff.NEW.getKey());
            combinedVmd.set(VulnerabilityMetaData.Attribute.STATUS, NA);

        } else { // hasBefore && hasAfter
            final VulnerabilityStatusDiff statusChange = VulnerabilityStatusDiff.deriveStatusChangeIdentifier(beforeStatus, afterStatus);
            combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_STATUS_CHANGE.getKey(), statusChange.getKey());

            if (statusChange.equals(VulnerabilityStatusDiff.NO_CHANGE)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Include resulting metrics:
     * 
    *
  • resulting score (modified CVSSv3.1 with fallback)
  • *
  • resulting severity (from above CVSS)
  • *
*

* Up to two vectors (and their score, severity) should be included in the resulting inventory (modified, * unmodified): *

    *
  1. After V3 Modified
  2. *
  3. Before V3 Modified
  4. *
  5. After V2 Modified
  6. *
  7. Before V2 Modified
  8. *
* *
    *
  1. After V3
  2. *
  3. Before V3
  4. *
  5. After V2
  6. *
  7. Before V2
  8. *
* * @param beforeVmd vulnerability meta data from the "before" inventory * @param afterVmd vulnerability meta data from the "after" inventory * @param combinedVmd vulnerability meta data to append to */ private void appendCvssScores(VulnerabilityMetaData beforeVmd, VulnerabilityMetaData afterVmd, VulnerabilityMetaData combinedVmd) { final Vulnerability beforeVulnerability = Vulnerability.fromVulnerabilityMetaData(beforeVmd); final Vulnerability afterVulnerability = Vulnerability.fromVulnerabilityMetaData(afterVmd); if (beforeVulnerability == null && afterVulnerability == null) { LOG.warn("Vulnerability {} is not defined in either before or after inventories", combinedVmd.get(VulnerabilityMetaData.Attribute.NAME)); return; } final Vulnerability combinedVulnerability = Vulnerability.fromVulnerabilityMetaData(combinedVmd); if (beforeVulnerability != null) { combinedVulnerability.getCvssVectors().addAllCvssVectors(beforeVulnerability.getCvssVectors()); } if (afterVulnerability != null) { combinedVulnerability.getCvssVectors().addAllCvssVectors(afterVulnerability.getCvssVectors()); } combinedVmd.getAttributes().clear(); combinedVulnerability.appendToBaseModel(combinedVmd); } private void appendMatchingCpes(VulnerabilityMetaData beforeVmd, VulnerabilityMetaData afterVmd, VulnerabilityMetaData combinedVmd) { final List beforeCpe = CommonEnumerationUtil.parseEffectiveCpe(beforeVmd); final List afterCpe = CommonEnumerationUtil.parseEffectiveCpe(afterVmd); final List merged = CommonEnumerationUtil.distinctAndSortedWithWildcards(beforeCpe, afterCpe); combinedVmd.set(VulnerabilityMetaData.Attribute.PRODUCT_URIS.getKey(), CommonEnumerationUtil.toCpe22UriOrFallbackToCpe23FS(merged)); } private void appendVulnerabilityIdentifiers(Inventory inventory, Collection identifiers) { inventory.getVulnerabilityMetaData().stream() .map(v -> v.get(VulnerabilityMetaData.Attribute.NAME)) .forEach(identifiers::add); } public static VulnerabilityMetaData findVulnerability(Inventory inventory, String name) { return inventory.getVulnerabilityMetaData().stream() .filter(v -> v.get(VulnerabilityMetaData.Attribute.NAME).equals(name)) .findFirst() .orElse(null); } private DiffResult createResult(Inventory containedIn1MissingIn2, Inventory containedIn2MissingIn1) { return new DiffResult(inventory1, inventory2, containedIn1MissingIn2, containedIn2MissingIn1); } private static Map readInventoriesIntoContext(Collection inventoryFiles) throws IOException { final Map inventoryContextMap = Maps.newHashMap(); for (File inventoryFile : inventoryFiles) { if (inventoryFile == null || !inventoryFile.exists()) { throw new FileNotFoundException("Inventory file does not exist: " + inventoryFile); } final Inventory inventory = new InventoryReader().readInventory(inventoryFile); inventoryContextMap.put(inventory, inventoryFile.getName().replace(".xls", "")); } return inventoryContextMap; } private static Inventory mergeInventories(Map inventories) { final Inventory mergedInventory1 = new Inventory(); final InventoryMerger merger1 = new InventoryMerger(mergedInventory1); for (Map.Entry inventoryEntry : inventories.entrySet()) { final Inventory inventory = inventoryEntry.getKey(); final String context = inventoryEntry.getValue(); merger1.addReferenceInventory(inventory, context); } merger1.includeVulnerabilities(); merger1.includeAdvisories(); return mergedInventory1; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy