////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2009-2014 Denim Group, Ltd.
//
// The contents of this file are subject to the Mozilla Public 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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is ThreadFix.
//
// The Initial Developer of the Original Code is Denim Group, Ltd.
// Portions created by Denim Group, Ltd. are Copyright (C)
// Denim Group, Ltd. All Rights Reserved.
//
// Contributor(s): Denim Group, Ltd.
//
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.service.merge;
import com.denimgroup.threadfix.data.entities.Finding;
import com.denimgroup.threadfix.data.entities.GenericSeverity;
import com.denimgroup.threadfix.data.entities.GenericVulnerability;
import com.denimgroup.threadfix.data.entities.Vulnerability;
import com.denimgroup.threadfix.logging.SanitizedLogger;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Calendar;
public class VulnerabilityParser {
private final static SanitizedLogger log = new SanitizedLogger(VulnerabilityParser.class);
private VulnerabilityParser(){}
/**
* Find the hashed vulnerability ID(s) and put them into a vulnerability
* object.
*
* THIS METHOD REQUIRES THE CHANNEL VULN AND EITHER PARAMETER OR PATH TO
* ALREADY BE SET
*
* @return resulting Vulnerability
*/
public static Vulnerability parse(Finding finding) {
if (finding == null) {
log.warn("Unable to parse a vulnerability due to a null Finding.");
return null;
}
if (finding.getChannelVulnerability() == null) {
log.warn("The finding did not have a ChannelVulnerability so no vulnerability could be parsed.");
return null;
}
Vulnerability returnVulnerability = null;
String locationVariableHash, locationHash, variableHash;
GenericVulnerability genericVulnerability = finding.getChannelVulnerability().getGenericVulnerability();
if (genericVulnerability == null
|| genericVulnerability.getName() == null
|| genericVulnerability.getName().trim().equals("")) {
log.warn("No generic vulnerability was found for the Channel Vulnerability with code "
+ finding.getChannelVulnerability().getCode());
return null;
}
Vulnerability vulnerability = new Vulnerability();
vulnerability.openVulnerability(Calendar.getInstance());
vulnerability.setGenericVulnerability(genericVulnerability);
vulnerability.setSurfaceLocation(finding.getSurfaceLocation());
// TODO calculate some sort of threshold here and figure out whether or not we want to keep
// the calculated url path or not.
vulnerability.setCalculatedUrlPath(finding.getCalculatedUrlPath());
if (finding.getIsStatic()) {
vulnerability.setCalculatedFilePath(finding.getCalculatedFilePath());
}
if (finding.isMarkedFalsePositive()) {
log.info("Creating a false positive vulnerability from a finding marked false positive.");
vulnerability.setIsFalsePositive(finding.isMarkedFalsePositive());
}
String vulnName = genericVulnerability.getName();
if (finding.getChannelSeverity() != null) {
vulnerability.setGenericSeverity(getGenericSeverity(finding));
}
String param = null;
if (finding.getSurfaceLocation() != null) {
param = finding.getSurfaceLocation().getParameter();
}
if (finding.getSurfaceLocation() != null
&& finding.getSurfaceLocation().getPath() != null
&& !finding.getSurfaceLocation().getPath().equals("")) {
if (param != null) {
// if we get here, all three variables are present. Hash all of
// them.
locationVariableHash = hashFindingInfo(vulnName, finding
.getSurfaceLocation().getPath(), param);
locationHash = hashFindingInfo(vulnName, finding
.getSurfaceLocation().getPath(), null);
variableHash = hashFindingInfo(vulnName, null, param);
vulnerability.setLocationVariableHash(locationVariableHash);
vulnerability.setLocationHash(locationHash);
vulnerability.setVariableHash(variableHash);
returnVulnerability = vulnerability;
} else {
// if we get here, we just have location and CWE.
locationHash = hashFindingInfo(vulnName, finding
.getSurfaceLocation().getPath(), null);
vulnerability.setLocationHash(locationHash);
returnVulnerability = vulnerability;
}
} else if (param != null) {
// if we get here, we have variable and CWE
variableHash = hashFindingInfo(vulnName, null, param);
vulnerability.setVariableHash(variableHash);
returnVulnerability = vulnerability;
} else {
log.warn("The finding had neither path nor parameter and no vulnerability could be parsed.");
}
if (returnVulnerability != null) {
vulnerability.setFindings(new ArrayList<Finding>());
vulnerability.getFindings().add(finding);
finding.setFirstFindingForVuln(true);
finding.setVulnerability(vulnerability);
}
return returnVulnerability;
}
/**
* Hashes whatever three strings are given to it.
*
* @param type
* The generic, CWE type of vulnerability.
* @param url
* The URL location of the vulnerability.
* @param param
* The vulnerable parameter (optional)
* @return The three strings concatenated, downcased, trimmed, and hashed.
*/
private static String hashFindingInfo(String type, String url, String param) {
StringBuffer toHash = new StringBuffer();
if (type != null) {
toHash = toHash.append(type.toLowerCase().trim());
}
if (url != null) {
if (url.indexOf('/') == 0 || url.indexOf('\\') == 0) {
toHash = toHash.append(url.substring(1).toLowerCase().trim());
} else {
toHash = toHash.append(url.toLowerCase().trim());
}
}
if (param != null) {
toHash = toHash.append(param.toLowerCase().trim());
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(toHash.toString().getBytes(), 0,
toHash.length());
return new BigInteger(1, messageDigest.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
public static void addToVuln(Vulnerability vuln, Finding finding) {
vuln.getFindings().add(finding);
// update the generic severity
GenericSeverity higherGenericSeverity = getHigherSeverity(finding, vuln);
vuln.setGenericSeverity(higherGenericSeverity);
vuln.setOriginalGenericSeverity(higherGenericSeverity);
finding.setVulnerability(vuln);
}
private static GenericSeverity getHigherSeverity(Finding finding, Vulnerability vuln) {
GenericSeverity
findingSeverity = getGenericSeverity(finding),
vulnSeverity = vuln.getGenericSeverity(),
returnSeverity = null;
if (findingSeverity != null) {
if (vulnSeverity == null) {
returnSeverity = findingSeverity;
} else if (vulnSeverity.getIntValue() != null &&
findingSeverity.getIntValue() != null &&
vulnSeverity.getIntValue() < findingSeverity.getIntValue()) {
returnSeverity = findingSeverity;
}
}
if (returnSeverity == null) {
returnSeverity = vulnSeverity;
}
return returnSeverity;
}
private static GenericSeverity getGenericSeverity(Finding finding) {
GenericSeverity severity = null;
if (finding != null && finding.getChannelSeverity() == null) {
log.warn("No Channel Severity found for " + finding.getChannelVulnerability().getName());
}
if (finding != null && finding.getChannelSeverity()!= null && finding.getChannelSeverity().getSeverityMap() != null)
severity = finding.getChannelSeverity().getSeverityMap().getGenericSeverity();
return severity;
}
}