SeverityUtil.java
/*
* 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) 2019 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility to estimate severity level scores.
*
* @author Jeremy Long
*/
public final class SeverityUtil {
/**
* The cutoff for high severity.
*/
private static final Double HIGH = 10.0;
/**
* The cutoff for medium severity.
*/
private static final Double MEDIUM = 6.9;
/**
* The cutoff for low severity.
*/
private static final Double LOW = 3.9;
/**
* The cutoff for none severity.
*/
private static final Double NONE = 0.0;
/**
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(SeverityUtil.class);
/**
* Private constructor for a utility class.
*/
private SeverityUtil() {
//noop
}
/**
* Estimates the CVSS V2 Score based on a given severity. The implementation
* will default to 3.9 if no recognized "severity" level is given (critical,
* high, low).
*
* @param severity the severity text (e.g. "medium")
* @return a score from 0 to 10
*/
public static Double estimateCvssV2(String severity) {
switch (severity == null ? "none" : severity.toLowerCase()) {
case "critical":
case "high":
return HIGH;
case "moderate":
case "medium":
return MEDIUM;
case "info":
case "none":
case "informational":
return NONE;
case "low":
case "unknown":
default:
return LOW;
}
}
/**
* Converts a textual severity to the text that should be used to signal it
* in a report.
*
* @param severity The textual unscored severity
* @return The severity when properly recognized, otherwise the severity
* extended with a remark that it was not recognized and assumed to
* represent a critical severity.
*/
public static String unscoredToSeveritytext(final String severity) {
switch (Severity.forUnscored(severity)) {
case CRITICAL:
case HIGH:
case MEDIUM:
case LOW:
case INFO:
return severity;
case ASSUMED_CRITICAL:
default:
final String sevText;
if ("0.0".equals(severity)) {
sevText = "Unknown";
} else {
sevText = severity;
}
return sevText + " (not recognized; assumed to be critical)";
}
}
/**
* Creates an estimated sort-adjusted CVSSv3 score for an unscored textual
* severity. For recognized severities below critical it returns a value at
* the lower bound of the CVSSv3 baseScore for that severity. For recognized
* critical severities it returns a score in-between the upper bound of the
* HIGH CVSSv2 score and the lowest sort-adjusted CVSSv3 critical score, so
* that unscored critical vulnerabilties are ordered in between CRITICAL
* scored CVSSv3 rated vulnerabilities and HIGH-scored CVSSv2 rated
* vulnerabilities. For unrecognized severities it returns a score
* in-between the top HIGH CVSSv2 score and the estimatedSortAdjustedCVSSv3
* score for an unscored severity recognized as critical, so that recognized
* critical will win over unrecognized severities while unrecognized
* severities are assumed to be of a critical nature.
*
* @param severity The textual severity, may be null
* @return A float that can be used to numerically sort vulnerabilities in
* approximated severity (highest float represents highest severity).
* @see #sortAdjustedCVSSv3BaseScore(Double)
*/
public static Double estimatedSortAdjustedCVSSv3(final String severity) {
switch (Severity.forUnscored(severity)) {
case CRITICAL:
return 10.2;
case HIGH:
return 7.0;
case MEDIUM:
return 4.0;
case LOW:
return 0.1;
case INFO:
return 0.0;
case ASSUMED_CRITICAL:
default:
SeverityUtil.LOGGER.debug("Unrecognized unscored textual severity: {}, assuming critical score as worst-case "
+ "estimate for sorting",
severity);
return 10.1;
}
}
/**
* Compute an adjusted CVSSv3 baseScore that ensures that CRITICAL CVSSv3
* scores will win over HIGH CVSSv2 and CRITICAL unscored severities to
* allow for a best-effort sorting that enables the report to list a
* reliable 'highest severity' in the report.
*
* @param cvssV3BaseScore The cvssV3 baseScore severity of a vulnerability
* @return The cvssV3 baseScore, adjusted if necessary in order to guarantee
* that CVSSv3 CRITICAL scores will rate higher than CVSSv2 HIGH, unscored
* critical severities and unscored unrecognized severities (which are
* assumed for sorting to be of a critical nature)
* @see #estimatedSortAdjustedCVSSv3(String)
*/
public static Double sortAdjustedCVSSv3BaseScore(final Double cvssV3BaseScore) {
if (cvssV3BaseScore.floatValue() >= 9.0f) {
return cvssV3BaseScore + 1.3;
}
return cvssV3BaseScore;
}
/**
* An enum to translate unscored severity texts to a severity level of a
* defined set of severities. Allows for re-use of the text-to-severity
* mapping in multiple helper methods.
*/
private enum Severity {
/**
* A severity level for textual values that should be regarded as
* accompanying a critical severity vulnerability
*/
CRITICAL,
/**
* A severity level for textual values that are not recognized and
* therefor assumed to be accompanying a critical severity vulnerability
*/
ASSUMED_CRITICAL,
/**
* A severity level for textual values that should be regarded as
* accompanying a high severity vulnerability
*/
HIGH,
/**
* A severity level for textual values that should be regarded as
* accompanying a medium severity vulnerability
*/
MEDIUM,
/**
* A severity level for textual values that should be regarded as
* accompanying a low severity vulnerability
*/
LOW,
/**
* A severity level for textual values that should be regarded as
* accompanying a vulnerability of informational nature
*/
INFO;
public static Severity forUnscored(String value) {
switch (value == null ? "none" : value.toLowerCase()) {
case "critical":
return CRITICAL;
case "high":
return HIGH;
case "moderate":
case "medium":
return MEDIUM;
case "info":
case "informational":
return INFO;
case "low":
case "unknown":
case "none":
return LOW;
default:
return ASSUMED_CRITICAL;
}
}
}
}