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

org.owasp.dependencycheck.data.nvdcve.CveDB Maven / Gradle / Ivy

/*
 * This file is part of dependency-check-core.
 *
 * 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.
 *
 * Copyright (c) 2018 Jeremy Long. All Rights Reserved.
 */
package org.owasp.dependencycheck.data.nvdcve;
//CSOFF: AvoidStarImport

import com.google.common.io.Resources;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.collections.map.ReferenceMap;
import org.owasp.dependencycheck.dependency.Vulnerability;
import org.owasp.dependencycheck.dependency.VulnerableSoftware;
import org.owasp.dependencycheck.utils.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.concurrent.ThreadSafe;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.JDBCType;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.stream.Collectors;
import org.anarres.jdiagnostics.DefaultQuery;

import static org.apache.commons.collections.map.AbstractReferenceMap.HARD;
import static org.apache.commons.collections.map.AbstractReferenceMap.SOFT;
import org.owasp.dependencycheck.analyzer.exception.LambdaExceptionWrapper;
import org.owasp.dependencycheck.analyzer.exception.UnexpectedAnalysisException;
import org.owasp.dependencycheck.data.nvd.json.BaseMetricV2;
import org.owasp.dependencycheck.data.nvd.json.BaseMetricV3;
import org.owasp.dependencycheck.data.nvd.json.CpeMatchStreamCollector;
import org.owasp.dependencycheck.data.nvd.json.DefCpeMatch;
import org.owasp.dependencycheck.data.nvd.json.DefCveItem;
import org.owasp.dependencycheck.data.nvd.json.LangString;
import org.owasp.dependencycheck.data.nvd.json.NodeFlatteningCollector;
import org.owasp.dependencycheck.data.nvd.json.ProblemtypeDatum;
import org.owasp.dependencycheck.data.nvd.json.Reference;
import static org.owasp.dependencycheck.data.nvdcve.CveDB.PreparedStatementCveDb.*;
import org.owasp.dependencycheck.data.update.cpe.CpeEcosystemCache;
import org.owasp.dependencycheck.data.update.cpe.CpePlus;
import org.owasp.dependencycheck.dependency.CvssV2;
import org.owasp.dependencycheck.dependency.CvssV3;
import org.owasp.dependencycheck.dependency.VulnerableSoftwareBuilder;
import us.springett.parsers.cpe.Cpe;
import us.springett.parsers.cpe.CpeBuilder;
import us.springett.parsers.cpe.CpeParser;
import us.springett.parsers.cpe.exceptions.CpeParsingException;
import us.springett.parsers.cpe.exceptions.CpeValidationException;

/**
 * The database holding information about the NVD CVE data. This class is safe
 * to be accessed from multiple threads in parallel, however internally only one
 * connection will be used.
 *
 * @author Jeremy Long
 */
@ThreadSafe
public final class CveDB implements AutoCloseable {

    /**
     * The logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(CveDB.class);

    /**
     * Resource location for SQL file containing updates to the ecosystem cache.
     */
    public static final String DB_ECOSYSTEM_CACHE = "data/dbEcosystemCacheUpdates.sql";

    /**
     * The database connection manager.
     */
    private final DatabaseManager databaseManager;

    /**
     * The bundle of statements used when accessing the database.
     */
    private ResourceBundle statementBundle;
    /**
     * Database properties object containing the 'properties' from the database
     * table.
     */
    private DatabaseProperties databaseProperties;
    /**
     * The filter for 2.3 CPEs in the CVEs - we don't import unless we get a
     * match.
     */
    private final String cpeStartsWithFilter;
    /**
     * Cache for CVE lookup; used to speed up the vulnerability search process.
     */
    @SuppressWarnings("unchecked")
    private final Map> vulnerabilitiesForCpeCache = Collections.synchronizedMap(new ReferenceMap(HARD, SOFT));
    /**
     * The configured settings
     */
    private final Settings settings;

    /**
     * Utility to extract information from
     * {@linkplain org.owasp.dependencycheck.data.nvd.json.DefCveItem}.
     */
    private final CveItemOperator cveItemConverter;
    /**
     * Flag indicating if the database is Oracle.
     */
    private boolean isOracle = false;
    /**
     * Flag indicating if the database is H2.
     */
    private boolean isH2 = false;

    /**
     * Updates the EcoSystem Cache.
     *
     * @return The number of records updated by the DB_ECOSYSTEM_CACHE update
     * script.
     */
    public int updateEcosystemCache() {
        LOGGER.debug("Updating the ecosystem cache");
        int updateCount = 0;
        try {
            final URL url = Resources.getResource(DB_ECOSYSTEM_CACHE);
            final List sql = Resources.readLines(url, StandardCharsets.UTF_8);

            try (Connection conn = databaseManager.getConnection();
                    Statement statement = conn.createStatement()) {
                for (String single : sql) {
                    updateCount += statement.executeUpdate(single);
                }
            } catch (SQLException ex) {
                LOGGER.debug("", ex);
                throw new DatabaseException("Unable to update the ecosystem cache", ex);
            }
        } catch (IOException ex) {
            throw new DatabaseException("Unable to update the ecosystem cache", ex);
        } catch (LinkageError ex) {
            LOGGER.debug(new DefaultQuery(ex).call().toString());
        }
        return updateCount;
    }

    /**
     * The enumeration value names must match the keys of the statements in the
     * statement bundles "dbStatements*.properties".
     */
    enum PreparedStatementCveDb {
        /**
         * Key for SQL Statement.
         */
        CLEANUP_ORPHANS,
        /**
         * Key for update ecosystem.
         */
        UPDATE_ECOSYSTEM,
        /**
         * Key for update ecosystem.
         */
        UPDATE_ECOSYSTEM2,
        /**
         * Key for SQL Statement.
         */
        COUNT_CPE,
        /**
         * Key for SQL Statement.
         */
        DELETE_VULNERABILITY,
        /**
         * Key for SQL Statement.
         */
        INSERT_PROPERTY,
        /**
         * Key for SQL Statement.
         */
        INSERT_CWE,
        /**
         * Key for SQL Statement.
         */
        INSERT_REFERENCE,
        /**
         * Key for SQL Statement.
         */
        INSERT_SOFTWARE,
        /**
         * Key for SQL Statement.
         */
        MERGE_PROPERTY,
        /**
         * Key for SQL Statement.
         */
        SELECT_CPE_ENTRIES,
        /**
         * Key for SQL Statement.
         */
        SELECT_CVE_FROM_SOFTWARE,
        /**
         * Key for SQL Statement.
         */
        SELECT_PROPERTIES,
        /**
         * Key for SQL Statement.
         */
        SELECT_VULNERABILITY_CWE,
        /**
         * Key for SQL Statement.
         */
        SELECT_REFERENCES,
        /**
         * Key for SQL Statement.
         */
        SELECT_SOFTWARE,
        /**
         * Key for SQL Statement.
         */
        SELECT_VENDOR_PRODUCT_LIST,
        /**
         * Key for SQL Statement.
         */
        SELECT_VENDOR_PRODUCT_LIST_FOR_NODE,
        /**
         * Key for SQL Statement.
         */
        SELECT_VULNERABILITY,
        /**
         * Key for SQL Statement.
         */
        UPDATE_PROPERTY,
        /**
         * Key for SQL Statement.
         */
        UPDATE_VULNERABILITY,
        /**
         * Key for SQL Statement.
         */
        SELECT_CPE_ECOSYSTEM,
        /**
         * Key for SQL Statement.
         */
        MERGE_CPE_ECOSYSTEM,
        /**
         * Key for SQL Statement.
         */
        DELETE_UNUSED_DICT_CPE,
        /**
         * Key for SQL Statement.
         */
        ADD_DICT_CPE,
        /**
         * Key for SQL Statement.
         */
        SELECT_KNOWN_EXPLOITED_VULNERABILITIES,
        /**
         * Key for SQL Statement.
         */
        MERGE_KNOWN_EXPLOITED
    }

