diff --git a/README.md b/README.md index 146937ba8e..80111f6ae0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # OWASP Benchmark -The OWASP Benchmark Project is a Java test suite designed to verify the speed and accuracy of vulnerability detection tools. The initial version is intended to support Static Analysis Security Testing Tools (SAST). A future release will support Dynamic Analysis Security Testing Tools (DAST), like OWASP ZAP, and Interactive Analysis Security Testing Tools (IAST). The goal is that this test application is fully runnable and all the vulnerabilities are actually exploitable so its a fair test for any kind of application vulnerability detection tool. +The OWASP Benchmark Project is a Java test suite designed to verify the speed and accuracy of vulnerability detection tools. It is a fully runnable open source web application that can be analyzed by any type of Application Security Testing (AST) tool, including SAST, DAST (like OWASP ZAP), and IAST tools. The intent is that all the vulnerabilities deliberately included in and scored by the Benchmark are actually exploitable so its a fair test for any kind of application vulnerability detection tool. The Benchmark also includes scorecard generators for numerous open source and commercial AST tools, and the set of supported tools is growing all the time. The project documentation is all on the OWASP site at the OWASP Benchmark project pages. Please refer to that site for all the project details. The current latest release is v1.2. Note that all the releases that are available here: https://github.com/OWASP/Benchmark/releases, are historical. The latest release is always available live by simply cloning or pulling the head of this repository (i.e., git pull). + diff --git a/VMs/Dockerfile b/VMs/Dockerfile index e1918733ce..aa76db4f7c 100644 --- a/VMs/Dockerfile +++ b/VMs/Dockerfile @@ -2,7 +2,7 @@ FROM ubuntu:latest MAINTAINER "Dave Wichers dave.wichers@owasp.org" -RUN apt-get update && apt-get clean +RUN apt-get update RUN apt-get install -q -y \ openjdk-8-jre-headless \ openjdk-8-jdk \ diff --git a/VMs/buildDockerImage.sh b/VMs/buildDockerImage.sh index 51badfa6ff..5520645881 100755 --- a/VMs/buildDockerImage.sh +++ b/VMs/buildDockerImage.sh @@ -1,3 +1,13 @@ +# Pull in latest version of ubuntu +docker pull ubuntu:latest +# Remove any ubuntu: image if it was left behind by a new version of ubunto:latest being pulled +i=$(docker images | grep "ubuntu" | grep "https://www.owasp.org/index.php/Benchmark. +* +* The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms +* of the GNU General Public License as published by the Free Software Foundation, version 2. +* +* The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details +* +* @author Dave Wichers +* @created 2019 +*/ + +package org.owasp.benchmark.score.parsers; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.owasp.benchmark.score.BenchmarkScore; + +public class KiuwanReader extends Reader { + + public TestResults parse( File f ) throws Exception { + String content = new String(Files.readAllBytes(Paths.get(f.getPath()))); + + /* + * This parser was written against the .threadfix schema as defined here: + * https://denimgroup.atlassian.net/wiki/spaces/TDOC/pages/496009270/ThreadFix+File+Format + * + * To make any JSON file more readable: python -m json.tool file.json > prettyjson.txt + */ + JSONObject obj = new JSONObject(content); +// String resultsFormatVersion = obj.getString( "version" ); // Note: no threadfix version info included in format. + + JSONArray findings = obj.getJSONArray("findings"); + JSONObject metadata = obj.getJSONObject("metadata"); + + String source = obj.getString("source"); + + TestResults tr = new TestResults(source, true, TestResults.ToolType.SAST); + + // Scan time is included in the threadfix schema: "metadata/Kiuwan-AnalysisDuration" + if (null != metadata) { + String analysisDuration = metadata.getString("Kiuwan-AnalysisDuration"); + if (null != analysisDuration) { + tr.setTime(analysisDuration); + } + } + + // Set the version of Kiuwan used to do the scan: "metadata/Kiuwan-EngineVersion" + if (null != metadata) { + String engineVersion = metadata.getString("Kiuwan-EngineVersion"); + if (null != engineVersion) { + tr.setToolVersion(engineVersion); + } + } + + //System.out.println("Found: " + findings.length() + " findings."); + for (int i = 0; i < findings.length(); i++) + { + JSONObject finding = findings.getJSONObject(i); + + TestCaseResult tcr = parseKiuwanFinding( finding ); + if ( tcr != null ) { + tr.put( tcr ); + } + } + + return tr; + } + + private TestCaseResult parseKiuwanFinding(JSONObject finding) { + try { + TestCaseResult tcr = new TestCaseResult(); + JSONObject staticDetails = finding.getJSONObject("staticDetails"); + JSONArray dataFlow = staticDetails.getJSONArray("dataFlow"); + int propagationPathLength = dataFlow.length()-1; + String filename = dataFlow.getJSONObject(propagationPathLength).getString("file"); + filename = filename.substring( filename.lastIndexOf( '/' ) ); + if ( filename.contains( BenchmarkScore.BENCHMARKTESTNAME ) ) { + String testNumber = filename.substring( BenchmarkScore.BENCHMARKTESTNAME.length() + 1, filename.length() - 5 ); + tcr.setNumber( Integer.parseInt( testNumber ) ); + + int cwe = -1; + try { + JSONArray mappings = finding.getJSONArray("mappings"); + for (int i = 0; i < mappings.length(); i++) { + String val = mappings.getJSONObject(i).getString("mappingType"); + if (val.equalsIgnoreCase("CWE")) { + // fixCWE maps the supplied CWE to the one we use, if necessary + cwe = fixCWE( mappings.getJSONObject(i).getString("value") ); + break; + } + } + + } catch (Exception e ) { + e.printStackTrace(); + } + + if (cwe != -1) { + //System.out.println("Found finding in: " + testNumber + " of cwe type: " + cwe); + tcr.setCWE( cwe ); + tcr.setCategory( finding.getString("summary") ); + tcr.setEvidence( finding.getString("scannerDetail") ); + } //else System.out.println("ERROR: Finding in: " + testNumber + " included no CWE number."); + + } + return tcr; + } catch (Exception e ) { + e.printStackTrace(); + } + return null; + } + + + private int fixCWE( String cweNumber ) { + int cwe = Integer.parseInt( cweNumber ); + if ( cwe == 564 ) cwe = 89; // SQLi + if ( cwe == 77 ) cwe = 78; // Command Injection + return cwe; + } + +} diff --git a/src/main/java/org/owasp/benchmark/score/parsers/LGTMReader.java b/src/main/java/org/owasp/benchmark/score/parsers/LGTMReader.java new file mode 100644 index 0000000000..ad0c765964 --- /dev/null +++ b/src/main/java/org/owasp/benchmark/score/parsers/LGTMReader.java @@ -0,0 +1,159 @@ +/** +* OWASP Benchmark Project +* +* This file is part of the Open Web Application Security Project (OWASP) +* Benchmark Project For details, please see +* https://www.owasp.org/index.php/Benchmark. +* +* The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms +* of the GNU General Public License as published by the Free Software Foundation, version 2. +* +* The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details +* +* @author Dave Wichers +* @created 2019 +*/ + +package org.owasp.benchmark.score.parsers; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.owasp.benchmark.score.BenchmarkScore; + +public class LGTMReader extends Reader { + + public TestResults parse( File f ) throws Exception { + String content = new String(Files.readAllBytes(Paths.get(f.getPath()))); + + /* + * This parser was written against version 2.1.0 of the sarif-schema + * NOTE: To help understand contents of JSON file, use: http://jsonviewer.stack.hu to view it. + */ + JSONObject obj = new JSONObject(content); +// String resultsFormatVersion = obj.getString( "version" ); // Might be needed in future if format changes + + JSONArray runs = obj.getJSONArray("runs"); + + TestResults tr = new TestResults( "LGTM", true, TestResults.ToolType.SAST); + // Scan time is not included in the sarif-schema. But scan time is provided on their web site next to results + tr.setTime(f); // This grabs the scan time out of the filename, if provided + // e.g., Benchmark_1.2_LGTM-660.sarif, means the scan took 660 seconds. + + for (int i = 0; i < runs.length(); i++) + { + // There are 1 or more runs in each results file, one per language found (Java, JavaScript, etc.) + JSONObject run = runs.getJSONObject(i); + JSONObject properties = run.getJSONObject("properties"); + String sourceLang = properties.getString("semmle.sourceLanguage"); + + // Only consider the Java results + if ("java".equalsIgnoreCase(sourceLang)) { + + // First, set the version of LGTM used to do the scan + JSONObject driver = run.getJSONObject("tool").getJSONObject("driver"); + tr.setToolVersion(driver.getString("version")); + + // Then, identify all the rules that report results and which CWEs they map to + JSONArray rules = driver.getJSONArray("rules"); + System.out.println("Found: " + rules.length() + " rules."); + HashMap rulesUsed = parseLGTMRules(rules); + //System.out.println("Parsed: " + rulesUsed.size() + " rules."); + + // Finally, parse out all the results + JSONArray results = run.getJSONArray("results"); + //System.out.println("Found: " + results.length() + " results."); + + for (int j = 0; j < results.length(); j++) + { + TestCaseResult tcr = parseLGTMFinding( results.getJSONObject(j), rulesUsed ); //, version ); + if ( tcr != null ) { + tr.put( tcr ); + } + } + } + } + + return tr; + } + +private static final String LGTMCWEPREFIX = "external/cwe/cwe-"; +private static final int LGTMCWEPREFIXLENGTH = LGTMCWEPREFIX.length(); + private HashMap parseLGTMRules( JSONArray rulesJSON ) { + + HashMap rulesUsed = new HashMap(); + + for (int j = 0; j < rulesJSON.length(); j++) + { + JSONObject ruleJSON = rulesJSON.getJSONObject(j); + + try { + String ruleName = ruleJSON.getString("name"); + JSONArray tags = ruleJSON.getJSONObject("properties").getJSONArray("tags"); + for (int i = 0; i < tags.length(); i++) { + String val = tags.getString(i); + if (val.startsWith(LGTMCWEPREFIX)) { +// System.out.println("Rule found for CWE: " + Integer.parseInt(val.substring(LGTMCWEPREFIXLENGTH))); +// int cwe = fixCWE( cweNumber ); + rulesUsed.put(ruleName, Integer.parseInt(val.substring(LGTMCWEPREFIXLENGTH))); + break; // Break out of for loop because we only want to use the first CWE it is mapped to + // currently. If they add rules where the first CWE is not the preferred one, then we need + // to implement fixCWE() and invoke it (commented out example below) + } + } + + } catch (Exception e ) { + e.printStackTrace(); + } + } + return rulesUsed; + } + + private TestCaseResult parseLGTMFinding(JSONObject finding, HashMap rulesUsed) { + try { + TestCaseResult tcr = new TestCaseResult(); + String filename = null; + JSONArray locations = finding.getJSONArray("locations"); + filename = locations.getJSONObject(0).getJSONObject("physicalLocation") + .getJSONObject("artifactLocation").getString("uri"); + filename = filename.substring( filename.lastIndexOf( '/' ) ); + if ( filename.contains( BenchmarkScore.BENCHMARKTESTNAME ) ) { + String testNumber = filename.substring( BenchmarkScore.BENCHMARKTESTNAME.length() + 1, filename.length() - 5 ); + tcr.setNumber( Integer.parseInt( testNumber ) ); + String ruleId = finding.getString("ruleId"); + Integer cweForRule = rulesUsed.get( ruleId ); +// System.out.println("Found finding in: " + testNumber + " of type: " + ruleId + " CWE: " + cweForRule); + if ( cweForRule == null ) { + return null; + } + if (locations.length() > 1) { + System.out.println("Unexpectedly found more than one location for finding against rule: " + ruleId); + } + int cwe = cweForRule.intValue(); + tcr.setCWE( cwe ); +// tcr.setCategory( props.getString( "subcategoryShortDescription" ) ); // Couldn't find any Category info in results file + tcr.setEvidence( finding.getJSONObject("message").getString("text") ); + } + return tcr; + } catch (Exception e ) { + e.printStackTrace(); + } + return null; + } + +/* + private int fixCWE( String cweNumber ) { + int cwe = Integer.parseInt( cweNumber ); + if ( cwe == 94 ) cwe = 643; + if ( cwe == 36 ) cwe = 22; + if ( cwe == 23 ) cwe = 22; + return cwe; + } +*/ +} diff --git a/src/main/java/org/owasp/benchmark/score/parsers/VeracodeReader.java b/src/main/java/org/owasp/benchmark/score/parsers/VeracodeReader.java index ebb340c74c..dba9864cce 100644 --- a/src/main/java/org/owasp/benchmark/score/parsers/VeracodeReader.java +++ b/src/main/java/org/owasp/benchmark/score/parsers/VeracodeReader.java @@ -134,6 +134,7 @@ private int translate(int cwe) { if ( cwe == 73 ) return 22; if ( cwe == 80 ) return 79; if ( cwe == 331 ) return 330; + if ( cwe == 91 ) return 643; return cwe; } } diff --git a/src/main/java/org/owasp/benchmark/score/parsers/XanitizerReader.java b/src/main/java/org/owasp/benchmark/score/parsers/XanitizerReader.java index 2e02cb3783..d1104c15fa 100644 --- a/src/main/java/org/owasp/benchmark/score/parsers/XanitizerReader.java +++ b/src/main/java/org/owasp/benchmark/score/parsers/XanitizerReader.java @@ -47,6 +47,7 @@ public TestResults parse(final File f) throws Exception { private final StringBuilder m_CollectedCharacters = new StringBuilder(); private String m_ProblemTypeId; + private int m_CWE = -1; private String m_Class; private String m_Classification; @@ -59,8 +60,12 @@ public void startElement(final String uri, final String localName, final String switch (qName) { case "XanitizerFindingsList": - String version = attributes.getValue("xanitizerVersion"); - version = version.replace('/', '-'); + String version = attributes.getValue("xanitizerVersionShort"); + // for backward compatibility - use full version + if (version == null) { + version = attributes.getValue("xanitizerVersion"); + version = version.replace('/', '-'); + } tr.setToolVersion(version); break; @@ -85,6 +90,15 @@ public void endElement(final String uri, final String localName, final String qN m_Classification = m_CollectedCharacters.toString(); break; + case "cweNumber": + // remove leading "CWE-" and thousands delimiter + try { + m_CWE = Integer.parseInt(m_CollectedCharacters.toString().substring(4).replace(".", "").replace(",", "")); + } catch (NumberFormatException e) { + m_CWE = -1; + } + break; + case "finding": // Finishing a finding. @@ -108,12 +122,18 @@ public void endElement(final String uri, final String localName, final String qN testCaseNumber = -1; } + // for backward compatibility + // for reports without CWE numbers - map problem type to CWE number + if (m_CWE < 0) { + m_CWE = figureCWE(m_ProblemTypeId); + } + if (testCaseNumber >= 0) { final TestCaseResult tcr = new TestCaseResult(); tcr.setNumber(testCaseNumber); tcr.setCategory(m_ProblemTypeId); - tcr.setCWE(figureCWE(m_ProblemTypeId)); + tcr.setCWE(m_CWE); tr.put(tcr); } @@ -122,6 +142,7 @@ public void endElement(final String uri, final String localName, final String qN } m_ProblemTypeId = null; + m_CWE = -1; m_Class = null; m_Classification = null; break;