From 5dcdd7f7ce7ad82ea4ea1367dfce5ee3252849b4 Mon Sep 17 00:00:00 2001 From: Steffen Ohrendorf Date: Fri, 5 Sep 2025 21:21:42 +0200 Subject: [PATCH] normalize VULNERABLESOFTWARE table, make CPE matching case-insensitive Signed-off-by: Steffen Ohrendorf --- .../model/VulnerableSoftware.java | 6 +-- .../VulnerableSoftwareQueryManager.java | 6 +-- .../FuzzyVulnerableSoftwareSearchManager.java | 5 +- .../search/VulnerableSoftwareIndexer.java | 6 ++- ...bstractVulnerableSoftwareAnalysisTask.java | 10 ++-- .../dependencytrack/upgrade/UpgradeItems.java | 1 + .../upgrade/v4135/v4135Updater.java | 50 +++++++++++++++++++ ...zyVulnerableSoftwareSearchManagerTest.java | 2 +- .../InternalAnalysisTaskCpeMatchingTest.java | 2 +- 9 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/dependencytrack/upgrade/v4135/v4135Updater.java diff --git a/src/main/java/org/dependencytrack/model/VulnerableSoftware.java b/src/main/java/org/dependencytrack/model/VulnerableSoftware.java index e0ba60a2b1..83ae6d9177 100644 --- a/src/main/java/org/dependencytrack/model/VulnerableSoftware.java +++ b/src/main/java/org/dependencytrack/model/VulnerableSoftware.java @@ -262,7 +262,7 @@ public String getPart() { } public void setPart(String part) { - this.part = part; + this.part = part == null ? null : part.toLowerCase(); } public String getVendor() { @@ -270,7 +270,7 @@ public String getVendor() { } public void setVendor(String vendor) { - this.vendor = vendor; + this.vendor = vendor == null ? null : vendor.toLowerCase(); } public String getProduct() { @@ -278,7 +278,7 @@ public String getProduct() { } public void setProduct(String product) { - this.product = product; + this.product = product == null ? null : product.toLowerCase(); } public String getVersion() { diff --git a/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java index 149e441f50..73355c6d44 100644 --- a/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/VulnerableSoftwareQueryManager.java @@ -276,9 +276,9 @@ public List getAllVulnerableSoftware( // SELECT "ID" FROM "VULNERABLESOFTWARE" WHERE "PART" = 'foo' ... if (cpePart != null && cpeVendor != null && cpeProduct != null) { - final List partConditions = buildCpeFilterConditions("\"PART\"", cpePart); - final List vendorConditions = buildCpeFilterConditions("\"VENDOR\"", cpeVendor); - final List productConditions = buildCpeFilterConditions("\"PRODUCT\"", cpeProduct); + final List partConditions = buildCpeFilterConditions("\"PART\"", cpePart.toLowerCase()); + final List vendorConditions = buildCpeFilterConditions("\"VENDOR\"", cpeVendor.toLowerCase()); + final List productConditions = buildCpeFilterConditions("\"PRODUCT\"", cpeProduct.toLowerCase()); for (final CpeFilterCondition partCondition : partConditions) { for (final CpeFilterCondition vendorCondition : vendorConditions) { diff --git a/src/main/java/org/dependencytrack/search/FuzzyVulnerableSoftwareSearchManager.java b/src/main/java/org/dependencytrack/search/FuzzyVulnerableSoftwareSearchManager.java index 23c422d468..d48dd2d3c2 100644 --- a/src/main/java/org/dependencytrack/search/FuzzyVulnerableSoftwareSearchManager.java +++ b/src/main/java/org/dependencytrack/search/FuzzyVulnerableSoftwareSearchManager.java @@ -45,7 +45,6 @@ import us.springett.parsers.cpe.values.Part; import java.io.IOException; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -262,7 +261,7 @@ public static String getLuceneCpeRegexp(String cpeString) { private static String getComponentRegex(String component) { if (component != null) { - return component.replace("*", ".*"); + return component.replace("*", ".*").toLowerCase(); } else { return ".*"; } @@ -274,7 +273,7 @@ private static String escapeLuceneQuery(final String input) { } else if (input.equals(".*")) { return input; } - return QueryParser.escape(input); + return QueryParser.escape(input.toLowerCase()); } } diff --git a/src/main/java/org/dependencytrack/search/VulnerableSoftwareIndexer.java b/src/main/java/org/dependencytrack/search/VulnerableSoftwareIndexer.java index 2f13b518a5..388a0f52b7 100644 --- a/src/main/java/org/dependencytrack/search/VulnerableSoftwareIndexer.java +++ b/src/main/java/org/dependencytrack/search/VulnerableSoftwareIndexer.java @@ -126,8 +126,10 @@ private static List fetchNext(final QueryManager qm, private Document convertToDocument(final VulnerableSoftwareDocument vs) { final var doc = new Document(); addField(doc, IndexConstants.VULNERABLESOFTWARE_UUID, vs.uuid().toString(), Field.Store.YES, false); - addField(doc, IndexConstants.VULNERABLESOFTWARE_CPE_22, vs.cpe22(), Field.Store.YES, false); - addField(doc, IndexConstants.VULNERABLESOFTWARE_CPE_23, vs.cpe23(), Field.Store.YES, false); + final var cpe22 = vs.cpe22() != null ? vs.cpe22().toLowerCase() : null; + addField(doc, IndexConstants.VULNERABLESOFTWARE_CPE_22, cpe22, Field.Store.YES, false); + final var cpe23 = vs.cpe23() != null ? vs.cpe23().toLowerCase() : null; + addField(doc, IndexConstants.VULNERABLESOFTWARE_CPE_23, cpe23, Field.Store.YES, false); addField(doc, IndexConstants.VULNERABLESOFTWARE_VENDOR, vs.vendor(), Field.Store.YES, true); addField(doc, IndexConstants.VULNERABLESOFTWARE_PRODUCT, vs.product(), Field.Store.YES, true); addField(doc, IndexConstants.VULNERABLESOFTWARE_VERSION, vs.version(), Field.Store.YES, true); diff --git a/src/main/java/org/dependencytrack/tasks/scanners/AbstractVulnerableSoftwareAnalysisTask.java b/src/main/java/org/dependencytrack/tasks/scanners/AbstractVulnerableSoftwareAnalysisTask.java index 20742afdea..03e372ebfb 100644 --- a/src/main/java/org/dependencytrack/tasks/scanners/AbstractVulnerableSoftwareAnalysisTask.java +++ b/src/main/java/org/dependencytrack/tasks/scanners/AbstractVulnerableSoftwareAnalysisTask.java @@ -65,6 +65,10 @@ protected void analyzeVersionRange(final QueryManager qm, final List relations = List.of( - Cpe.compareAttribute(vs.getPart(), targetCpe.getPart().getAbbreviation()), - Cpe.compareAttribute(vs.getVendor(), targetCpe.getVendor()), - Cpe.compareAttribute(vs.getProduct(), targetCpe.getProduct()), + Cpe.compareAttribute(vs.getPart(), toLowerCaseNullable(targetCpe.getPart().getAbbreviation())), + Cpe.compareAttribute(vs.getVendor(), toLowerCaseNullable(targetCpe.getVendor())), + Cpe.compareAttribute(vs.getProduct(), toLowerCaseNullable(targetCpe.getProduct())), Cpe.compareAttribute(vs.getVersion(), targetVersion), Cpe.compareAttribute(vs.getUpdate(), targetCpe.getUpdate()), Cpe.compareAttribute(vs.getEdition(), targetCpe.getEdition()), diff --git a/src/main/java/org/dependencytrack/upgrade/UpgradeItems.java b/src/main/java/org/dependencytrack/upgrade/UpgradeItems.java index dc6dd487ee..4509a40075 100644 --- a/src/main/java/org/dependencytrack/upgrade/UpgradeItems.java +++ b/src/main/java/org/dependencytrack/upgrade/UpgradeItems.java @@ -45,6 +45,7 @@ class UpgradeItems { UPGRADE_ITEMS.add(org.dependencytrack.upgrade.v4130.v4130Updater.class); UPGRADE_ITEMS.add(org.dependencytrack.upgrade.v4130.v4130_1Updater.class); UPGRADE_ITEMS.add(org.dependencytrack.upgrade.v4131.v4131Updater.class); + UPGRADE_ITEMS.add(org.dependencytrack.upgrade.v4135.v4135Updater.class); } static List> getUpgradeItems() { diff --git a/src/main/java/org/dependencytrack/upgrade/v4135/v4135Updater.java b/src/main/java/org/dependencytrack/upgrade/v4135/v4135Updater.java new file mode 100644 index 0000000000..ecb0f5f102 --- /dev/null +++ b/src/main/java/org/dependencytrack/upgrade/v4135/v4135Updater.java @@ -0,0 +1,50 @@ +/* + * This file is part of Dependency-Track. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.dependencytrack.upgrade.v4135; + +import alpine.common.logging.Logger; +import alpine.persistence.AlpineQueryManager; +import alpine.server.upgrade.AbstractUpgradeItem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +public class v4135Updater extends AbstractUpgradeItem { + private static final Logger LOGGER = Logger.getLogger(v4135Updater.class); + + @Override + public String getSchemaVersion() { + return "4.13.5"; + } + + @Override + public void executeUpgrade(final AlpineQueryManager qm, final Connection connection) throws Exception { + normalizeCpeData(connection); + } + + private void normalizeCpeData(final Connection connection) throws SQLException { + try (final Statement statement = connection.createStatement()) { + LOGGER.info("Normalizing \"VULNERABLESOFTWARE\" CPE columns"); + statement.execute(/* language=SQL */ """ + UPDATE TABLE "VULNERABLESOFTWARE" SET "PART" = LOWER("PART"), "VENDOR" = LOWER("VENDOR"), "PRODUCT" = LOWER("PRODUCT") + """); + } + } +} diff --git a/src/test/java/org/dependencytrack/search/FuzzyVulnerableSoftwareSearchManagerTest.java b/src/test/java/org/dependencytrack/search/FuzzyVulnerableSoftwareSearchManagerTest.java index 77f5223927..cec05004c1 100644 --- a/src/test/java/org/dependencytrack/search/FuzzyVulnerableSoftwareSearchManagerTest.java +++ b/src/test/java/org/dependencytrack/search/FuzzyVulnerableSoftwareSearchManagerTest.java @@ -101,7 +101,7 @@ void getLuceneCpeRegexp() throws CpeValidationException, CpeEncodingException { us.springett.parsers.cpe.Cpe os = new us.springett.parsers.cpe.Cpe( Part.OPERATING_SYSTEM, "vendor", "product", "1\\.0", "2", "33","en", "inside", "Vista", "x86", "other"); Assertions.assertEquals("cpe23:/cpe\\:2\\.3\\:a\\:.*\\:.*\\:.*\\:.*\\:.*\\:.*\\:.*\\:.*\\:.*\\:.*/", FuzzyVulnerableSoftwareSearchManager.getLuceneCpeRegexp("cpe:2.3:a:*:*:*:*:*:*:*:*:*:*")); - Assertions.assertEquals("cpe23:/cpe\\:2\\.3\\:o\\:vendor\\:product\\:1.0\\:2\\:33\\:en\\:inside\\:Vista\\:x86\\:other/", FuzzyVulnerableSoftwareSearchManager.getLuceneCpeRegexp(os.toCpe23FS())); + Assertions.assertEquals("cpe23:/cpe\\:2\\.3\\:o\\:vendor\\:product\\:1.0\\:2\\:33\\:en\\:inside\\:vista\\:x86\\:other/", FuzzyVulnerableSoftwareSearchManager.getLuceneCpeRegexp(os.toCpe23FS())); Assertions.assertEquals("cpe22:/cpe\\:\\/o\\:vendor\\:product\\:1.0\\:2\\:33\\:en/", FuzzyVulnerableSoftwareSearchManager.getLuceneCpeRegexp(os.toCpe22Uri())); } diff --git a/src/test/java/org/dependencytrack/tasks/scanners/InternalAnalysisTaskCpeMatchingTest.java b/src/test/java/org/dependencytrack/tasks/scanners/InternalAnalysisTaskCpeMatchingTest.java index 343a0f76a6..1c88229b7a 100644 --- a/src/test/java/org/dependencytrack/tasks/scanners/InternalAnalysisTaskCpeMatchingTest.java +++ b/src/test/java/org/dependencytrack/tasks/scanners/InternalAnalysisTaskCpeMatchingTest.java @@ -353,7 +353,7 @@ public static Collection parameters() { // Note: CPEs with uppercase "part" are considered invalid by the cpe-parser library. // TODO: This should match, but can't currently support this as it would require an function index on UPPER("PART"), // UPPER("VENDOR"), and UPPER("PRODUCT"), which we cannot add through JDO annotations. - Arguments.of("cpe:2.3:o:lInUx:lInUx_KeRnEl:5.15.37:*:*:*:*:*:*:*", WITHOUT_RANGE, DOES_NOT_MATCH, "cpe:2.3:o:LiNuX:LiNuX_kErNeL:5.15.37:*:*:*:*:*:*:*"), + Arguments.of("cpe:2.3:o:lInUx:lInUx_KeRnEl:5.15.37:*:*:*:*:*:*:*", WITHOUT_RANGE, MATCHES, "cpe:2.3:o:LiNuX:LiNuX_kErNeL:5.15.37:*:*:*:*:*:*:*"), // --- // Issue: https://github.com/DependencyTrack/dependency-track/issues/2988 // Scenario: "other" attribute of source is NA, "other" attribute of target is ANY -> SUBSET.