    /**
     * Creates a new CveDB object and opens the database connection. Note, the
     * connection must be closed by the caller by calling the close method.
     *
     * @param settings the configured settings
     * @throws DatabaseException thrown if there is an exception opening the
     * database.
     */
    public CveDB(Settings settings) throws DatabaseException {
        this.settings = settings;
        this.cpeStartsWithFilter = settings.getString(Settings.KEYS.CVE_CPE_STARTS_WITH_FILTER, "cpe:2.3:a:");
        this.cveItemConverter = new CveItemOperator(cpeStartsWithFilter);
        databaseManager = new DatabaseManager(settings);
        statementBundle = databaseManager.getSqlStatements();
        isOracle = databaseManager.isOracle();
        isH2 = databaseManager.isH2Connection();
    }

    /**
     * Opens the database connection pool.
     */
    public void open() {
        databaseManager.open();
        databaseProperties = new DatabaseProperties(this);
    }

    /**
     * Closes the database connection. Close should be called on this object
     * when it is done being used.
     */
    @Override
    public void close() {
        if (isOpen()) {
            LOGGER.debug("Closing database");
            clearCache();
            LOGGER.debug("Cache cleared");
            try {
                databaseManager.close();
                LOGGER.debug("Connection closed");
            } catch (Throwable ex) {
                LOGGER.error("There was an exception attempting to close the CveDB, see the log for more details.");
                LOGGER.debug("", ex);
            }
            releaseResources();
            LOGGER.debug("Resources released");
            databaseManager.cleanup();
        }
    }

    /**
     * Releases the resources used by CveDB.
     */
    private void releaseResources() {
        statementBundle = null;
        databaseProperties = null;
    }

    /**
     * Returns whether the database connection is open or closed.
     *
     * @return whether the database connection is open or closed
     */
    public boolean isOpen() {
        return databaseManager.isOpen();
    }

    /**
     * Creates a prepared statement from the given key. The SQL is stored in a
     * properties file and the key is used to lookup the specific query.
     *
     * @param connection the database connection
     * @param key the key to select the prepared statement from the properties
     * file
     * @param parameter the first parameter to pass into the statement
     * @return the prepared statement
     * @throws DatabaseException throw if there is an error generating the
     * prepared statement
     */
    private PreparedStatement getPreparedStatement(Connection connection, PreparedStatementCveDb key, String parameter)
            throws DatabaseException, SQLException {
        final PreparedStatement preparedStatement = getPreparedStatement(connection, key);
        preparedStatement.setString(1, parameter);
        return preparedStatement;
    }

    /**
     * Creates a prepared statement from the given key. The SQL is stored in a
     * properties file and the key is used to lookup the specific query.
     *
     * @param connection the database connection
     * @param key the key to select the prepared statement from the properties
     * file
     * @param parameter the first parameter to pass into the statement
     * @return the prepared statement
     * @throws DatabaseException throw if there is an error generating the
     * prepared statement
     */
    private PreparedStatement getPreparedStatement(Connection connection, PreparedStatementCveDb key, int parameter)
            throws DatabaseException, SQLException {
        final PreparedStatement preparedStatement = getPreparedStatement(connection, key);
        preparedStatement.setInt(1, parameter);
        return preparedStatement;
    }

    /**
     * Creates a prepared statement from the given key. The SQL is stored in a
     * properties file and the key is used to lookup the specific query.
     *
     * @param connection the database connection
     * @param key the key to select the prepared statement from the properties
     * file
     * @return the prepared statement
     * @throws DatabaseException throw if there is an error generating the
     * prepared statement
     */
    private PreparedStatement getPreparedStatement(Connection connection, PreparedStatementCveDb key) throws DatabaseException {
        PreparedStatement preparedStatement = null;
        try {
            final String statementString = statementBundle.getString(key.name());
            if (isOracle && key == UPDATE_VULNERABILITY) {
                preparedStatement = connection.prepareCall(statementString);
//            } else if (key == INSERT_CPE) {
//                final String[] returnedColumns = {"id"};
//                preparedStatement = connection.prepareStatement(statementString, returnedColumns);
            } else {
                preparedStatement = connection.prepareStatement(statementString);
            }
            if (isOracle) {
                // Oracle has a default fetch-size of 10; MariaDB, MySQL, SQLServer and PostgreSQL by default cache the full
                // resultset at the client https://venkatsadasivam.com/2009/02/01/jdbc-performance-tuning-with-optimal-fetch-size/
                preparedStatement.setFetchSize(10_000);
            }
        } catch (SQLException ex) {
            throw new DatabaseException(ex);
        } catch (MissingResourceException ex) {
            if (!ex.getMessage().contains("key MERGE_PROPERTY")) {
                throw new DatabaseException(ex);
            }
        }
        return preparedStatement;
    }

    /**
     * Cleans up the object and ensures that "close" has been called.
     *
     * @throws Throwable thrown if there is a problem
     */
    @Override
    @SuppressWarnings("FinalizeDeclaration")
    protected void finalize() throws Throwable {
        LOGGER.debug("Entering finalize");
        close();
        super.finalize();
    }

    /**
     * Get the value of databaseProperties.
     *
     * @return the value of databaseProperties
     */
    public DatabaseProperties getDatabaseProperties() {
        return databaseProperties;
    }

    /**
     * Used within the unit tests to reload the database properties.
     *
     * @return the database properties
     */
    DatabaseProperties reloadProperties() {
        databaseProperties = new DatabaseProperties(this);
        return databaseProperties;
    }

    /**
     * Searches the CPE entries in the database and retrieves all entries for a
     * given vendor and product combination. The returned list will include all
     * versions of the product that are registered in the NVD CVE data.
     *
     * @param vendor the identified vendor name of the dependency being analyzed
     * @param product the identified name of the product of the dependency being
     * analyzed
     * @return a set of vulnerable software
     */
    public Set getCPEs(String vendor, String product) {
        final Set cpe = new HashSet<>();
        try (Connection conn = databaseManager.getConnection();
                PreparedStatement ps = getPreparedStatement(conn, SELECT_CPE_ENTRIES)) {
            //part, vendor, product, version, update_version, edition,
            //lang, sw_edition, target_sw, target_hw, other, ecosystem
            ps.setString(1, vendor);
            ps.setString(2, product);
            try (ResultSet rs = ps.executeQuery()) {
                final CpeBuilder builder = new CpeBuilder();
                while (rs.next()) {
                    final Cpe entry = builder
                            .part(rs.getString(1))
                            .vendor(rs.getString(2))
                            .product(rs.getString(3))
                            .version(rs.getString(4))
                            .update(rs.getString(5))
                            .edition(rs.getString(6))
                            .language(rs.getString(7))
                            .swEdition(rs.getString(8))
                            .targetSw(rs.getString(9))
                            .targetHw(rs.getString(10))
                            .other(rs.getString(11)).build();
                    final CpePlus plus = new CpePlus(entry, rs.getString(12));
                    cpe.add(plus);
                }
            }
        } catch (SQLException | CpeParsingException | CpeValidationException ex) {
            LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
            LOGGER.debug("", ex);
        }
        return cpe;
    }

    /**
     * Returns the entire list of vendor/product combinations.
     *
     * @return the entire list of vendor/product combinations
     * @throws DatabaseException thrown when there is an error retrieving the
     * data from the DB
     */
    public Set> getVendorProductList() throws DatabaseException {
        final Set> data = new HashSet<>();
        try (Connection conn = databaseManager.getConnection();
                PreparedStatement ps = getPreparedStatement(conn, SELECT_VENDOR_PRODUCT_LIST);
                ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                data.add(new Pair<>(rs.getString(1), rs.getString(2)));
            }
        } catch (SQLException ex) {
            final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
            throw new DatabaseException(msg, ex);
        }
        return data;
    }

    /**
     * Returns the entire list of vendor/product combinations filtered for just
     * Node JS related products.
     *
     * @return the list of vendor/product combinations that are known to be
     * related to Node JS
     * @throws DatabaseException thrown when there is an error retrieving the
     * data from the DB
     */
    public Set> getVendorProductListForNode() throws DatabaseException {
        final Set> data = new HashSet<>();
        try (Connection conn = databaseManager.getConnection();
                PreparedStatement ps = getPreparedStatement(conn, SELECT_VENDOR_PRODUCT_LIST_FOR_NODE);
                ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                data.add(new Pair<>(rs.getString(1), rs.getString(2)));
            }
        } catch (SQLException ex) {
            final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
            throw new DatabaseException(msg, ex);
        }
        return data;
    }

    /**
     * Returns a set of properties.
     *
     * @return the properties from the database
     */
    public Properties getProperties() {
        final Properties prop = new Properties();
        try (Connection conn = databaseManager.getConnection();
                PreparedStatement ps = getPreparedStatement(conn, SELECT_PROPERTIES);
                ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                prop.setProperty(rs.getString(1), rs.getString(2));
            }
        } catch (SQLException ex) {
            LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
            LOGGER.debug("", ex);
        }
        return prop;
    }

    /**
     * Saves a property to the database.
     *
     * @param key the property key
     * @param value the property value
     */
    public void saveProperty(String key, String value) {
        clearCache();
        try (Connection conn = databaseManager.getConnection();
                PreparedStatement mergeProperty = getPreparedStatement(conn, MERGE_PROPERTY)) {
            if (mergeProperty != null) {
                mergeProperty.setString(1, key);
                mergeProperty.setString(2, value);
                mergeProperty.execute();
            } else {
                // No Merge statement, so doing an Update/Insert...
                try (PreparedStatement updateProperty = getPreparedStatement(conn, UPDATE_PROPERTY)) {
                    updateProperty.setString(1, value);
                    updateProperty.setString(2, key);
                    if (updateProperty.executeUpdate() == 0) {
                        try (PreparedStatement insertProperty = getPreparedStatement(conn, INSERT_PROPERTY)) {
                            insertProperty.setString(1, key);
                            insertProperty.setString(2, value);
                            insertProperty.executeUpdate();
                        }
                    }
                }
            }
        } catch (SQLException ex) {
            LOGGER.warn("Unable to save property '{}' with a value of '{}' to the database", key, value);
            LOGGER.debug("", ex);
        }
    }

    /**
     * Clears cache. Should be called whenever something is modified. While this
     * is not the optimal cache eviction strategy, this is good enough for
     * typical usage (update DB and then only read) and it is easier to maintain
     * the code.
     * 

* It should be also called when DB is closed. *

*/ private void clearCache() { vulnerabilitiesForCpeCache.clear(); } /** * Retrieves the vulnerabilities associated with the specified CPE. * * @param cpe the CPE to retrieve vulnerabilities for * @return a list of Vulnerabilities * @throws DatabaseException thrown if there is an exception retrieving data */ public List getVulnerabilities(Cpe cpe) throws DatabaseException { final List cachedVulnerabilities = vulnerabilitiesForCpeCache.get(cpe.toCpe23FS()); if (cachedVulnerabilities != null) { LOGGER.debug("Cache hit for {}", cpe.toCpe23FS()); return cachedVulnerabilities; } else { LOGGER.debug("Cache miss for {}", cpe.toCpe23FS()); } final List vulnerabilities = new ArrayList<>(); try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, SELECT_CVE_FROM_SOFTWARE)) { ps.setString(1, cpe.getVendor()); ps.setString(2, cpe.getProduct()); try (ResultSet rs = ps.executeQuery()) { String currentCVE = ""; final Set vulnSoftware = new HashSet<>(); final VulnerableSoftwareBuilder vulnerableSoftwareBuilder = new VulnerableSoftwareBuilder(); while (rs.next()) { final String cveId = rs.getString(1); if (currentCVE.isEmpty()) { //first loop we don't have the cveId currentCVE = cveId; } if (!vulnSoftware.isEmpty() && !currentCVE.equals(cveId)) { //check for match and add final VulnerableSoftware matchedCPE = getMatchingSoftware(cpe, vulnSoftware); if (matchedCPE != null) { final Vulnerability v = getVulnerability(currentCVE, conn); if (v != null) { v.setMatchedVulnerableSoftware(matchedCPE); v.setSource(Vulnerability.Source.NVD); vulnerabilities.add(v); } } vulnSoftware.clear(); currentCVE = cveId; } // 1 cve, 2 part, 3 vendor, 4 product, 5 version, 6 update_version, 7 edition, // 8 lang, 9 sw_edition, 10 target_sw, 11 target_hw, 12 other, 13 versionEndExcluding, //14 versionEndIncluding, 15 versionStartExcluding, 16 versionStartIncluding, 17 vulnerable final VulnerableSoftware vs; try { vs = vulnerableSoftwareBuilder.part(rs.getString(2)).vendor(rs.getString(3)) .product(rs.getString(4)).version(rs.getString(5)).update(rs.getString(6)) .edition(rs.getString(7)).language(rs.getString(8)).swEdition(rs.getString(9)) .targetSw(rs.getString(10)).targetHw(rs.getString(11)).other(rs.getString(12)) .versionEndExcluding(rs.getString(13)).versionEndIncluding(rs.getString(14)) .versionStartExcluding(rs.getString(15)).versionStartIncluding(rs.getString(16)) .vulnerable(rs.getBoolean(17)).build(); } catch (CpeParsingException | CpeValidationException ex) { throw new DatabaseException("Database contains an invalid Vulnerable Software Entry", ex); } vulnSoftware.add(vs); } //remember to process the last set of CVE/CPE entries final VulnerableSoftware matchedCPE = getMatchingSoftware(cpe, vulnSoftware); if (matchedCPE != null) { final Vulnerability v = getVulnerability(currentCVE, conn); if (v != null) { v.setMatchedVulnerableSoftware(matchedCPE); v.setSource(Vulnerability.Source.NVD); vulnerabilities.add(v); } } } } catch (SQLException ex) { throw new DatabaseException("Exception retrieving vulnerability for " + cpe.toCpe23FS(), ex); } vulnerabilitiesForCpeCache.put(cpe.toCpe23FS(), vulnerabilities); return vulnerabilities; } /** * Gets a vulnerability for the provided CVE. * * @param cve the CVE to lookup * @return a vulnerability object * @throws DatabaseException if an exception occurs */ public Vulnerability getVulnerability(String cve) throws DatabaseException { try (Connection conn = databaseManager.getConnection()) { return getVulnerability(cve, conn); } catch (SQLException ex) { throw new DatabaseException("Error retrieving " + cve, ex); } } /** * Gets a vulnerability for the provided CVE. * * @param cve the CVE to lookup * @param conn already active database connection * @return a vulnerability object * @throws DatabaseException if an exception occurs */ public Vulnerability getVulnerability(String cve, Connection conn) throws DatabaseException { final int cveId; final VulnerableSoftwareBuilder vulnerableSoftwareBuilder = new VulnerableSoftwareBuilder(); Vulnerability vuln = null; try { try (PreparedStatement psV = getPreparedStatement(conn, SELECT_VULNERABILITY, cve); ResultSet rsV = psV.executeQuery()) { if (rsV.next()) { //1.id, 2.description, cveId = rsV.getInt(1); vuln = new Vulnerability(); vuln.setSource(Vulnerability.Source.NVD); vuln.setName(cve); vuln.setDescription(rsV.getString(2)); //3.v2Severity, 4.v2ExploitabilityScore, 5.v2ImpactScore, 6.v2AcInsufInfo, 7.v2ObtainAllPrivilege, //8.v2ObtainUserPrivilege, 9.v2ObtainOtherPrivilege, 10.v2UserInteractionRequired, 11.v2Score, //12.v2AccessVector, 13.v2AccessComplexity, 14.v2Authentication, 15.v2ConfidentialityImpact, //16.v2IntegrityImpact, 17.v2AvailabilityImpact, 18.v2Version, if (rsV.getObject(11) != null) { final CvssV2 cvss = new CvssV2(rsV.getFloat(11), rsV.getString(12), rsV.getString(13), rsV.getString(14), rsV.getString(15), rsV.getString(16), rsV.getString(17), rsV.getString(3), getFloatValue(rsV, 4), getFloatValue(rsV, 5), getBooleanValue(rsV, 6), getBooleanValue(rsV, 7), getBooleanValue(rsV, 8), getBooleanValue(rsV, 9), getBooleanValue(rsV, 10), rsV.getString(18)); vuln.setCvssV2(cvss); } //19.v3ExploitabilityScore, 20.v3ImpactScore, 21.v3AttackVector, 22.v3AttackComplexity, 23.v3PrivilegesRequired, //24.v3UserInteraction, 25.v3Scope, 26.v3ConfidentialityImpact, 27.v3IntegrityImpact, 28.v3AvailabilityImpact, //29.v3BaseScore, 30.v3BaseSeverity, 21.v3Version if (rsV.getObject(21) != null) { final CvssV3 cvss = new CvssV3(rsV.getString(21), rsV.getString(22), rsV.getString(23), rsV.getString(24), rsV.getString(25), rsV.getString(26), rsV.getString(27), rsV.getString(28), rsV.getFloat(29), rsV.getString(30), getFloatValue(rsV, 19), getFloatValue(rsV, 20), rsV.getString(31)); vuln.setCvssV3(cvss); } } else { LOGGER.debug(cve + " does not exist in the database"); return null; } } try (PreparedStatement psCWE = getPreparedStatement(conn, SELECT_VULNERABILITY_CWE, cveId); ResultSet rsC = psCWE.executeQuery()) { while (rsC.next()) { vuln.addCwe(rsC.getString(1)); } } try (PreparedStatement psR = getPreparedStatement(conn, SELECT_REFERENCES, cveId); ResultSet rsR = psR.executeQuery()) { while (rsR.next()) { vuln.addReference(rsR.getString(1), rsR.getString(2), rsR.getString(3)); } } try (PreparedStatement psS = getPreparedStatement(conn, SELECT_SOFTWARE, cveId); ResultSet rsS = psS.executeQuery()) { //1 part, 2 vendor, 3 product, 4 version, 5 update_version, 6 edition, 7 lang, //8 sw_edition, 9 target_sw, 10 target_hw, 11 other, 12 versionEndExcluding, //13 versionEndIncluding, 14 versionStartExcluding, 15 versionStartIncluding, 16 vulnerable while (rsS.next()) { vulnerableSoftwareBuilder.part(rsS.getString(1)) .vendor(rsS.getString(2)) .product(rsS.getString(3)) .version(rsS.getString(4)) .update(rsS.getString(5)) .edition(rsS.getString(6)) .language(rsS.getString(7)) .swEdition(rsS.getString(8)) .targetSw(rsS.getString(9)) .targetHw(rsS.getString(10)) .other(rsS.getString(11)) .versionEndExcluding(rsS.getString(12)) .versionEndIncluding(rsS.getString(13)) .versionStartExcluding(rsS.getString(14)) .versionStartIncluding(rsS.getString(15)) .vulnerable(rsS.getBoolean(16)); vuln.addVulnerableSoftware(vulnerableSoftwareBuilder.build()); } } } catch (SQLException ex) { throw new DatabaseException("Error retrieving " + cve, ex); } catch (CpeParsingException | CpeValidationException ex) { throw new DatabaseException("The database contains an invalid Vulnerable Software Entry", ex); } return vuln; } /** * Updates the vulnerability within the database. If the vulnerability does * not exist it will be added. * * @param cve the vulnerability from the NVD CVE Data Feed to add to the * database * @param baseEcosystem the ecosystem the CVE belongs to; this is based off * of things like the CVE description * @throws DatabaseException is thrown if the database */ public void updateVulnerability(DefCveItem cve, String baseEcosystem) { clearCache(); final String cveId = cve.getCve().getCVEDataMeta().getId(); try { final String description = cveItemConverter.extractDescription(cve); if (cveItemConverter.isRejected(description)) { deleteVulnerability(cveId); } else { if (cveItemConverter.testCveCpeStartWithFilter(cve)) { final int vulnerabilityId = updateOrInsertVulnerability(cve, description); updateVulnerabilityInsertCwe(vulnerabilityId, cve); updateVulnerabilityInsertReferences(vulnerabilityId, cve); final List software = parseCpes(cve); updateVulnerabilityInsertSoftware(vulnerabilityId, cveId, software, baseEcosystem); } } } catch (SQLException ex) { final String msg = String.format("Error updating '%s'", cveId); LOGGER.debug(msg, ex); throw new DatabaseException(msg, ex); } catch (CpeValidationException ex) { final String msg = String.format("Error parsing CPE entry from '%s'", cveId); LOGGER.debug(msg, ex); throw new DatabaseException(msg, ex); } } private void loadCpeEcosystemCache() { final Map, String> map = new HashMap<>(); try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, SELECT_CPE_ECOSYSTEM); ResultSet rs = ps.executeQuery()) { while (rs.next()) { final Pair key = new Pair<>(rs.getString(1), rs.getString(2)); final String value = rs.getString(3); map.put(key, value); } } catch (SQLException ex) { final String msg = String.format("Error loading the Cpe Ecosystem Cache: %s", ex.getMessage()); LOGGER.debug(msg, ex); throw new DatabaseException(msg, ex); } CpeEcosystemCache.setCache(map); } private void saveCpeEcosystemCache() { final Map, String> map = CpeEcosystemCache.getChanged(); if (map != null && !map.isEmpty()) { try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, MERGE_CPE_ECOSYSTEM)) { for (Map.Entry, String> entry : map.entrySet()) { ps.setString(1, entry.getKey().getLeft()); ps.setString(2, entry.getKey().getRight()); ps.setString(3, entry.getValue()); if (isBatchInsertEnabled()) { ps.addBatch(); } else { ps.execute(); } } if (isBatchInsertEnabled()) { ps.executeBatch(); } } catch (SQLException ex) { final String msg = String.format("Error saving the Cpe Ecosystem Cache: %s", ex.getMessage()); LOGGER.debug(msg, ex); throw new DatabaseException(msg, ex); } } } /** * Used when updating a vulnerability - this method inserts the * vulnerability entry itself. * * @param cve the CVE data * @param description the description of the CVE entry * @return the vulnerability ID */ private int updateOrInsertVulnerability(DefCveItem cve, String description) { if (CpeEcosystemCache.isEmpty()) { loadCpeEcosystemCache(); } final int vulnerabilityId; try (Connection conn = databaseManager.getConnection(); PreparedStatement callUpdate = getPreparedStatement(conn, UPDATE_VULNERABILITY)) { // String 1.cve, String 2.description, String 3.v2Severity, Float 4.v2ExploitabilityScore, // Float 5.v2ImpactScore, Boolean 6.v2AcInsufInfo, Boolean 7.v2ObtainAllPrivilege, // Boolean 8.v2ObtainUserPrivilege, Boolean 9.v2ObtainOtherPrivilege, Boolean 10.v2UserInteractionRequired, // Float 11.v2Score, String 12.v2AccessVector, String 13.v2AccessComplexity, // String 14.v2Authentication, String 15.v2ConfidentialityImpact, String 16.v2IntegrityImpact, // String 17.v2AvailabilityImpact, String 18.v2Version, Float 19.v3ExploitabilityScore, // Float 20.v3ImpactScore, String 21.v3AttackVector, String 22.v3AttackComplexity, // String 23.v3PrivilegesRequired, String 24.v3UserInteraction, String 25.v3Scope, // String 26.v3ConfidentialityImpact, String 27.v3IntegrityImpact, String 28.v3AvailabilityImpact, // Float 29.v3BaseScore, String 30.v3BaseSeverity, String 31.v3Version callUpdate.setString(1, cve.getCve().getCVEDataMeta().getId()); callUpdate.setString(2, description); if (cve.getImpact().getBaseMetricV2() != null) { final BaseMetricV2 cvssv2 = cve.getImpact().getBaseMetricV2(); Map props = cvssv2.getAdditionalProperties(); callUpdate.setString(3, cvssv2.getSeverity()); setFloatValue(callUpdate, 4, props, "exploitabilityScore"); setFloatValue(callUpdate, 5, props, "impactScore"); setBooleanValue(callUpdate, 6, props, "acInsufInfo"); setBooleanValue(callUpdate, 7, props, "obtainAllPrivilege"); setBooleanValue(callUpdate, 8, props, "obtainUserPrivilege"); setBooleanValue(callUpdate, 9, props, "obtainOtherPrivilege"); setBooleanValue(callUpdate, 10, props, "userInteractionRequired"); callUpdate.setFloat(11, cvssv2.getCvssV2().getBaseScore().floatValue()); callUpdate.setString(12, cvssv2.getCvssV2().getAccessVector().value()); callUpdate.setString(13, cvssv2.getCvssV2().getAccessComplexity().value()); callUpdate.setString(14, cvssv2.getCvssV2().getAuthentication().value()); callUpdate.setString(15, cvssv2.getCvssV2().getConfidentialityImpact().value()); callUpdate.setString(16, cvssv2.getCvssV2().getIntegrityImpact().value()); callUpdate.setString(17, cvssv2.getCvssV2().getAvailabilityImpact().value()); props = cvssv2.getCvssV2().getAdditionalProperties(); setStringValue(callUpdate, 18, props, "version"); } else { callUpdate.setNull(3, java.sql.Types.NULL); callUpdate.setNull(4, java.sql.Types.NULL); callUpdate.setNull(5, java.sql.Types.NULL); callUpdate.setNull(6, java.sql.Types.NULL); callUpdate.setNull(7, java.sql.Types.NULL); callUpdate.setNull(8, java.sql.Types.NULL); callUpdate.setNull(9, java.sql.Types.NULL); callUpdate.setNull(10, java.sql.Types.NULL); callUpdate.setNull(11, java.sql.Types.NULL); callUpdate.setNull(12, java.sql.Types.NULL); callUpdate.setNull(13, java.sql.Types.NULL); callUpdate.setNull(14, java.sql.Types.NULL); callUpdate.setNull(15, java.sql.Types.NULL); callUpdate.setNull(16, java.sql.Types.NULL); callUpdate.setNull(17, java.sql.Types.NULL); callUpdate.setNull(18, java.sql.Types.NULL); } if (cve.getImpact().getBaseMetricV3() != null) { final BaseMetricV3 cvssv3 = cve.getImpact().getBaseMetricV3(); Map props = cvssv3.getAdditionalProperties(); setFloatValue(callUpdate, 19, props, "exploitabilityScore"); setFloatValue(callUpdate, 20, props, "impactScore"); callUpdate.setString(21, cvssv3.getCvssV3().getAttackVector().value()); callUpdate.setString(22, cvssv3.getCvssV3().getAttackComplexity().value()); callUpdate.setString(23, cvssv3.getCvssV3().getPrivilegesRequired().value()); callUpdate.setString(24, cvssv3.getCvssV3().getUserInteraction().value()); callUpdate.setString(25, cvssv3.getCvssV3().getScope().value()); callUpdate.setString(26, cvssv3.getCvssV3().getConfidentialityImpact().value()); callUpdate.setString(27, cvssv3.getCvssV3().getIntegrityImpact().value()); callUpdate.setString(28, cvssv3.getCvssV3().getAvailabilityImpact().value()); callUpdate.setFloat(29, cvssv3.getCvssV3().getBaseScore().floatValue()); callUpdate.setString(30, cvssv3.getCvssV3().getBaseSeverity().value()); props = cvssv3.getCvssV3().getAdditionalProperties(); setStringValue(callUpdate, 31, props, "version"); } else { callUpdate.setNull(19, java.sql.Types.NULL); callUpdate.setNull(20, java.sql.Types.NULL); callUpdate.setNull(21, java.sql.Types.NULL); callUpdate.setNull(22, java.sql.Types.NULL); callUpdate.setNull(23, java.sql.Types.NULL); callUpdate.setNull(24, java.sql.Types.NULL); callUpdate.setNull(25, java.sql.Types.NULL); callUpdate.setNull(26, java.sql.Types.NULL); callUpdate.setNull(27, java.sql.Types.NULL); callUpdate.setNull(28, java.sql.Types.NULL); callUpdate.setNull(29, java.sql.Types.NULL); callUpdate.setNull(30, java.sql.Types.NULL); callUpdate.setNull(31, java.sql.Types.NULL); } if (isOracle) { try { final CallableStatement cs = (CallableStatement) callUpdate; cs.registerOutParameter(32, JDBCType.INTEGER); cs.executeUpdate(); vulnerabilityId = cs.getInt(32); } catch (SQLException ex) { final String msg = String.format("Unable to retrieve id for new vulnerability for '%s'", cve.getCve().getCVEDataMeta().getId()); throw new DatabaseException(msg, ex); } } else { try (ResultSet rs = callUpdate.executeQuery()) { rs.next(); vulnerabilityId = rs.getInt(1); } catch (SQLException ex) { final String msg = String.format("Unable to retrieve id for new vulnerability for '%s'", cve.getCve().getCVEDataMeta().getId()); throw new DatabaseException(msg, ex); } } } catch (SQLException ex) { throw new UnexpectedAnalysisException(ex); } return vulnerabilityId; } /** * Used when updating a vulnerability - this method inserts the CWE entries. * * @param vulnerabilityId the vulnerability ID * @param cve the CVE entry that contains the CWE entries to insert * @throws SQLException thrown if there is an error inserting the data */ private void updateVulnerabilityInsertCwe(int vulnerabilityId, DefCveItem cve) throws SQLException { try (Connection conn = databaseManager.getConnection(); PreparedStatement insertCWE = getPreparedStatement(conn, INSERT_CWE, vulnerabilityId)) { for (ProblemtypeDatum datum : cve.getCve().getProblemtype().getProblemtypeData()) { for (LangString desc : datum.getDescription()) { if ("en".equals(desc.getLang())) { insertCWE.setString(2, desc.getValue()); if (isBatchInsertEnabled()) { insertCWE.addBatch(); } else { insertCWE.execute(); } } } } if (isBatchInsertEnabled()) { insertCWE.executeBatch(); } } } /** * Used when updating a vulnerability - in some cases a CVE needs to be * removed. * * @param cve the vulnerability CVE * @throws SQLException thrown if there is an error deleting the * vulnerability */ private void deleteVulnerability(String cve) throws SQLException { try (Connection conn = databaseManager.getConnection(); PreparedStatement deleteVulnerability = getPreparedStatement(conn, DELETE_VULNERABILITY, cve)) { deleteVulnerability.executeUpdate(); } } /** * Merges the list of known exploited vulnerabilities into the database. * * @param vulnerabilities the list of known exploited vulnerabilities * @throws DatabaseException thrown if there is an exception... duh.. * @throws SQLException thrown if there is an exception... duh.. */ public void updateKnownExploitedVulnerabilities( List vulnerabilities) throws DatabaseException, SQLException { try (Connection conn = databaseManager.getConnection(); PreparedStatement mergeKnownVulnerability = getPreparedStatement(conn, MERGE_KNOWN_EXPLOITED)) { int ctr = 0; for (org.owasp.dependencycheck.data.knownexploited.json.Vulnerability v : vulnerabilities) { mergeKnownVulnerability.setString(1, v.getCveID()); addNullableStringParameter(mergeKnownVulnerability, 2, v.getVendorProject()); addNullableStringParameter(mergeKnownVulnerability, 3, v.getProduct()); addNullableStringParameter(mergeKnownVulnerability, 4, v.getVulnerabilityName()); addNullableStringParameter(mergeKnownVulnerability, 5, v.getDateAdded()); addNullableStringParameter(mergeKnownVulnerability, 6, v.getShortDescription()); addNullableStringParameter(mergeKnownVulnerability, 7, v.getRequiredAction()); addNullableStringParameter(mergeKnownVulnerability, 8, v.getDueDate()); addNullableStringParameter(mergeKnownVulnerability, 9, v.getNotes()); if (isBatchInsertEnabled()) { mergeKnownVulnerability.addBatch(); ctr++; if (ctr >= getBatchSize()) { mergeKnownVulnerability.executeBatch(); ctr = 0; } } else { try { mergeKnownVulnerability.execute(); } catch (SQLException ex) { if (ex.getMessage().contains("Duplicate entry")) { final String msg = String.format("Duplicate known exploited vulnerability key identified in '%s'", v.getCveID()); LOGGER.info(msg, ex); } else { throw ex; } } } } if (isBatchInsertEnabled()) { mergeKnownVulnerability.executeBatch(); } } } /** * Used when updating a vulnerability - this method inserts the list of * vulnerable software. * * @param vulnerabilityId the vulnerability id * @param cveId the CVE ID - used for reporting * @param software the list of vulnerable software * @param baseEcosystem the ecosystem based off of the vulnerability * description * @throws DatabaseException thrown if there is an error inserting the data * @throws SQLException thrown if there is an error inserting the data */ private void updateVulnerabilityInsertSoftware(int vulnerabilityId, String cveId, List software, String baseEcosystem) throws DatabaseException, SQLException { try (Connection conn = databaseManager.getConnection(); PreparedStatement insertSoftware = getPreparedStatement(conn, INSERT_SOFTWARE)) { for (VulnerableSoftware parsedCpe : software) { insertSoftware.setInt(1, vulnerabilityId); insertSoftware.setString(2, parsedCpe.getPart().getAbbreviation()); insertSoftware.setString(3, parsedCpe.getVendor()); insertSoftware.setString(4, parsedCpe.getProduct()); insertSoftware.setString(5, parsedCpe.getVersion()); insertSoftware.setString(6, parsedCpe.getUpdate()); insertSoftware.setString(7, parsedCpe.getEdition()); insertSoftware.setString(8, parsedCpe.getLanguage()); insertSoftware.setString(9, parsedCpe.getSwEdition()); insertSoftware.setString(10, parsedCpe.getTargetSw()); insertSoftware.setString(11, parsedCpe.getTargetHw()); insertSoftware.setString(12, parsedCpe.getOther()); final String ecosystem = CpeEcosystemCache.getEcosystem(parsedCpe.getVendor(), parsedCpe.getProduct(), cveItemConverter.extractEcosystem(baseEcosystem, parsedCpe)); addNullableStringParameter(insertSoftware, 13, ecosystem); addNullableStringParameter(insertSoftware, 14, parsedCpe.getVersionEndExcluding()); addNullableStringParameter(insertSoftware, 15, parsedCpe.getVersionEndIncluding()); addNullableStringParameter(insertSoftware, 16, parsedCpe.getVersionStartExcluding()); addNullableStringParameter(insertSoftware, 17, parsedCpe.getVersionStartIncluding()); insertSoftware.setBoolean(18, parsedCpe.isVulnerable()); if (isBatchInsertEnabled()) { insertSoftware.addBatch(); } else { try { insertSoftware.execute(); } catch (SQLException ex) { if (ex.getMessage().contains("Duplicate entry")) { final String msg = String.format("Duplicate software key identified in '%s'", cveId); LOGGER.info(msg, ex); } else { throw ex; } } } } if (isBatchInsertEnabled()) { executeBatch(cveId, insertSoftware); } } } /** * Used when updating a vulnerability - this method inserts the list of * references. * * @param vulnerabilityId the vulnerability id * @param cve the CVE entry that contains the list of references * @throws SQLException thrown if there is an error inserting the data */ private void updateVulnerabilityInsertReferences(int vulnerabilityId, DefCveItem cve) throws SQLException { try (Connection conn = databaseManager.getConnection(); PreparedStatement insertReference = getPreparedStatement(conn, INSERT_REFERENCE)) { if (cve.getCve().getReferences() != null) { for (Reference r : cve.getCve().getReferences().getReferenceData()) { insertReference.setInt(1, vulnerabilityId); insertReference.setString(2, r.getName()); insertReference.setString(3, r.getUrl()); insertReference.setString(4, r.getRefsource()); if (isBatchInsertEnabled()) { insertReference.addBatch(); } else { insertReference.execute(); } } } if (isBatchInsertEnabled()) { insertReference.executeBatch(); } } } /** * Parses the configuration entries from the CVE entry into a list of * VulnerableSoftware objects. * * @param cve the CVE to parse the vulnerable software entries from * @return the list of vulnerable software * @throws CpeValidationException if an invalid CPE is present */ private List parseCpes(DefCveItem cve) throws CpeValidationException { final List software = new ArrayList<>(); final List cpeEntries = cve.getConfigurations().getNodes().stream() .collect(NodeFlatteningCollector.getInstance()) .collect(CpeMatchStreamCollector.getInstance()) .filter(predicate -> predicate.getCpe23Uri() != null) .filter(predicate -> predicate.getCpe23Uri().startsWith(cpeStartsWithFilter)) //this single CPE entry causes nearly 100% FP - so filtering it at the source. .filter(entry -> !("CVE-2009-0754".equals(cve.getCve().getCVEDataMeta().getId()) && "cpe:2.3:a:apache:apache:*:*:*:*:*:*:*:*".equals(entry.getCpe23Uri()))) .collect(Collectors.toList()); final VulnerableSoftwareBuilder builder = new VulnerableSoftwareBuilder(); try { cpeEntries.forEach(entry -> { builder.cpe(parseCpe(entry, cve.getCve().getCVEDataMeta().getId())) .versionEndExcluding(entry.getVersionEndExcluding()) .versionStartExcluding(entry.getVersionStartExcluding()) .versionEndIncluding(entry.getVersionEndIncluding()) .versionStartIncluding(entry.getVersionStartIncluding()) .vulnerable(entry.getVulnerable()); try { software.add(builder.build()); } catch (CpeValidationException ex) { throw new LambdaExceptionWrapper(ex); } }); } catch (LambdaExceptionWrapper ex) { throw (CpeValidationException) ex.getCause(); } return software; } /** * Helper method to convert a CpeMatch (generated code used in parsing the * NVD JSON) into a CPE object. * * @param cpe the CPE Match * @param cveId the CVE associated with the CPEMatch - used for error * reporting * @return the resulting CPE object * @throws DatabaseException thrown if there is an error converting the * CpeMatch into a CPE object */ private Cpe parseCpe(DefCpeMatch cpe, String cveId) throws DatabaseException { Cpe parsedCpe; try { //the replace is a hack as the NVD does not properly escape backslashes in their JSON parsedCpe = CpeParser.parse(cpe.getCpe23Uri(), true); } catch (CpeParsingException ex) { LOGGER.debug("NVD (" + cveId + ") contain an invalid 2.3 CPE: " + cpe.getCpe23Uri()); if (cpe.getCpe22Uri() != null && !cpe.getCpe22Uri().isEmpty()) { try { parsedCpe = CpeParser.parse(cpe.getCpe22Uri(), true); } catch (CpeParsingException ex2) { throw new DatabaseException("Unable to parse CPE: " + cpe.getCpe23Uri(), ex); } } else { throw new DatabaseException("Unable to parse CPE: " + cpe.getCpe23Uri(), ex); } } return parsedCpe; } /** * Returns the size of the batch. * * @return the size of the batch */ private int getBatchSize() { int max; try { max = settings.getInt(Settings.KEYS.MAX_BATCH_SIZE); } catch (InvalidSettingException pE) { max = 1000; } return max; } /** * Determines whether or not batch insert is enabled. * * @return true if batch insert is enabled; otherwise * false */ private boolean isBatchInsertEnabled() { boolean batch; try { batch = settings.getBoolean(Settings.KEYS.ENABLE_BATCH_UPDATES); } catch (InvalidSettingException pE) { //If there's no configuration, default is to not perform batch inserts batch = false; } return batch; } /** * Executes batch inserts of vulnerabilities when property * database.batchinsert.maxsize is reached. * * @param vulnId the vulnerability ID * @param statement the prepared statement to batch execute * @throws SQLException thrown when the batch cannot be executed */ private void executeBatch(String vulnId, PreparedStatement statement) throws SQLException { try { statement.executeBatch(); } catch (SQLException ex) { if (ex.getMessage().contains("Duplicate entry")) { final String msg = String.format("Duplicate software key identified in '%s'", vulnId); LOGGER.info(msg, ex); } else { throw ex; } } } /** * Checks to see if data exists so that analysis can be performed. * * @return true if data exists; otherwise false */ public boolean dataExists() { try (Connection conn = databaseManager.getConnection(); PreparedStatement cs = getPreparedStatement(conn, COUNT_CPE); ResultSet rs = cs.executeQuery()) { if (rs.next() && rs.getInt(1) > 0) { return true; } } catch (Exception ex) { String dd; try { dd = settings.getDataDirectory().getAbsolutePath(); } catch (IOException ex1) { dd = settings.getString(Settings.KEYS.DATA_DIRECTORY); } LOGGER.error("Unable to access the local database.\n\nEnsure that '{}' is a writable directory. " + "If the problem persist try deleting the files in '{}' and running {} again. If the problem continues, please " + "create a log file (see documentation at https://jeremylong.github.io/DependencyCheck/) and open a ticket at " + "https://github.com/jeremylong/DependencyCheck/issues and include the log file.\n\n", dd, dd, settings.getString(Settings.KEYS.APPLICATION_NAME)); LOGGER.debug("", ex); } return false; } /** * It is possible that orphaned rows may be generated during database * updates. This should be called after all updates have been completed to * ensure orphan entries are removed. */ public void cleanupDatabase() { LOGGER.info("Begin database maintenance"); final long start = System.currentTimeMillis(); try (Connection conn = databaseManager.getConnection(); PreparedStatement psOrphans = getPreparedStatement(conn, CLEANUP_ORPHANS); PreparedStatement psEcosystem = getPreparedStatement(conn, UPDATE_ECOSYSTEM); PreparedStatement psEcosystem2 = getPreparedStatement(conn, UPDATE_ECOSYSTEM2)) { if (psEcosystem != null) { final int count = psEcosystem.executeUpdate(); if (count > 0) { LOGGER.info("Updated the CPE ecosystem on {} NVD records", count); } } if (psEcosystem2 != null) { final int count = psEcosystem2.executeUpdate(); if (count > 0) { LOGGER.info("Removed the CPE ecosystem on {} NVD records", count); } } if (psOrphans != null) { final int count = psOrphans.executeUpdate(); if (count > 0) { LOGGER.info("Cleaned up {} orphaned NVD records", count); } } final long millis = System.currentTimeMillis() - start; //final long seconds = TimeUnit.MILLISECONDS.toSeconds(millis); LOGGER.info("End database maintenance ({} ms)", millis); } catch (SQLException ex) { LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details."); LOGGER.debug("", ex); throw new DatabaseException("Unexpected SQL Exception", ex); } } /** * Persist the EcosystemCache into the database. */ public void persistEcosystemCache() { saveCpeEcosystemCache(); clearCache(); } /** * If the database is using an H2 file based database calling * defrag() will de-fragment the database. */ public void defrag() { if (isH2) { final long start = System.currentTimeMillis(); try (Connection conn = databaseManager.getConnection(); CallableStatement psCompaxt = conn.prepareCall("SHUTDOWN DEFRAG")) { LOGGER.info("Begin database defrag"); psCompaxt.execute(); final long millis = System.currentTimeMillis() - start; //final long seconds = TimeUnit.MILLISECONDS.toSeconds(millis); LOGGER.info("End database defrag ({} ms)", millis); } catch (SQLException ex) { LOGGER.error("An unexpected SQL Exception occurred compacting the database; please see the verbose log for more details."); LOGGER.debug("", ex); } } } /** * Determines if the given identifiedVersion is affected by the given cpeId * and previous version flag. A non-null, non-empty string passed to the * previous version argument indicates that all previous versions are * affected. * * @param cpe the CPE for the given dependency * @param vulnerableSoftware a set of the vulnerable software * @return true if the identified version is affected, otherwise false */ VulnerableSoftware getMatchingSoftware(Cpe cpe, Set vulnerableSoftware) { VulnerableSoftware matched = null; for (VulnerableSoftware vs : vulnerableSoftware) { if (vs.matches(cpe)) { if (matched == null) { matched = vs; } else { if ("*".equals(vs.getWellFormedUpdate()) && !"*".equals(matched.getWellFormedUpdate())) { matched = vs; } } } } return matched; } /** * This method is only referenced in unused code. *

* Deletes unused dictionary entries from the database. *

*/ public void deleteUnusedCpe() { clearCache(); try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, DELETE_UNUSED_DICT_CPE)) { ps.executeUpdate(); } catch (SQLException ex) { LOGGER.error("Unable to delete CPE dictionary entries", ex); } } /** * This method is only referenced in unused code and will likely break on * MySQL if ever used due to the MERGE statement. *

* Merges CPE entries into the database. *

* * @param cpe the CPE identifier * @param vendor the CPE vendor * @param product the CPE product */ public void addCpe(String cpe, String vendor, String product) { clearCache(); try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, ADD_DICT_CPE)) { ps.setString(1, cpe); ps.setString(2, vendor); ps.setString(3, product); ps.executeUpdate(); } catch (SQLException ex) { LOGGER.error("Unable to add CPE dictionary entry", ex); } } /** * Returns a map of known exploited vulnerabilities. * * @return a map of known exploited vulnerabilities */ public Map getknownExploitedVulnerabilities() { final Map known = new HashMap<>(); try (Connection conn = databaseManager.getConnection(); PreparedStatement ps = getPreparedStatement(conn, SELECT_KNOWN_EXPLOITED_VULNERABILITIES); ResultSet rs = ps.executeQuery()) { while (rs.next()) { final org.owasp.dependencycheck.data.knownexploited.json.Vulnerability kev = new org.owasp.dependencycheck.data.knownexploited.json.Vulnerability(); kev.setCveID(rs.getString(1)); kev.setVendorProject(rs.getString(2)); kev.setProduct(rs.getString(3)); kev.setVulnerabilityName(rs.getString(4)); kev.setDateAdded(rs.getString(5)); kev.setShortDescription(rs.getString(6)); kev.setRequiredAction(rs.getString(7)); kev.setDueDate(rs.getString(8)); kev.setNotes(rs.getString(9)); known.put(kev.getCveID(), kev); } } catch (SQLException ex) { throw new DatabaseException(ex); } return known; } /** * Helper method to add a nullable string parameter. * * @param ps the prepared statement * @param pos the position of the parameter * @param value the value of the parameter * @throws SQLException thrown if there is an error setting the parameter. */ private void addNullableStringParameter(PreparedStatement ps, int pos, String value) throws SQLException { if (value == null || value.isEmpty()) { ps.setNull(pos, java.sql.Types.VARCHAR); } else { ps.setString(pos, value); } } /** * Sets the float parameter on a prepared statement from a properties map. * * @param ps a prepared statement * @param i the index of the property * @param props the property collection * @param key the property key * @throws SQLException thrown if there is an error adding the property */ private void setFloatValue(PreparedStatement ps, int i, Map props, String key) throws SQLException { if (props != null && props.containsKey(key)) { try { ps.setFloat(i, Float.parseFloat(props.get(key).toString())); } catch (NumberFormatException nfe) { ps.setNull(i, java.sql.Types.NULL); } } else { ps.setNull(i, java.sql.Types.NULL); } } /** * Sets the string parameter on a prepared statement from a properties map. * * @param ps a prepared statement * @param i the index of the property * @param props the property collection * @param key the property key * @throws SQLException thrown if there is an error adding the property */ private void setStringValue(PreparedStatement ps, int i, Map props, String key) throws SQLException { if (props != null && props.containsKey(key)) { ps.setString(i, props.get(key).toString()); } else { ps.setNull(i, java.sql.Types.NULL); } } /** * Sets the boolean parameter on a prepared statement from a properties map. * * @param ps a prepared statement * @param i the index of the property * @param props the property collection * @param key the property key * @throws SQLException thrown if there is an error adding the property */ private void setBooleanValue(PreparedStatement ps, int i, Map props, String key) throws SQLException { if (props != null && props.containsKey(key)) { ps.setBoolean(i, Boolean.parseBoolean(props.get(key).toString())); } else { ps.setNull(i, java.sql.Types.NULL); } } /** * Returns the Boolean value for the given index; if the value is null then * null is returned. * * @param rs the record set * @param index the parameter index * @return the Boolean value; or null * @throws SQLException thrown if there is an error obtaining the value */ @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL") private Boolean getBooleanValue(ResultSet rs, int index) throws SQLException { if (rs.getObject(index) == null) { return null; } return rs.getBoolean(index); } /** * Returns the Float value for the given index; if the value is null then * null is returned. * * @param rs the record set * @param index the parameter index * @return the Float value; or null * @throws SQLException thrown if there is an error obtaining the value */ private Float getFloatValue(ResultSet rs, int index) throws SQLException { if (rs.getObject(index) == null) { return null; } return rs.getFloat(index); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy