From 83262afd13a47267236b401501eca9647cc80593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B8ytil?= Date: Mon, 1 May 2017 18:11:53 +0200 Subject: [PATCH 01/30] Added support to generate report in JSON format --- .../org/owasp/dependencycheck/CliParser.java | 4 +- dependency-check-core/pom.xml | 12 +- .../dependencycheck/reporting/EscapeTool.java | 12 ++ .../reporting/ReportGenerator.java | 50 +++-- .../main/resources/templates/JsonReport.vsl | 198 ++++++++++++++++++ pom.xml | 5 + 6 files changed, 260 insertions(+), 21 deletions(-) create mode 100644 dependency-check-core/src/main/resources/templates/JsonReport.vsl diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java index 5be1c2d92..6413017c1 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java @@ -120,7 +120,7 @@ public final class CliParser { Format.valueOf(format); } catch (IllegalArgumentException ex) { final String msg = String.format("An invalid 'format' of '%s' was specified. " - + "Supported output formats are XML, HTML, VULN, or ALL", format); + + "Supported output formats are XML, JSON, HTML, VULN, or ALL", format); throw new ParseException(msg); } } @@ -262,7 +262,7 @@ public final class CliParser { .build(); final Option outputFormat = Option.builder(ARGUMENT.OUTPUT_FORMAT_SHORT).argName("format").hasArg().longOpt(ARGUMENT.OUTPUT_FORMAT) - .desc("The output format to write to (XML, HTML, VULN, ALL). The default is HTML.") + .desc("The output format to write to (XML, JSON, HTML, VULN, ALL). The default is HTML.") .build(); final Option verboseLog = Option.builder(ARGUMENT.VERBOSE_LOG_SHORT).argName("file").hasArg().longOpt(ARGUMENT.VERBOSE_LOG) diff --git a/dependency-check-core/pom.xml b/dependency-check-core/pom.xml index dc7590c6c..d7b44ac21 100644 --- a/dependency-check-core/pom.xml +++ b/dependency-check-core/pom.xml @@ -340,6 +340,10 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. com.sun.mail mailapi + + com.google.code.gson + gson + org.apache.maven.scm @@ -470,6 +474,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. test true + @@ -672,13 +677,6 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. test true - - com.google.code.gson - gson - 2.3.1 - test - true - com.google.gerrit gerrit-extension-api diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java index 4a7a2b491..9c1d905ce 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java @@ -80,4 +80,16 @@ public class EscapeTool { } return StringEscapeUtils.escapeXml11(text); } + + /** + * JSON Encodes the provded text + * @param text the text to encode + * @return the JSON encoded text + */ + public String json(String text) { + if (text == null || text.isEmpty()) { + return text; + } + return StringEscapeUtils.escapeJson(text); + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java index a97aef3fa..e8ebcd779 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java @@ -17,17 +17,14 @@ */ package org.owasp.dependencycheck.reporting; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; +import java.io.*; +import java.nio.file.*; import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; @@ -78,7 +75,11 @@ public class ReportGenerator { /** * Generate HTML Vulnerability report. */ - VULN + VULN, + /** + * Generate JSON report. + */ + JSON } /** * The Velocity Engine. @@ -164,6 +165,9 @@ public class ReportGenerator { if (format == Format.VULN || format == Format.ALL) { generateReport("VulnerabilityReport", outputStream); } + if (format == Format.JSON || format == Format.ALL) { + generateReport("JsonReport", outputStream); + } } /** @@ -178,12 +182,27 @@ public class ReportGenerator { if (format == Format.XML || format == Format.ALL) { generateReport("XmlReport", outputDir + File.separator + "dependency-check-report.xml"); } + if (format == Format.JSON || format == Format.ALL) { + generateReport("JsonReport", outputDir + File.separator + "dependency-check-report.json"); + try { + Path resultPath = Paths.get(outputDir + File.separator + "dependency-check-report.json"); + String content = new String(Files.readAllBytes(resultPath)); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + JsonParser jp = new JsonParser(); + JsonElement je = jp.parse(content); + String prettyJson = gson.toJson(je); + Files.write(Paths.get(outputDir + File.separator + "dependency-check-report.json"), prettyJson.getBytes(), StandardOpenOption.WRITE); + } catch (IOException e) { + LOGGER.error("Unable to generate pretty report, got error: ", e.getMessage()); + } + } if (format == Format.HTML || format == Format.ALL) { generateReport("HtmlReport", outputDir + File.separator + "dependency-check-report.html"); } if (format == Format.VULN || format == Format.ALL) { generateReport("VulnerabilityReport", outputDir + File.separator + "dependency-check-vulnerability.html"); } + } /** @@ -198,7 +217,7 @@ public class ReportGenerator { public void generateReports(String outputDir, String outputFormat) throws ReportException { final String format = outputFormat.toUpperCase(); final String pathToCheck = outputDir.toLowerCase(); - if (format.matches("^(XML|HTML|VULN|ALL)$")) { + if (format.matches("^(XML|HTML|VULN|JSON|ALL)$")) { if ("XML".equalsIgnoreCase(format)) { if (pathToCheck.endsWith(".xml")) { generateReport("XmlReport", outputDir); @@ -220,6 +239,13 @@ public class ReportGenerator { generateReports(outputDir, Format.VULN); } } + if ("JSON".equalsIgnoreCase(format)) { + if (pathToCheck.endsWith(".json")) { + generateReport("JsonReport", outputDir); + } else { + generateReports(outputDir, Format.JSON); + } + } if ("ALL".equalsIgnoreCase(format)) { generateReports(outputDir, Format.ALL); } diff --git a/dependency-check-core/src/main/resources/templates/JsonReport.vsl b/dependency-check-core/src/main/resources/templates/JsonReport.vsl new file mode 100644 index 000000000..d83feba51 --- /dev/null +++ b/dependency-check-core/src/main/resources/templates/JsonReport.vsl @@ -0,0 +1,198 @@ +{ + "analysis": { + "scanInfo": { + "engineVersion": "$version", + "dataSource": [ + #foreach($prop in $properties.getMetaData().entrySet()) + #if($foreach.count > 1),#end{ + "name": "$enc.json($prop.key)", + "timestamp": "$enc.json($prop.value)" + } + #end + ] + }, + "projectInfo": { + "name": "$enc.json($applicationName)", + "reportDate": "$scanDateXML", + "credits": "This report contains data retrieved from the National Vulnerability Database: http://nvd.nist.gov" + }, + "dependencies": [ + #foreach($dependency in $dependencies)#if($foreach.count > 1),#end{ + "fileName": "$enc.json($dependency.DisplayFileName)", + "filePath": "$enc.json($dependency.FilePath)", + "md5": "$enc.json($dependency.Md5sum)", + "sha1": "$enc.json($dependency.Sha1sum)" + #if($dependency.description),"description": "$enc.json($dependency.description)"#end + #if($dependency.license),"license": "$enc.json($dependency.license)"#end + #if ($dependency.getRelatedDependencies().size()>0) + ,"relatedDependencies": [ + #foreach($related in $dependency.getRelatedDependencies()) #if($foreach.count > 1),#end { + "filePath": "$enc.json($related.FilePath)", + "sha1": "$enc.json($related.Sha1sum)", + "md5": "$enc.json($related.Md5sum)"#if($related.getIdentifiers()),#end + "identifiers": [ + #foreach($id in $related.getIdentifiers()) + #if ($id.type=="maven") + { + "type": "$enc.json($id.type)", + "name": "$id.value" + #if( $id.url ),"url": "$enc.json($id.url)"#end + #if ($id.notes),"notes": "$enc.json($id.notes)"#end + } + #end + #end + ] + } + #end + ] + #end + + ,"evidenceCollected": { + + "vendorEvidence": [ + #foreach($evidence in $dependency.getVendorEvidence()) + #if($foreach.count > 1),#end{ + "type": "vendor", + "confidence": "$enc.json($evidence.getConfidence().toString())", + "source": "$enc.json($evidence.getSource())", + "name": "$enc.json($evidence.getName())", + "value": "$enc.json($evidence.getValue().trim())" + } + #end + ], + "productEvidence": [ + #foreach($evidence in $dependency.getProductEvidence()) + #if($foreach.count > 1),#end{ + "type": "product", + "confidence": "$enc.json($evidence.getConfidence().toString())", + "source": "$enc.json($evidence.getSource())", + "name": "$enc.json($evidence.getName())", + "value": "$enc.json($evidence.getValue().trim())" + } + #end + ], + "versionEvidence": [ + #foreach($evidence in $dependency.getVersionEvidence()) + #if($foreach.count > 1),#end{ + "type": "version", + "confidence": "$enc.json($evidence.getConfidence().toString())", + "source": "$enc.json($evidence.getSource())", + "name": "$enc.json($evidence.getName())", + "value": "$enc.json($evidence.getValue().trim())" + } + #end + ] + + }, + "identifiers": [ + #foreach($id in $dependency.getIdentifiers())#if($foreach.count > 1),#end{ + "name": "$id.value", + "type": "$enc.json($id.type)", + #if($id.confidence)"confidence": "$id.confidence",#end + #if($id.url)"url": "$enc.json($id.url)",#end + #if($id.description )"description": "$enc.json($id.description)",#end + #if ($id.notes)"notes": "$enc.json($id.notes)",#end + "suppressedIdentifiers": [ + #foreach($id in $dependency.getSuppressedIdentifiers()) + #if($foreach.count > 1),#end{ + "type": "$enc.json($id.type)", + #if($id.confidence)"confidence": "$id.confidence",#end + "name": "$id.value", + #if($id.url)"url": "$enc.json($id.url),"#end + #if($id.description)"description": "$enc.json($id.description)",#end + #if ($id.notes)"notes": "$enc.json($id.notes)"#end + } + #end + ] + } + #end + ] + + #if($dependency.getVulnerabilities().size()>0 || $dependency.getSuppressedVulnerabilities().size()>0) + ,"vulnerabilities": [ + #foreach($vuln in $dependency.getVulnerabilities()) + #if($foreach.count > 1),#end { + "name": "$enc.json($vuln.name)", + "cvssScore": "$vuln.cvssScore", + "cvssAccessVector": "$enc.json($vuln.cvssAccessVector)", + "cvssAccessComplexity": "$enc.json($vuln.cvssAccessComplexity)", + "cvssAuthenticationr": "$enc.json($vuln.cvssAuthentication)", + "cvssConfidentialImpact": "$enc.json($vuln.cvssConfidentialityImpact)", + "cvssIntegrityImpact": "$enc.json($vuln.cvssIntegrityImpact)", + "cvssAvailabilityImpact": "$enc.json($vuln.cvssAvailabilityImpact)", + #if ($vuln.cvssScore<4.0) + "severity": "Low", + #elseif ($vuln.cvssScore>=7.0) + "severity": "High", + #else + "severity": "Medium", + #end + #if($vuln.cwe)"cwe": "$enc.json($vuln.cwe)",#end + "description": "$enc.json($vuln.description)", + #if ($vuln.notes)"notes": "$enc.json($vuln.notes)"#end + "references": [ + #foreach($ref in $vuln.getReferences()) + #if($foreach.count > 1),#end { + "source": "$enc.json($ref.source)", + "url": "$enc.json($ref.url)", + "name": "$enc.json($ref.name)" + } + #end + ], + "vulnerableSoftware": [ + #foreach($vs in $vuln.getVulnerableSoftware()) + #if($foreach.count > 1),#end { + #if($vs.hasPreviousVersion()) "allPreviousVersion": "true",#end + "software": "$enc.json($vs.name)" + } + #end + ] + }#end + ] + #end + + #if($dependency.getSuppressedVulnerabilities().size()>0 || $dependency.getSuppressedVulnerabilities().size()>0) + ,"suppressedVulnerabilities": [ + #foreach($vuln in $dependency.getSuppressedVulnerabilities())#if($foreach.count > 1),#end { + "name": "$enc.json($vuln.name)", + "cvssScore": "$vuln.cvssScore", + "cvssAccessVector": "$enc.json"($vuln.cvssAccessVector), + "cvssAccessComplexity": "$enc.json"($vuln.cvssAccessComplexity), + "cvssAuthenticationr": "$enc.json"($vuln.cvssAuthentication), + "cvssConfidentialImpact": "$enc.json"($vuln.cvssConfidentialityImpact), + "cvssIntegrityImpact": "$enc.json"($vuln.cvssIntegrityImpact), + "cvssAvailabilityImpact": "$enc.json"($vuln.cvssAvailabilityImpact), + #if ($vuln.cvssScore<4.0) "severity": "Low", + #elseif ($vuln.cvssScore>=7.0) "severity": "High", + #else "severity": "Medium", + #end + #if ($vuln.cwe)"cwe": "$enc.json($vuln.cwe)",#end + "description": "$enc.json($vuln.description)" + #if ($vuln.notes),"notes": "$enc.json($vuln.notes)"#end + ,"references" [ + #foreach($ref in $vuln.getReferences()) + #if($foreach.count > 1),#end { + "source": "$enc.json($ref.source)", + "url": "$enc.json($ref.url)", + "name": "$enc.json($ref.name)" + } + #end + ], + "vulnerableSoftware": [ + #foreach($vs in $vuln.getVulnerableSoftware()) + #if($foreach.count > 1),#end { + #if($vs.hasPreviousVersion()) "allPreviousVersion": "true"#end, + "name": "$enc.json($vs.name)" + } + #end + ] + } + #end + } + #end + + } + #end + ] + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8698773ce..36ba0daf3 100644 --- a/pom.xml +++ b/pom.xml @@ -597,6 +597,11 @@ Copyright (c) 2012 - Jeremy Long annotations 3.0.1u2 + + com.google.code.gson + gson + 2.3.1 + com.h2database h2 From 0a02f43b8c02ef3126332168ca3a8546d53c38b0 Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 09:57:39 +0200 Subject: [PATCH 02/30] Refactor Dockerfile for readability. --- Dockerfile | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index c311a3b6b..cb6839911 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,20 @@ FROM java:8 MAINTAINER Timo Pagel -RUN wget -O /tmp/current.txt http://jeremylong.github.io/DependencyCheck/current.txt && current=$(cat /tmp/current.txt) && wget https://dl.bintray.com/jeremy-long/owasp/dependency-check-$current-release.zip && unzip dependency-check-$current-release.zip && mv dependency-check /usr/share/ +RUN wget -O /tmp/current.txt http://jeremylong.github.io/DependencyCheck/current.txt && \ + current=$(cat /tmp/current.txt) && \ + wget https://dl.bintray.com/jeremy-long/owasp/dependency-check-$current-release.zip && \ + unzip dependency-check-$current-release.zip && \ + mv dependency-check /usr/share/ + +RUN useradd -ms /bin/bash dockeruser && \ + chown -R dockeruser:dockeruser /usr/share/dependency-check && \ + mkdir /report && \ + chown -R dockeruser:dockeruser /report -RUN useradd -ms /bin/bash dockeruser && chown -R dockeruser:dockeruser /usr/share/dependency-check && mkdir /report && chown -R dockeruser:dockeruser /report USER dockeruser -VOLUME "/src /usr/share/dependency-check/data /report" +VOLUME ["/src" "/usr/share/dependency-check/data" "/report"] WORKDIR /report From 7c1c99f5f924a5c9562ef30dba8a56203b42e49b Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 10:00:37 +0200 Subject: [PATCH 03/30] Use script without arguments as entry point to allow running any command on the resulting container without having to override entry point. --- Dockerfile | 3 ++- README.md | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index cb6839911..8daae2e15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,4 +19,5 @@ VOLUME ["/src" "/usr/share/dependency-check/data" "/report"] WORKDIR /report -ENTRYPOINT ["/usr/share/dependency-check/bin/dependency-check.sh", "--scan", "/src"] +CMD ["--help"] +ENTRYPOINT ["/usr/share/dependency-check/bin/dependency-check.sh"] diff --git a/README.md b/README.md index 87a68fc54..e07da7ec8 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ if [ ! -d $DATA_DIRECTORY ]; then echo "Initially creating persistent directories" mkdir -p $DATA_DIRECTORY chmod -R 777 $DATA_DIRECTORY - + mkdir -p $REPORT_DIRECTORY chmod -R 777 $REPORT_DIRECTORY fi @@ -123,7 +123,8 @@ docker run --rm \ --volume $DATA_DIRECTORY:/usr/share/dependency-check/data \ --volume $REPORT_DIRECTORY:/report \ --name dependency-check \ - dc \ + owasp/dependency-check \ + --scan /src \ --suppression "/src/security/dependency-check-suppression.xml"\ --format "ALL" \ --project "My OWASP Dependency Check Project" \ From 13d781d2b1ec117d37c2a86b1012b5fa97d45180 Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 10:01:52 +0200 Subject: [PATCH 04/30] Re-order arguments in likeliness of being commented out. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e07da7ec8..41f19304f 100644 --- a/README.md +++ b/README.md @@ -125,9 +125,9 @@ docker run --rm \ --name dependency-check \ owasp/dependency-check \ --scan /src \ - --suppression "/src/security/dependency-check-suppression.xml"\ --format "ALL" \ --project "My OWASP Dependency Check Project" \ + --suppression "/src/security/dependency-check-suppression.xml" ``` From 8edf65186f7d3ee88d2ec1f2cdc2e9a00abbf24d Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 10:03:24 +0200 Subject: [PATCH 05/30] Clean up after download to reduce image size. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 8daae2e15..01c34b042 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,7 @@ RUN wget -O /tmp/current.txt http://jeremylong.github.io/DependencyCheck/current current=$(cat /tmp/current.txt) && \ wget https://dl.bintray.com/jeremy-long/owasp/dependency-check-$current-release.zip && \ unzip dependency-check-$current-release.zip && \ + rm dependency-check-$current-release.zip && \ mv dependency-check /usr/share/ RUN useradd -ms /bin/bash dockeruser && \ From 57b1895b5ebb7b7710656784799a823e5a6d4bb2 Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 10:19:27 +0200 Subject: [PATCH 06/30] Refactor user name into variable. --- Dockerfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 01c34b042..becfe66eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,8 @@ FROM java:8 MAINTAINER Timo Pagel +ENV user=dockeruser + RUN wget -O /tmp/current.txt http://jeremylong.github.io/DependencyCheck/current.txt && \ current=$(cat /tmp/current.txt) && \ wget https://dl.bintray.com/jeremy-long/owasp/dependency-check-$current-release.zip && \ @@ -9,12 +11,12 @@ RUN wget -O /tmp/current.txt http://jeremylong.github.io/DependencyCheck/current rm dependency-check-$current-release.zip && \ mv dependency-check /usr/share/ -RUN useradd -ms /bin/bash dockeruser && \ - chown -R dockeruser:dockeruser /usr/share/dependency-check && \ +RUN useradd -ms /bin/bash ${user} && \ + chown -R ${user}:${user} /usr/share/dependency-check && \ mkdir /report && \ - chown -R dockeruser:dockeruser /report + chown -R ${user}:${user} /report -USER dockeruser +USER ${user} VOLUME ["/src" "/usr/share/dependency-check/data" "/report"] From bf7b8ccce8f24400b206b7e3292e5298037320ba Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 10:20:02 +0200 Subject: [PATCH 07/30] Use specific user name to easily identify who is doing things here. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index becfe66eb..761f6b8bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM java:8 MAINTAINER Timo Pagel -ENV user=dockeruser +ENV user=dependencycheck RUN wget -O /tmp/current.txt http://jeremylong.github.io/DependencyCheck/current.txt && \ current=$(cat /tmp/current.txt) && \ From ff6b3dbd4f6bf87c07c176a3f1c2238c71104c67 Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 10:30:29 +0200 Subject: [PATCH 08/30] Refactor to make URLs easier to change and reduce repetition. --- Dockerfile | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 761f6b8bf..ecd4b02ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,12 +3,15 @@ FROM java:8 MAINTAINER Timo Pagel ENV user=dependencycheck +ENV version_url=https://jeremylong.github.io/DependencyCheck/current.txt +ENV download_url=https://dl.bintray.com/jeremy-long/owasp -RUN wget -O /tmp/current.txt http://jeremylong.github.io/DependencyCheck/current.txt && \ - current=$(cat /tmp/current.txt) && \ - wget https://dl.bintray.com/jeremy-long/owasp/dependency-check-$current-release.zip && \ - unzip dependency-check-$current-release.zip && \ - rm dependency-check-$current-release.zip && \ +RUN wget -O /tmp/current.txt ${version_url} && \ + version=$(cat /tmp/current.txt) && \ + file="dependency-check-${version}-release.zip" && \ + wget "$download_url/$file" && \ + unzip ${file} && \ + rm ${file} && \ mv dependency-check /usr/share/ RUN useradd -ms /bin/bash ${user} && \ From 03f84fa77efbf0f261eed7bfa18cc5a370ff347a Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 10:36:25 +0200 Subject: [PATCH 09/30] Improve docker usage documentation. --- README.md | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 41f19304f..c8785bec5 100644 --- a/README.md +++ b/README.md @@ -101,33 +101,38 @@ Then load the resulting 'DependencyCheck-Report.html' into your favorite browser ### Docker -In the following example it is assumed that the source to be checked is in the actual directory. A persistent data directory and a persistent report directory is used so that the container can be destroyed after running it to make sure that you use the newest version, always. +In the following example it is assumed that the source to be checked is in the current working directory. Persistent data and report directories are used, allowing you to destroy the container after running. + ``` -# After the first run, feel free to change the owner of the directories to the owner of the created files and the permissions to 744 -DATA_DIRECTORY=$HOME/OWASP-Dependency-Check/data -REPORT_DIRECTORY=/$HOME/OWASP-Dependency-Check/reports +#!/bin/sh -if [ ! -d $DATA_DIRECTORY ]; then - echo "Initially creating persistent directories" - mkdir -p $DATA_DIRECTORY - chmod -R 777 $DATA_DIRECTORY +OWASPDC_DIRECTORY=$HOME/OWASP-Dependency-Check +DATA_DIRECTORY="$OWASPDC_DIRECTORY/data" +REPORT_DIRECTORY="$OWASPDC_DIRECTORY/reports" - mkdir -p $REPORT_DIRECTORY - chmod -R 777 $REPORT_DIRECTORY +if [ ! -d "$DATA_DIRECTORY" ]; then + echo "Initially creating persistent directories" + mkdir -p "$DATA_DIRECTORY" + chmod -R 777 "$DATA_DIRECTORY" + + mkdir -p "$REPORT_DIRECTORY" + chmod -R 777 "$REPORT_DIRECTORY" fi -docker pull owasp/dependency-check # Make sure it is the actual version +# Make sure we are using the latest version +docker pull owasp/dependency-check docker run --rm \ - --volume $(pwd):/src \ - --volume $DATA_DIRECTORY:/usr/share/dependency-check/data \ - --volume $REPORT_DIRECTORY:/report \ - --name dependency-check \ - owasp/dependency-check \ - --scan /src \ - --format "ALL" \ - --project "My OWASP Dependency Check Project" \ - --suppression "/src/security/dependency-check-suppression.xml" + --volume $(pwd):/src \ + --volume "$DATA_DIRECTORY":/usr/share/dependency-check/data \ + --volume "$REPORT_DIRECTORY":/report \ + owasp/dependency-check \ + --scan /src \ + --format "ALL" \ + --project "My OWASP Dependency Check Project" + # Use suppression like this: (/src == $pwd) + # --suppression "/src/security/dependency-check-suppression.xml" + ``` From ae65ebe687fea8be307d0eb1404269baf8e76b31 Mon Sep 17 00:00:00 2001 From: janpapenbrock Date: Sat, 6 May 2017 11:11:13 +0200 Subject: [PATCH 10/30] Fix argument name for CLI. --- .../src/site/markdown/arguments.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/dependency-check-cli/src/site/markdown/arguments.md b/dependency-check-cli/src/site/markdown/arguments.md index 48ca5b765..f7688b6b0 100644 --- a/dependency-check-cli/src/site/markdown/arguments.md +++ b/dependency-check-cli/src/site/markdown/arguments.md @@ -4,22 +4,22 @@ Command Line Arguments The following table lists the command line arguments: Short | Argument Name   | Parameter | Description | Requirement --------|-----------------------|-----------------|-------------|------------ - | \-\-project | \ | The name of the project being scanned. | Required - \-s | \-\-scan | \ | The path to scan \- this option can be specified multiple times. It is also possible to specify Ant style paths (e.g. directory/**/*.jar). | Required - | \-\-exclude | \ | The path patterns to exclude from the scan \- this option can be specified multiple times. This accepts Ant style path patterns (e.g. **/exclude/**). | Optional - | \-\-symLink | \ | The depth that symbolic links will be followed; the default is 0 meaning symbolic links will not be followed. | Optional - \-o | \-\-out | \ | The folder to write reports to. This defaults to the current directory. If the format is not set to ALL one could specify a specific file name. | Optional - \-f | \-\-format | \ | The output format to write to (XML, HTML, VULN, ALL). The default is HTML. | Required - | \-\-failOnCvss | \ | If the score set between 0 and 10 the exit code from dependency-check will indicate if a vulnerability with a CVSS score equal to or higher was identified. | Optional - \-l | \-\-log | \ | The file path to write verbose logging information. | Optional - \-n | \-\-noupdate | | Disables the automatic updating of the CPE data. | Optional - | \-\-suppression | \ | The file path to the suppression XML file; used to suppress [false positives](../general/suppression.html). | Optional - \-h | \-\-help | | Print the help message. | Optional - | \-\-advancedHelp | | Print the advanced help message. | Optional - \-v | \-\-version | | Print the version information. | Optional - | \-\-cveValidForHours | \ | The number of hours to wait before checking for new updates from the NVD. The default is 4 hours. | Optional - | \-\-experimental | | Enable the [experimental analyzers](../analyzers/index.html). If not set the analyzers marked as experimental below will not be loaded or used. | Optional +-------|------------------------|-----------------|-------------|------------ + | \-\-project | \ | The name of the project being scanned. | Required + \-s | \-\-scan | \ | The path to scan \- this option can be specified multiple times. It is also possible to specify Ant style paths (e.g. directory/**/*.jar). | Required + | \-\-exclude | \ | The path patterns to exclude from the scan \- this option can be specified multiple times. This accepts Ant style path patterns (e.g. **/exclude/**). | Optional + | \-\-symLink | \ | The depth that symbolic links will be followed; the default is 0 meaning symbolic links will not be followed. | Optional + \-o | \-\-out | \ | The folder to write reports to. This defaults to the current directory. If the format is not set to ALL one could specify a specific file name. | Optional + \-f | \-\-format | \ | The output format to write to (XML, HTML, VULN, ALL). The default is HTML. | Required + | \-\-failOnCvss | \ | If the score set between 0 and 10 the exit code from dependency-check will indicate if a vulnerability with a CVSS score equal to or higher was identified. | Optional + \-l | \-\-log | \ | The file path to write verbose logging information. | Optional + \-n | \-\-noupdate | | Disables the automatic updating of the CPE data. | Optional + | \-\-suppression | \ | The file path to the suppression XML file; used to suppress [false positives](../general/suppression.html). | Optional + \-h | \-\-help | | Print the help message. | Optional + | \-\-advancedHelp | | Print the advanced help message. | Optional + \-v | \-\-version | | Print the version information. | Optional + | \-\-cveValidForHours | \ | The number of hours to wait before checking for new updates from the NVD. The default is 4 hours. | Optional + | \-\-enableExperimental | | Enable the [experimental analyzers](../analyzers/index.html). If not set the analyzers marked as experimental below will not be loaded or used. | Optional Advanced Options ================ From 4134fb3fefe46b34c6a3ca5f7e895b1de6c19a9e Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 6 May 2017 12:05:30 -0400 Subject: [PATCH 11/30] fixed sorting issue and resolved enhancement request #515 --- .../main/resources/templates/HtmlReport.vsl | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/dependency-check-core/src/main/resources/templates/HtmlReport.vsl b/dependency-check-core/src/main/resources/templates/HtmlReport.vsl index 8283a443a..b065e8a58 100644 --- a/dependency-check-core/src/main/resources/templates/HtmlReport.vsl +++ b/dependency-check-core/src/main/resources/templates/HtmlReport.vsl @@ -632,7 +632,7 @@ Getting Help: - $enc.html($dependency.DisplayFileName) + $enc.html($dependency.DisplayFileName) #set($mavenlink="") #set($cpeIdCount=0) #set($cpeIdConf="") @@ -673,9 +673,9 @@ Getting Help: #else - #if( $mavenlink.url ) + #if( $mavenlink.url ) ##yes, we are HTML Encoding the href. This is okay. We can't URL encode as we have to trust the analyzer here... - $enc.html($mavenlink.value) + $enc.html($mavenlink.value)  #elseif ($mavenlink.value) $enc.html($mavenlink.value) #end @@ -762,10 +762,10 @@ Getting Help: SHA1: $enc.html($related.Sha1sum)
  • MD5: $enc.html($related.Md5sum)
  • #foreach($id in $related.getIdentifiers()) - #if ($id.type=="maven") - #if( $id.url ) + #if( $id.url ) + #if ($id.type=="maven") ##yes, we are HTML Encoding the href. this is okay. We can't URL encode as we have to trust the analyzer here... -
  • $enc.html($id.type): $enc.html($id.value) +
  • $enc.html($id.type): $enc.html($id.value)  #else
  • $enc.html($id.type): $enc.html($id.value) #end @@ -800,8 +800,12 @@ Getting Help: $enc.html($id.type): $enc.html($id.value)  + #else
  • $enc.html($id.type): $enc.html($id.value) + #end #else
  • $enc.html($id.type): $enc.html($id.value) #end @@ -949,8 +953,12 @@ Getting Help: #foreach($id in $dependency.getSuppressedIdentifiers()) #if( $id.url ) + #if($id.type=="maven") ##yes, we are HTML Encoding the href. this is okay. We can't URL encode as we have to trust the analyzer here... +
  • $enc.html($id.type): $enc.html($id.value)   suppressed + #else
  • $enc.html($id.type): $enc.html($id.value)  suppressed + #end #else
  • $enc.html($id.type): $enc.html($id.value)  suppressed #end From c2f521f528c77eea342316f79e94e74fc3dce810 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 6 May 2017 15:12:27 -0400 Subject: [PATCH 12/30] updated to support changes from #682 --- .../src/main/resources/templates/JsonReport.vsl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dependency-check-core/src/main/resources/templates/JsonReport.vsl b/dependency-check-core/src/main/resources/templates/JsonReport.vsl index d83feba51..4fbf0b79a 100644 --- a/dependency-check-core/src/main/resources/templates/JsonReport.vsl +++ b/dependency-check-core/src/main/resources/templates/JsonReport.vsl @@ -1,4 +1,5 @@ { + "reportSchema": "1.0", "analysis": { "scanInfo": { "engineVersion": "$version", @@ -13,6 +14,9 @@ }, "projectInfo": { "name": "$enc.json($applicationName)", + #if($groupID)"groupID":"$enc.json($groupID)",#end + #if($artifactID)"artifactID":"$enc.json($artifactID)",#end + #if($version)"version":"$enc.json($version)",#end "reportDate": "$scanDateXML", "credits": "This report contains data retrieved from the National Vulnerability Database: http://nvd.nist.gov" }, @@ -46,9 +50,7 @@ #end ] #end - ,"evidenceCollected": { - "vendorEvidence": [ #foreach($evidence in $dependency.getVendorEvidence()) #if($foreach.count > 1),#end{ @@ -82,7 +84,6 @@ } #end ] - }, "identifiers": [ #foreach($id in $dependency.getIdentifiers())#if($foreach.count > 1),#end{ @@ -107,7 +108,6 @@ } #end ] - #if($dependency.getVulnerabilities().size()>0 || $dependency.getSuppressedVulnerabilities().size()>0) ,"vulnerabilities": [ #foreach($vuln in $dependency.getVulnerabilities()) @@ -195,4 +195,4 @@ #end ] } -} \ No newline at end of file +} From de6cd3621b8a5020017119fb6002ee92bb7355ec Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 6 May 2017 16:44:04 -0400 Subject: [PATCH 13/30] updated docker documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87a68fc54..452fb6980 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ if [ ! -d $DATA_DIRECTORY ]; then chmod -R 777 $REPORT_DIRECTORY fi -docker pull owasp/dependency-check # Make sure it is the actual version +docker pull owasp/dependency-check docker run --rm \ --volume $(pwd):/src \ From 7b7861206b194a0bd2b8205530691b376ee25840 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 6 May 2017 19:25:54 -0400 Subject: [PATCH 14/30] fixes for #700, #713, and #699 --- .../dependencycheck-base-suppression.xml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml index 481048c12..b4c75e316 100644 --- a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml +++ b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml @@ -561,4 +561,27 @@ ^org\.springframework\.boot:spring-boot-starter-data-jpa:.*$ CVE-2016-6652 + + + ^com\.splunk:splunk:.*$ + cpe:/a:splunk:splunk + + + + ^org\.openid4java:openid4java:.*$ + cpe:/a:openid:openid + cpe:/a:openid:openid4java + + + + ^org\.springframework\.cloud:spring-cloud-netflix-core:.*$ + cpe:/a:pivotal:spring_framework + cpe:/a:pivotal_software:spring_framework + From 0504f9c4cc3c41d39455d53968ec72d2b6652f6e Mon Sep 17 00:00:00 2001 From: Andrey Arapov Date: Sun, 7 May 2017 10:07:28 +0200 Subject: [PATCH 15/30] fix readme --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 010d54c29..c8785bec5 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,8 @@ if [ ! -d "$DATA_DIRECTORY" ]; then chmod -R 777 "$REPORT_DIRECTORY" fi -<<<<<<< HEAD -docker pull owasp/dependency-check -======= # Make sure we are using the latest version docker pull owasp/dependency-check ->>>>>>> 04b506662f96d23d66b858d3d4b9ea29caa4ccb8 docker run --rm \ --volume $(pwd):/src \ From abb10600f7c2fa6e7ae84732c53cb9898909242c Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 7 May 2017 10:14:09 -0400 Subject: [PATCH 16/30] fix for PR #716 - corrected malformed json generation --- .../main/resources/templates/JsonReport.vsl | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/dependency-check-core/src/main/resources/templates/JsonReport.vsl b/dependency-check-core/src/main/resources/templates/JsonReport.vsl index 4fbf0b79a..5bf5c1aad 100644 --- a/dependency-check-core/src/main/resources/templates/JsonReport.vsl +++ b/dependency-check-core/src/main/resources/templates/JsonReport.vsl @@ -156,20 +156,20 @@ #foreach($vuln in $dependency.getSuppressedVulnerabilities())#if($foreach.count > 1),#end { "name": "$enc.json($vuln.name)", "cvssScore": "$vuln.cvssScore", - "cvssAccessVector": "$enc.json"($vuln.cvssAccessVector), - "cvssAccessComplexity": "$enc.json"($vuln.cvssAccessComplexity), - "cvssAuthenticationr": "$enc.json"($vuln.cvssAuthentication), - "cvssConfidentialImpact": "$enc.json"($vuln.cvssConfidentialityImpact), - "cvssIntegrityImpact": "$enc.json"($vuln.cvssIntegrityImpact), - "cvssAvailabilityImpact": "$enc.json"($vuln.cvssAvailabilityImpact), - #if ($vuln.cvssScore<4.0) "severity": "Low", - #elseif ($vuln.cvssScore>=7.0) "severity": "High", - #else "severity": "Medium", - #end - #if ($vuln.cwe)"cwe": "$enc.json($vuln.cwe)",#end + "cvssAccessVector": "$enc.json($vuln.cvssAccessVector)", + "cvssAccessComplexity": "$enc.json($vuln.cvssAccessComplexity)", + "cvssAuthenticationr": "$enc.json($vuln.cvssAuthentication)", + "cvssConfidentialImpact": "$enc.json($vuln.cvssConfidentialityImpact)", + "cvssIntegrityImpact": "$enc.json($vuln.cvssIntegrityImpact)", + "cvssAvailabilityImpact": "$enc.json($vuln.cvssAvailabilityImpact)", + #if ($vuln.cvssScore<4.0) "severity": "Low", + #elseif ($vuln.cvssScore>=7.0) "severity": "High", + #else "severity": "Medium", + #end + #if ($vuln.cwe)"cwe": "$enc.json($vuln.cwe)",#end "description": "$enc.json($vuln.description)" #if ($vuln.notes),"notes": "$enc.json($vuln.notes)"#end - ,"references" [ + ,"references": [ #foreach($ref in $vuln.getReferences()) #if($foreach.count > 1),#end { "source": "$enc.json($ref.source)", @@ -181,14 +181,14 @@ "vulnerableSoftware": [ #foreach($vs in $vuln.getVulnerableSoftware()) #if($foreach.count > 1),#end { - #if($vs.hasPreviousVersion()) "allPreviousVersion": "true"#end, + #if($vs.hasPreviousVersion()) "allPreviousVersion": "true",#end "name": "$enc.json($vs.name)" } #end ] } #end - } + ] #end } From fd0abd906664520597f6a39dd8a04ebffaedecc2 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 7 May 2017 10:14:50 -0400 Subject: [PATCH 17/30] updated test cases for FP --- .../dependencycheck-base-suppression.xml | 9 ++++ .../src/it/false-positives/pom.xml | 46 ++++++++++++++++++- .../src/it/false-positives/postbuild.groovy | 10 ---- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml index b4c75e316..b2f0a6db6 100644 --- a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml +++ b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml @@ -584,4 +584,13 @@ cpe:/a:pivotal:spring_framework cpe:/a:pivotal_software:spring_framework + + + ^org\.springframework\.cloud:spring-cloud-.*$ + cpe:/a:pivotal:spring_framework + cpe:/a:pivotal_software:spring_framework + cpe:/a:context_project:context + diff --git a/dependency-check-maven/src/it/false-positives/pom.xml b/dependency-check-maven/src/it/false-positives/pom.xml index 646ca1a47..079ab7818 100644 --- a/dependency-check-maven/src/it/false-positives/pom.xml +++ b/dependency-check-maven/src/it/false-positives/pom.xml @@ -22,6 +22,19 @@ Copyright (c) 2017 Jeremy Long. All Rights Reserved. test-dataformat-jackson 1.0.0-SNAPSHOT jar + + + redhat + redhat + https://maven.repository.redhat.com/ga/ + + + spring + spring + http://repo.spring.io/plugins-release/ + + + @@ -29,6 +42,37 @@ Copyright (c) 2017 Jeremy Long. All Rights Reserved. spring-boot 1.4.3.RELEASE - + + + + com.splunk + splunk + 1.6.2.0 + + + + + org.springframework.cloud + spring-cloud-netflix-core + 1.2.5.RELEASE + + + org.springframework.cloud + spring-cloud-commons + 1.1.7.RELEASE + + + org.springframework.cloud + spring-cloud-context + 1.1.7.RELEASE + + + + + org.openid4java + openid4java + 0.9.7 + + \ No newline at end of file diff --git a/dependency-check-maven/src/it/false-positives/postbuild.groovy b/dependency-check-maven/src/it/false-positives/postbuild.groovy index 4ce83a609..7c58af5af 100644 --- a/dependency-check-maven/src/it/false-positives/postbuild.groovy +++ b/dependency-check-maven/src/it/false-positives/postbuild.groovy @@ -20,16 +20,6 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import java.nio.charset.Charset; -// Save NVD-CVE for next IT (if not already done) -File datasDwl = new File("target/local-repo/org/owasp/dependency-check-data/3.0", "dc.h2.db"); -File datasSave = new File("target/nvd-cve-backup", "dc.h2.db"); -if (datasDwl.exists() && !datasSave.exists()){ - System.out.println("Save NVD-CVE into backup"); - FileUtils.copyFile(datasDwl, datasSave); -} - - - // Check to see if jackson-dataformat-xml-2.4.5.jar was identified. //TODO change this to xpath and check for CVE-2016-3720 From 0c991af58da0c311b6ada470fa58f8ffe1f870db Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 7 May 2017 16:52:20 -0400 Subject: [PATCH 18/30] fixed logging for #710 --- .../main/java/org/owasp/dependencycheck/xml/pom/PomParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/pom/PomParser.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/pom/PomParser.java index 3b81e1591..8de5bcb2e 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/pom/PomParser.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/pom/PomParser.java @@ -62,7 +62,7 @@ public class PomParser { return parse(fis); } catch (IOException ex) { LOGGER.debug("", ex); - throw new PomParseException(ex); + throw new PomParseException(String.format("Unable to parse pom '%s'", file.toString()), ex); } } From 96cda322a3de839b68f672fd523dd801e7d3217e Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 7 May 2017 17:11:46 -0400 Subject: [PATCH 19/30] fixed coverity reported bug --- .../org/owasp/dependencycheck/data/update/NvdCveUpdater.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java index 60b577582..5659a4376 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java @@ -182,8 +182,8 @@ public class NvdCveUpdater implements CachedWebDataSource { LOGGER.trace("Ignorable exception", ex); } } - if (lockFile != null) { - lockFile.delete(); + if (lockFile != null && lockFile.isFile() && !lockFile.delete()) { + LOGGER.error("Lock file '{}' was unable to be deleted. Please manually delete this file.", lockFile.toString()); } } } From 06d6fe4bd66f3d1db5ed57ea7cf7336dc8b82f07 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 7 May 2017 17:47:24 -0400 Subject: [PATCH 20/30] updated #716 --- .../reporting/ReportGenerator.java | 112 ++++++++++++++---- 1 file changed, 89 insertions(+), 23 deletions(-) diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java index 340166b0e..194c3ceca 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java @@ -18,13 +18,15 @@ package org.owasp.dependencycheck.reporting; import java.io.*; -import java.nio.file.*; import java.util.List; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import static com.google.gson.stream.JsonToken.*; +import com.google.gson.stream.JsonWriter; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; @@ -138,16 +140,14 @@ public class ReportGenerator { * @param properties the database properties (containing timestamps of the * NVD CVE data) */ + public ReportGenerator(String applicationName, String applicationVersion, String artifactID, String groupID, List dependencies, List analyzers, DatabaseProperties properties) { - public ReportGenerator(String applicationName,String applicationVersion,String artifactID,String groupID, List dependencies, List analyzers, DatabaseProperties properties) { - - this(applicationName,dependencies,analyzers,properties); - context.put("applicationVersion",applicationVersion); - context.put("artifactID",artifactID); - context.put("groupID",groupID); + this(applicationName, dependencies, analyzers, properties); + context.put("applicationVersion", applicationVersion); + context.put("artifactID", artifactID); + context.put("groupID", groupID); } - /** * Creates a new Velocity Engine. * @@ -206,17 +206,7 @@ public class ReportGenerator { } if (format == Format.JSON || format == Format.ALL) { generateReport("JsonReport", outputDir + File.separator + "dependency-check-report.json"); - try { - Path resultPath = Paths.get(outputDir + File.separator + "dependency-check-report.json"); - String content = new String(Files.readAllBytes(resultPath)); - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - JsonParser jp = new JsonParser(); - JsonElement je = jp.parse(content); - String prettyJson = gson.toJson(je); - Files.write(Paths.get(outputDir + File.separator + "dependency-check-report.json"), prettyJson.getBytes(), StandardOpenOption.WRITE); - } catch (IOException e) { - LOGGER.error("Unable to generate pretty report, got error: ", e.getMessage()); - } + pretifyJson(outputDir + File.separator + "dependency-check-report.json"); } if (format == Format.HTML || format == Format.ALL) { generateReport("HtmlReport", outputDir + File.separator + "dependency-check-report.html"); @@ -224,7 +214,82 @@ public class ReportGenerator { if (format == Format.VULN || format == Format.ALL) { generateReport("VulnerabilityReport", outputDir + File.separator + "dependency-check-vulnerability.html"); } + } + private void pretifyJson(String pathToJson) throws JsonSyntaxException { + final String outputPath = pathToJson + ".pretty"; + final File in = new File(pathToJson); + final File out = new File(outputPath); + try (JsonReader reader = new JsonReader(new InputStreamReader(new FileInputStream(in), StandardCharsets.UTF_8)); + JsonWriter writer = new JsonWriter(new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8))) { + prettyPrint(reader, writer); + } catch (IOException ex) { + LOGGER.error("Unable to generate pretty report, caused by: ", ex.getMessage()); + return; + } + if (out.isFile() && in.isFile() && in.delete()) { + try { + org.apache.commons.io.FileUtils.moveFile(out, in); + } catch (IOException ex) { + LOGGER.error("Unable to generate pretty report, caused by: ", ex.getMessage()); + } + } + } + + /** + * Streams from a json reader to a json writer and performs pretty printing. + * + * This function is copied from https://sites.google.com/site/gson/streaming + * + * @param reader json reader + * @param writer json writer + * @throws IOException thrown if the json is malformed + */ + private static void prettyPrint(JsonReader reader, JsonWriter writer) throws IOException { + writer.setIndent(" "); + while (true) { + JsonToken token = reader.peek(); + switch (token) { + case BEGIN_ARRAY: + reader.beginArray(); + writer.beginArray(); + break; + case END_ARRAY: + reader.endArray(); + writer.endArray(); + break; + case BEGIN_OBJECT: + reader.beginObject(); + writer.beginObject(); + break; + case END_OBJECT: + reader.endObject(); + writer.endObject(); + break; + case NAME: + String name = reader.nextName(); + writer.name(name); + break; + case STRING: + String s = reader.nextString(); + writer.value(s); + break; + case NUMBER: + String n = reader.nextString(); + writer.value(new BigDecimal(n)); + break; + case BOOLEAN: + boolean b = reader.nextBoolean(); + writer.value(b); + break; + case NULL: + reader.nextNull(); + writer.nullValue(); + break; + case END_DOCUMENT: + return; + } + } } /** @@ -264,6 +329,7 @@ public class ReportGenerator { if ("JSON".equalsIgnoreCase(format)) { if (pathToCheck.endsWith(".json")) { generateReport("JsonReport", outputDir); + pretifyJson(outputDir); } else { generateReports(outputDir, Format.JSON); } From 8fc42078c792d96fe2be646b42d7e4ac855bccd1 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 7 May 2017 18:40:25 -0400 Subject: [PATCH 21/30] checkstyle corrections, minor restructuring, etc. --- .../dependencycheck/reporting/EscapeTool.java | 8 +++-- .../reporting/ReportGenerator.java | 30 ++++++++++++------- .../reporting/ReportGeneratorIT.java | 23 +++++++------- .../maven/BaseDependencyCheckMojo.java | 8 +++-- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java index 9c1d905ce..43e0ea230 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java @@ -24,8 +24,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * An extremely simple wrapper around various escape utils to perform URL and HTML encoding within the reports. This class was - * created to simplify the velocity configuration and avoid using the "built-in" escape tool. + * An extremely simple wrapper around various escape utils to perform URL and + * HTML encoding within the reports. This class was created to simplify the + * velocity configuration and avoid using the "built-in" escape tool. * * @author Jeremy Long */ @@ -82,7 +83,8 @@ public class EscapeTool { } /** - * JSON Encodes the provded text + * JSON Encodes the provided text. + * * @param text the text to encode * @return the JSON encoded text */ diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java index 194c3ceca..da4fa5429 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java @@ -132,18 +132,19 @@ public class ReportGenerator { * Constructs a new ReportGenerator. * * @param applicationName the application name being analyzed - * @param applicationVersion the application version being analyzed - * @param artifactID the application version being analyzed - * @param applicationVersion the application version being analyzed + * @param groupID the group id of the project being analyzed + * @param artifactID the application id of the project being analyzed + * @param version the application version of the project being analyzed * @param dependencies the list of dependencies * @param analyzers the list of analyzers used * @param properties the database properties (containing timestamps of the * NVD CVE data) */ - public ReportGenerator(String applicationName, String applicationVersion, String artifactID, String groupID, List dependencies, List analyzers, DatabaseProperties properties) { + public ReportGenerator(String applicationName, String groupID, String artifactID, String version, + List dependencies, List analyzers, DatabaseProperties properties) { this(applicationName, dependencies, analyzers, properties); - context.put("applicationVersion", applicationVersion); + context.put("applicationVersion", version); context.put("artifactID", artifactID); context.put("groupID", groupID); } @@ -216,6 +217,12 @@ public class ReportGenerator { } } + /** + * Reformats the given JSON file. + * + * @param pathToJson the path to the JSON file to be reformatted + * @throws JsonSyntaxException thrown if the given JSON file is malformed + */ private void pretifyJson(String pathToJson) throws JsonSyntaxException { final String outputPath = pathToJson + ".pretty"; final File in = new File(pathToJson); @@ -248,7 +255,7 @@ public class ReportGenerator { private static void prettyPrint(JsonReader reader, JsonWriter writer) throws IOException { writer.setIndent(" "); while (true) { - JsonToken token = reader.peek(); + final JsonToken token = reader.peek(); switch (token) { case BEGIN_ARRAY: reader.beginArray(); @@ -267,19 +274,19 @@ public class ReportGenerator { writer.endObject(); break; case NAME: - String name = reader.nextName(); + final String name = reader.nextName(); writer.name(name); break; case STRING: - String s = reader.nextString(); + final String s = reader.nextString(); writer.value(s); break; case NUMBER: - String n = reader.nextString(); + final String n = reader.nextString(); writer.value(new BigDecimal(n)); break; case BOOLEAN: - boolean b = reader.nextBoolean(); + final boolean b = reader.nextBoolean(); writer.value(b); break; case NULL: @@ -288,6 +295,9 @@ public class ReportGenerator { break; case END_DOCUMENT: return; + default: + LOGGER.debug("Unexpected JSON toekn {}", token.toString()); + break; } } } diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/ReportGeneratorIT.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/ReportGeneratorIT.java index 131baf78c..e472c1e63 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/ReportGeneratorIT.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/ReportGeneratorIT.java @@ -109,8 +109,8 @@ public class ReportGeneratorIT extends BaseDBTestCase { } /** - * Generates an XML report containing known vulnerabilities and realistic data and validates the generated XML document - * against the XSD. + * Generates an XML report containing known vulnerabilities and realistic + * data and validates the generated XML document against the XSD. * * @throws Exception */ @@ -118,42 +118,43 @@ public class ReportGeneratorIT extends BaseDBTestCase { public void testGenerateXMLReport() { try { String templateName = "XmlReport"; - + File f = new File("target/test-reports"); if (!f.exists()) { f.mkdir(); } String writeTo = "target/test-reports/Report.xml"; File suppressionFile = BaseTest.getResourceAsFile(this, "incorrectSuppressions.xml"); - + Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile.getAbsolutePath()); - + //File struts = new File(this.getClass().getClassLoader().getResource("struts2-core-2.1.2.jar").getPath()); File struts = BaseTest.getResourceAsFile(this, "struts2-core-2.1.2.jar"); //File axis = new File(this.getClass().getClassLoader().getResource("axis2-adb-1.4.1.jar").getPath()); File axis = BaseTest.getResourceAsFile(this, "axis2-adb-1.4.1.jar"); //File jetty = new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath()); File jetty = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar"); - + boolean autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Engine engine = new Engine(); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate); - + engine.scan(struts); engine.scan(axis); engine.scan(jetty); engine.analyzeDependencies(); - + CveDB cveDB = CveDB.getInstance(); DatabaseProperties dbProp = cveDB.getDatabaseProperties(); - ReportGenerator generator = new ReportGenerator("Test Report","1.4.7","dependency-check-core","org.owasp", engine.getDependencies(), engine.getAnalyzers(), dbProp); + ReportGenerator generator = new ReportGenerator("Test Report", "org.owasp", "dependency-check-core", "1.4.7", + engine.getDependencies(), engine.getAnalyzers(), dbProp); generator.generateReport(templateName, writeTo); cveDB.close(); - + engine.cleanup(); - + InputStream xsdStream = ReportGenerator.class.getClassLoader().getResourceAsStream("schema/dependency-check.1.5.xsd"); StreamSource xsdSource = new StreamSource(xsdStream); StreamSource xmlSource = new StreamSource(new File(writeTo)); diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index 547f68fb5..1a923a698 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -117,7 +117,7 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma * The Maven Session. */ @Parameter(defaultValue = "${session}", readonly = true, required = true) - protected MavenSession session; + private MavenSession session; /** * Remote repositories which will be searched for artifacts. @@ -627,7 +627,8 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma * @return a collection of exceptions that may have occurred while resolving * and scanning the dependencies */ - private ExceptionCollection collectDependencies(Engine engine, MavenProject project, List nodes, ProjectBuildingRequest buildingRequest) { + private ExceptionCollection collectDependencies(Engine engine, MavenProject project, + List nodes, ProjectBuildingRequest buildingRequest) { ExceptionCollection exCol = null; for (DependencyNode dependencyNode : nodes) { exCol = collectDependencies(engine, project, dependencyNode.getChildren(), buildingRequest); @@ -1015,7 +1016,8 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma getLog().debug("Unable to retrieve DB Properties", ex); } } - final ReportGenerator r = new ReportGenerator(p.getName(),p.getVersion(),p.getArtifactId(),p.getGroupId(), engine.getDependencies(), engine.getAnalyzers(), prop); + final ReportGenerator r = new ReportGenerator(p.getName(), p.getGroupId(), p.getArtifactId(), p.getVersion(), + engine.getDependencies(), engine.getAnalyzers(), prop); try { r.generateReports(outputDir.getAbsolutePath(), format); } catch (ReportException ex) { From 4c15993a44e5f0bfafb92d1855c44019f42c9eb5 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Mon, 8 May 2017 07:42:01 -0400 Subject: [PATCH 22/30] updated documentation for issue #675 and pr #716 --- dependency-check-ant/src/site/markdown/configuration.md | 6 +++--- dependency-check-cli/src/site/markdown/arguments.md | 2 +- dependency-check-maven/src/site/markdown/configuration.md | 2 +- src/site/markdown/dependency-check-gradle/configuration.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dependency-check-ant/src/site/markdown/configuration.md b/dependency-check-ant/src/site/markdown/configuration.md index c7639323a..8b0b84634 100644 --- a/dependency-check-ant/src/site/markdown/configuration.md +++ b/dependency-check-ant/src/site/markdown/configuration.md @@ -36,11 +36,11 @@ cveValidForHours | Sets the number of hours to wait before checking for new failBuildOnCVSS | Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which means since the CVSS scores are 0-10, by default the build will never fail. | 11 failOnError | Whether the build should fail if there is an error executing the dependency-check analysis | true projectName | The name of the project being scanned. | Dependency-Check -reportFormat | The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the Site plugin unless the externalReport is set to true. | HTML +reportFormat | The report format to be generated (HTML, XML, CSV, JSON, VULN, ALL). This configuration option has no affect if using this within the Site plugin unless the externalReport is set to true. | HTML reportOutputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build | 'target' suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html) |   -hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html) |   -proxyServer | The Proxy Server; see the [proxy configuration](../data/proxy.html) page for more information. |   +hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html) |   +proxyServer | The Proxy Server; see the [proxy configuration](../data/proxy.html) page for more information. |   proxyPort | The Proxy Port. |   proxyUsername | Defines the proxy user name. |   proxyPassword | Defines the proxy password. |   diff --git a/dependency-check-cli/src/site/markdown/arguments.md b/dependency-check-cli/src/site/markdown/arguments.md index f7688b6b0..048c1f193 100644 --- a/dependency-check-cli/src/site/markdown/arguments.md +++ b/dependency-check-cli/src/site/markdown/arguments.md @@ -10,7 +10,7 @@ Short | Argument Name   | Parameter | Description | Requir | \-\-exclude | \ | The path patterns to exclude from the scan \- this option can be specified multiple times. This accepts Ant style path patterns (e.g. **/exclude/**). | Optional | \-\-symLink | \ | The depth that symbolic links will be followed; the default is 0 meaning symbolic links will not be followed. | Optional \-o | \-\-out | \ | The folder to write reports to. This defaults to the current directory. If the format is not set to ALL one could specify a specific file name. | Optional - \-f | \-\-format | \ | The output format to write to (XML, HTML, VULN, ALL). The default is HTML. | Required + \-f | \-\-format | \ | The output format to write to (XML, HTML, CSV, JSON, VULN, ALL). The default is HTML. | Required | \-\-failOnCvss | \ | If the score set between 0 and 10 the exit code from dependency-check will indicate if a vulnerability with a CVSS score equal to or higher was identified. | Optional \-l | \-\-log | \ | The file path to write verbose logging information. | Optional \-n | \-\-noupdate | | Disables the automatic updating of the CPE data. | Optional diff --git a/dependency-check-maven/src/site/markdown/configuration.md b/dependency-check-maven/src/site/markdown/configuration.md index c43075f2e..c42cfc28b 100644 --- a/dependency-check-maven/src/site/markdown/configuration.md +++ b/dependency-check-maven/src/site/markdown/configuration.md @@ -19,7 +19,7 @@ cveValidForHours | Sets the number of hours to wait before checking f failBuildOnCVSS | Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which means since the CVSS scores are 0-10, by default the build will never fail. | 11 failBuildOnAnyVulnerability | Specific that if any vulnerability is identified, the build will fail. | false failOnError | Whether the build should fail if there is an error executing the dependency-check analysis. | true -format | The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the Site plugin unless the externalReport is set to true. | HTML +format | The report format to be generated (HTML, XML, CSV, JSON, VULN, ALL). This configuration option has no affect if using this within the Site plugin unless the externalReport is set to true. | HTML name | The name of the report in the site. | dependency-check or dependency-check:aggregate outputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build. | 'target' skip | Skips the dependency-check analysis. | false diff --git a/src/site/markdown/dependency-check-gradle/configuration.md b/src/site/markdown/dependency-check-gradle/configuration.md index f33730a01..9268b9487 100644 --- a/src/site/markdown/dependency-check-gradle/configuration.md +++ b/src/site/markdown/dependency-check-gradle/configuration.md @@ -17,7 +17,7 @@ autoUpdate | Sets whether auto-updating of the NVD CVE/CPE data is ena cveValidForHours | Sets the number of hours to wait before checking for new updates from the NVD. | 4 failOnError | Fails the build if an error occurs during the dependency-check analysis. | true failBuildOnCVSS | Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11; since the CVSS scores are 0-10, by default the build will never fail. | 11 -format | The report format to be generated (HTML, XML, VULN, ALL). | HTML +format | The report format to be generated (HTML, XML, CSV, JSON, VULN, ALL). | HTML outputDirectory | The location to write the report(s). This directory will be located in the build directory. | build/reports skipTestGroups | When set to true (the default) all dependency groups that being with 'test' will be skipped. | true suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html) |   From e721dac3891a16c2f705c90826b6e8c460e3e8f3 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Mon, 8 May 2017 07:43:39 -0400 Subject: [PATCH 23/30] implemented CSV reports per #675 --- .../owasp/dependencycheck/taskdefs/Check.java | 4 +- .../org/owasp/dependencycheck/CliParser.java | 2 +- .../dependencycheck/reporting/EscapeTool.java | 69 +++++ .../reporting/ReportGenerator.java | 19 +- .../main/resources/templates/CsvReport.vsl | 27 ++ .../reporting/EscapeToolTest.java | 235 ++++++++++++++++++ .../maven/BaseDependencyCheckMojo.java | 4 + 7 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 dependency-check-core/src/main/resources/templates/CsvReport.vsl create mode 100644 dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java diff --git a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java index 5cc1e8226..d69b3ecbe 100644 --- a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java +++ b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java @@ -146,7 +146,7 @@ public class Check extends Update { private boolean updateOnly = false; /** - * The report format to be generated (HTML, XML, VULN, ALL). Default is + * The report format to be generated (HTML, XML, VULN, CSV, JSON, ALL). Default is * HTML. */ private String reportFormat = "HTML"; @@ -1102,7 +1102,7 @@ public class Check extends Update { } /** - * An enumeration of supported report formats: "ALL", "HTML", "XML", "VULN", + * An enumeration of supported report formats: "ALL", "HTML", "XML", "CSV", "JSON", "VULN", * etc.. */ public static class ReportFormats extends EnumeratedAttribute { diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java index 6413017c1..c259e50d0 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java @@ -120,7 +120,7 @@ public final class CliParser { Format.valueOf(format); } catch (IllegalArgumentException ex) { final String msg = String.format("An invalid 'format' of '%s' was specified. " - + "Supported output formats are XML, JSON, HTML, VULN, or ALL", format); + + "Supported output formats are HTML, XML, CSV, JSON, VULN, or ALL", format); throw new ParseException(msg); } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java index 43e0ea230..48014183a 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java @@ -19,7 +19,9 @@ package org.owasp.dependencycheck.reporting; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.util.Set; import org.apache.commons.lang3.StringEscapeUtils; +import org.owasp.dependencycheck.dependency.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,4 +96,71 @@ public class EscapeTool { } return StringEscapeUtils.escapeJson(text); } + + /** + * Formats text for CSV format. This includes trimming whitespace, replace + * line breaks with spaces, and if necessary quotes the text and/or escapes + * contained quotes. + * + * @param text the text to escape and quote + * @return the escaped and quoted text + */ + public String csv(String text) { + if (text == null || text.isEmpty()) { + return text; + } + return StringEscapeUtils.escapeCsv(text.trim().replace("\n", " ")); + } + + /** + * Takes a set of Identifiers, filters them to none CPE, and formats them + * for display in a CSV. + * + * @param ids the set of identifiers + * @return the formated list of none CPE identifiers + */ + public String csvIdentifiers(Set ids) { + if (ids == null || ids.isEmpty()) { + return ""; + } + boolean addComma = false; + StringBuilder sb = new StringBuilder(); + for (Identifier id : ids) { + if (!"cpe".equals(id.getType())) { + if (addComma) { + sb.append(", "); + } else { + addComma = true; + } + sb.append(id.getValue()); + } + } + return StringEscapeUtils.escapeCsv(sb.toString()); + } + + /** + * Takes a set of Identifiers, filters them to just CPEs, and formats them + * for display in a CSV. + * + * @param ids the set of identifiers + * @return the formated list of CPE identifiers + */ + public String csvCpe(Set ids) { + if (ids == null || ids.isEmpty()) { + return ""; + } + boolean addComma = false; + StringBuilder sb = new StringBuilder(); + for (Identifier id : ids) { + if ("cpe".equals(id.getType())) { + if (addComma) { + sb.append(", "); + } else { + addComma = true; + } + sb.append(id.getValue()); + } + } + return StringEscapeUtils.escapeCsv(sb.toString()); + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java index da4fa5429..b8acd2557 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java @@ -81,7 +81,11 @@ public class ReportGenerator { /** * Generate JSON report. */ - JSON + JSON, + /** + * Generate CSV report. + */ + CSV } /** * The Velocity Engine. @@ -191,6 +195,9 @@ public class ReportGenerator { if (format == Format.JSON || format == Format.ALL) { generateReport("JsonReport", outputStream); } + if (format == Format.CSV || format == Format.ALL) { + generateReport("CsvReport", outputStream); + } } /** @@ -209,6 +216,9 @@ public class ReportGenerator { generateReport("JsonReport", outputDir + File.separator + "dependency-check-report.json"); pretifyJson(outputDir + File.separator + "dependency-check-report.json"); } + if (format == Format.CSV || format == Format.ALL) { + generateReport("CsvReport", outputDir + File.separator + "dependency-check-report.csv"); + } if (format == Format.HTML || format == Format.ALL) { generateReport("HtmlReport", outputDir + File.separator + "dependency-check-report.html"); } @@ -344,6 +354,13 @@ public class ReportGenerator { generateReports(outputDir, Format.JSON); } } + if ("CSV".equalsIgnoreCase(format)) { + if (pathToCheck.endsWith(".csv")) { + generateReport("CsvReport", outputDir); + } else { + generateReports(outputDir, Format.JSON); + } + } if ("ALL".equalsIgnoreCase(format)) { generateReports(outputDir, Format.ALL); } diff --git a/dependency-check-core/src/main/resources/templates/CsvReport.vsl b/dependency-check-core/src/main/resources/templates/CsvReport.vsl new file mode 100644 index 000000000..99c2758a1 --- /dev/null +++ b/dependency-check-core/src/main/resources/templates/CsvReport.vsl @@ -0,0 +1,27 @@ +#** +This file is part of Dependency-Check. + +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) 2017 Jeremy Long. All Rights Reserved. + +@author Jeremy Long +@version 1 *### +"Project","ScanDate","DependencyName","DependencyPath","Description","License","Md5","Sha1","Identifiers","CPE","CVE","CWE","Vulnerability","Severity","CVSSv2" +#macro(writeSev $score)#if($score<4.0)"Low"#elseif($score>=7.0)"High"#else"Medium"#end#end +#foreach($dependency in $dependencies)#if($dependency.getVulnerabilities().size()>0) +#foreach($vuln in $dependency.getVulnerabilities()) +$enc.csv($applicationName),$enc.csv($scanDate),$enc.csv($dependency.DisplayFileName),#if($dependency.FilePath)$enc.csv($dependency.FilePath)#end,#if($dependency.description)$enc.csv($dependency.description)#end,#if($dependency.license)$enc.csv($dependency.license)#end,#if($dependency.Md5sum)$enc.csv($dependency.Md5sum)#end,#if($dependency.Sha1sum)$enc.csv($dependency.Sha1sum)#end,#if($dependency.identifiers)$enc.csvIdentifiers($dependency.identifiers)#end,#if($dependency.identifiers)$enc.csvCpe($dependency.identifiers)#end,#if($vuln.name)$enc.csv($vuln.name)#end,#if($dependency.cwe)$enc.csv($vuln.cwe)#end,#if($vuln.description)$enc.csv($vuln.description)#end,#writeSev($vuln.cvssScore),$vuln.cvssScore +#end +#end +#end \ No newline at end of file diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java new file mode 100644 index 000000000..f49f060b4 --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java @@ -0,0 +1,235 @@ +/* + * 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) 2017 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.reporting; + +import java.util.HashSet; +import java.util.Set; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; +import org.owasp.dependencycheck.dependency.Identifier; + +/** + * + * @author jerem + */ +public class EscapeToolTest { + + /** + * Test of url method, of class EscapeTool. + */ + @Test + public void testUrl() { + String text = null; + EscapeTool instance = new EscapeTool(); + String expResult = null; + String result = instance.url(text); + assertEquals(expResult, result); + + text = ""; + expResult = ""; + result = instance.url(text); + assertEquals(expResult, result); + + text = " "; + expResult = "+"; + result = instance.url(text); + assertEquals(expResult, result); + } + + /** + * Test of html method, of class EscapeTool. + */ + @Test + public void testHtml() { + EscapeTool instance = new EscapeTool(); + String text = null; + String expResult = null; + String result = instance.html(text); + assertEquals(expResult, result); + + text = ""; + expResult = ""; + result = instance.html(text); + assertEquals(expResult, result); + + text = "
    "; + expResult = "<div>"; + result = instance.html(text); + assertEquals(expResult, result); + } + + /** + * Test of xml method, of class EscapeTool. + */ + @Test + public void testXml() { + EscapeTool instance = new EscapeTool(); + String text = null; + String expResult = null; + String result = instance.xml(text); + assertEquals(expResult, result); + + text = ""; + expResult = ""; + result = instance.xml(text); + assertEquals(expResult, result); + + text = "
    "; + expResult = "<div>"; + result = instance.xml(text); + assertEquals(expResult, result); + } + + /** + * Test of json method, of class EscapeTool. + */ + @Test + public void testJson() { + String text = null; + EscapeTool instance = new EscapeTool(); + String expResult = null; + String result = instance.json(text); + assertEquals(expResult, result); + + text = ""; + expResult = ""; + result = instance.json(text); + assertEquals(expResult, result); + + text = "test \"quote\"\""; + expResult = "test \\\"quote\\\"\\\""; + result = instance.json(text); + assertEquals(expResult, result); + } + + /** + * Test of csv method, of class EscapeTool. + */ + @Test + public void testCsv() { + String text = null; + EscapeTool instance = new EscapeTool(); + String expResult = null; + String result = instance.csv(text); + assertEquals(expResult, result); + + text = ""; + expResult = ""; + result = instance.csv(text); + assertEquals(expResult, result); + + text = "one, two"; + expResult = "\"one, two\""; + result = instance.csv(text); + assertEquals(expResult, result); + } + + /** + * Test of csvIdentifiers method, of class EscapeTool. + */ + @Test + public void testCsvIdentifiers() { + EscapeTool instance = new EscapeTool(); + Set ids = null; + String expResult = ""; + String result = instance.csvIdentifiers(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + expResult = ""; + result = instance.csvIdentifiers(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", "")); + expResult = ""; + result = instance.csvIdentifiers(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + ids.add(new Identifier("gav", "somegroup:something:1.0", "")); + expResult = "somegroup:something:1.0"; + result = instance.csvIdentifiers(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", "")); + ids.add(new Identifier("gav", "somegroup:something:1.0", "")); + expResult = "somegroup:something:1.0"; + result = instance.csvIdentifiers(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", "")); + ids.add(new Identifier("gav", "somegroup:something:1.0", "")); + ids.add(new Identifier("gav", "somegroup2:something:1.2", "")); + expResult = "\"somegroup:something:1.0, somegroup2:something:1.2\""; + String expResult2 = "\"somegroup2:something:1.2, somegroup:something:1.0\""; + result = instance.csvIdentifiers(ids); + assertTrue(expResult.equals(result) || expResult2.equals(result)); + } + + /** + * Test of csvCpe method, of class EscapeTool. + */ + @Test + public void testCsvCpe() { + EscapeTool instance = new EscapeTool(); + Set ids = null; + String expResult = ""; + String result = instance.csvCpe(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + expResult = ""; + result = instance.csvCpe(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + ids.add(new Identifier("gav", "somegroup:something:1.0", "")); + expResult = ""; + result = instance.csvCpe(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", "")); + expResult = "cpe:/a:somegroup:something:1.0"; + result = instance.csvCpe(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", "")); + ids.add(new Identifier("gav", "somegroup:something:1.0", "")); + expResult = "cpe:/a:somegroup:something:1.0"; + result = instance.csvCpe(ids); + assertEquals(expResult, result); + + ids = new HashSet<>(); + ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", "")); + ids.add(new Identifier("gav", "somegroup:something:1.0", "")); + ids.add(new Identifier("cpe", "cpe:/a:somegroup2:something:1.2", "")); + expResult = "\"cpe:/a:somegroup:something:1.0, cpe:/a:somegroup2:something:1.2\""; + String expResult2 = "\"cpe:/a:somegroup2:something:1.2, cpe:/a:somegroup:something:1.0\""; + result = instance.csvCpe(ids); + assertTrue(expResult.equals(result) || expResult2.equals(result)); + } +} diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index 1a923a698..9dc7dc799 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -757,6 +757,10 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma return "dependency-check-report.xml#"; } else if ("VULN".equalsIgnoreCase(this.format)) { return "dependency-check-vulnerability"; + } else if ("JSON".equalsIgnoreCase(this.format)) { + return "dependency-check-report.json"; + } else if ("CSV".equalsIgnoreCase(this.format)) { + return "dependency-check-report.csv"; } else { getLog().warn("Unknown report format used during site generation."); return "dependency-check-report"; From ee72f172d2ded278f71002102bc032ce9294275d Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Mon, 8 May 2017 21:41:05 -0400 Subject: [PATCH 24/30] fix codacy issue --- .../org/owasp/dependencycheck/reporting/EscapeToolTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java index f49f060b4..04eda668c 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java @@ -19,10 +19,6 @@ package org.owasp.dependencycheck.reporting; import java.util.HashSet; import java.util.Set; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; import org.owasp.dependencycheck.dependency.Identifier; From 290cd0507e986113daef89ae7a18ee2297a5aaea Mon Sep 17 00:00:00 2001 From: Seth Jackson Date: Tue, 9 May 2017 10:23:34 -0400 Subject: [PATCH 25/30] Suppress EntityFramework false positives. --- .../main/resources/dependencycheck-base-suppression.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml index b2f0a6db6..2234195fa 100644 --- a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml +++ b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml @@ -1,5 +1,13 @@ + + + .*EntityFramework\.SqlServer*\.dll + cpe:/a:microsoft:server:6.0.0.0 + cpe:/a:microsoft:sql_server:6.0 + Date: Tue, 9 May 2017 10:57:58 -0400 Subject: [PATCH 26/30] Update Windows development usage. So that the example invocation works in PowerShell and cmd. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c8785bec5..f14f03c16 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,8 @@ $ ./dependency-check-cli/target/release/bin/dependency-check.sh --project Testin On Windows ``` > mvn install -> dependency-check-cli/target/release/bin/dependency-check.bat -h -> dependency-check-cli/target/release/bin/dependency-check.bat --project Testing --out . --scan ./src/test/resources +> .\dependency-check-cli\target\release\bin\dependency-check.bat -h +> .\dependency-check-cli\target\release\bin\dependency-check.bat --project Testing --out . --scan ./src/test/resources ``` Then load the resulting 'DependencyCheck-Report.html' into your favorite browser. From 9c7f6daf75b4559ca5126b60d3ff240160cd694a Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 13 May 2017 08:37:22 -0400 Subject: [PATCH 27/30] updated groovy version to allow the use of newer APIs in the build scripts --- dependency-check-maven/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dependency-check-maven/pom.xml b/dependency-check-maven/pom.xml index ec672c570..8f03b9bb0 100644 --- a/dependency-check-maven/pom.xml +++ b/dependency-check-maven/pom.xml @@ -227,6 +227,13 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-invoker-plugin 2.0.0 + + + org.codehaus.groovy + groovy-all + 2.4.11 + + From 523eed9319e8ef2e08f5098c5dd9367486687a90 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 13 May 2017 08:38:43 -0400 Subject: [PATCH 28/30] resolved issue #686 - reports are generated even if no dependencies were analyzed --- .../dependencycheck/maven/CheckMojo.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java index b566cc306..cb95628db 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java @@ -108,24 +108,23 @@ public class CheckMojo extends BaseDependencyCheckMojo { } exCol = ex; } - if (exCol == null || !exCol.isFatal()) { - try { - writeReports(engine, getProject(), getCorrectOutputDirectory()); - } catch (ReportException ex) { - if (this.isFailOnError()) { - if (exCol != null) { - exCol.addException(ex); - } else { - exCol = new ExceptionCollection("Unable to write the dependency-check report", ex); - } + } + if (exCol == null || !exCol.isFatal()) { + try { + writeReports(engine, getProject(), getCorrectOutputDirectory()); + } catch (ReportException ex) { + if (this.isFailOnError()) { + if (exCol != null) { + exCol.addException(ex); + } else { + exCol = new ExceptionCollection("Unable to write the dependency-check report", ex); } } - //writeDataFile(getProject(), null, engine.getDependencies()); - showSummary(getProject(), engine.getDependencies()); - checkForFailure(engine.getDependencies()); - if (exCol != null && this.isFailOnError()) { - throw new MojoExecutionException("One or more exceptions occurred during dependency-check analysis", exCol); - } + } + showSummary(getProject(), engine.getDependencies()); + checkForFailure(engine.getDependencies()); + if (exCol != null && this.isFailOnError()) { + throw new MojoExecutionException("One or more exceptions occurred during dependency-check analysis", exCol); } } engine.cleanup(); From 555b1dc1cc9a9e781e205e4c3f1252f56b612701 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 13 May 2017 08:40:08 -0400 Subject: [PATCH 29/30] resolution for enhancement #729 --- .../it/729-system-scope/invoker.properties | 19 +++++++++++ .../src/it/729-system-scope/pom.xml | 34 +++++++++++++++++++ .../src/it/729-system-scope/postbuild.groovy | 30 ++++++++++++++++ .../src/it/729-system-scope/prebuild.groovy | 17 ++++++++++ .../maven/BaseDependencyCheckMojo.java | 12 ++++++- .../src/site/markdown/configuration.md | 3 +- 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 dependency-check-maven/src/it/729-system-scope/invoker.properties create mode 100644 dependency-check-maven/src/it/729-system-scope/pom.xml create mode 100644 dependency-check-maven/src/it/729-system-scope/postbuild.groovy create mode 100644 dependency-check-maven/src/it/729-system-scope/prebuild.groovy diff --git a/dependency-check-maven/src/it/729-system-scope/invoker.properties b/dependency-check-maven/src/it/729-system-scope/invoker.properties new file mode 100644 index 000000000..b41bc60f9 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope/invoker.properties @@ -0,0 +1,19 @@ +# +# This file is part of dependency-check-maven. +# +# 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) 2014 Jeremy Long. All Rights Reserved. +# + +invoker.goals = install ${project.groupId}:${project.artifactId}:${project.version}:check -DskipSystemScope=true -Dformat=JSON diff --git a/dependency-check-maven/src/it/729-system-scope/pom.xml b/dependency-check-maven/src/it/729-system-scope/pom.xml new file mode 100644 index 000000000..6f2d06950 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope/pom.xml @@ -0,0 +1,34 @@ + + + + 4.0.0 + org.owasp.test + test-system-scope + 1.0.0-SNAPSHOT + jar + + + system + com.sun + tools + 1.8 + ${java.home}/../lib/tools.jar + + + \ No newline at end of file diff --git a/dependency-check-maven/src/it/729-system-scope/postbuild.groovy b/dependency-check-maven/src/it/729-system-scope/postbuild.groovy new file mode 100644 index 000000000..335aaa589 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope/postbuild.groovy @@ -0,0 +1,30 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2017 Jeremy Long. All Rights Reserved. + */ + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import java.nio.charset.Charset; +import groovy.json.JsonSlurper; + +def slurper = new JsonSlurper() +def json = slurper.parse(new File(basedir, "target/dependency-check-report.json"), "UTF-8") + +assert json instanceof Map +assert json.analysis.dependencies instanceof List +assert json.analysis.dependencies.size()==0 +return true; diff --git a/dependency-check-maven/src/it/729-system-scope/prebuild.groovy b/dependency-check-maven/src/it/729-system-scope/prebuild.groovy new file mode 100644 index 000000000..9eff4bb5c --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope/prebuild.groovy @@ -0,0 +1,17 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2014 Jeremy Long. All Rights Reserved. + */ diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index 1a923a698..741e832a3 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -403,6 +403,13 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma @SuppressWarnings("CanBeFinal") @Parameter(property = "skipProvidedScope", defaultValue = "false", required = false) private boolean skipProvidedScope = false; + + /** + * Skip Analysis for Provided Scope Dependencies. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "skipSystemScope", defaultValue = "false", required = false) + private boolean skipSystemScope = false; /** * The data directory, hold DC SQL DB. */ @@ -631,10 +638,10 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma List nodes, ProjectBuildingRequest buildingRequest) { ExceptionCollection exCol = null; for (DependencyNode dependencyNode : nodes) { - exCol = collectDependencies(engine, project, dependencyNode.getChildren(), buildingRequest); if (excludeFromScan(dependencyNode.getArtifact().getScope())) { continue; } + exCol = collectDependencies(engine, project, dependencyNode.getChildren(), buildingRequest); try { final ArtifactCoordinate coordinate = TransferUtils.toArtifactCoordinate(dependencyNode.getArtifact()); final Artifact result = artifactResolver.resolveArtifact(buildingRequest, coordinate).getArtifact(); @@ -963,6 +970,9 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma if (skipProvidedScope && org.apache.maven.artifact.Artifact.SCOPE_PROVIDED.equals(scope)) { return true; } + if (skipSystemScope && org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(scope)) { + return true; + } return skipRuntimeScope && !org.apache.maven.artifact.Artifact.SCOPE_RUNTIME.equals(scope); } diff --git a/dependency-check-maven/src/site/markdown/configuration.md b/dependency-check-maven/src/site/markdown/configuration.md index c43075f2e..9f1518976 100644 --- a/dependency-check-maven/src/site/markdown/configuration.md +++ b/dependency-check-maven/src/site/markdown/configuration.md @@ -23,9 +23,10 @@ format | The report format to be generated (HTML, XML, VULN name | The name of the report in the site. | dependency-check or dependency-check:aggregate outputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build. | 'target' skip | Skips the dependency-check analysis. | false -skipTestScope | Skip analysis for artifacts with Test Scope. | true skipProvidedScope | Skip analysis for artifacts with Provided Scope. | false skipRuntimeScope | Skip analysis for artifacts with Runtime Scope. | false +skipSystemScope | Skip analysis for artifacts with System Scope. | false +skipTestScope | Skip analysis for artifacts with Test Scope. | true suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html). |   hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html). |   enableExperimental | Enable the [experimental analyzers](../analyzers/index.html). If not enabled the experimental analyzers (see below) will not be loaded or used. | false From e218b8ad707f451fcc668ca6a4c947676405e2d3 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 14 May 2017 07:45:55 -0400 Subject: [PATCH 30/30] added attempt to resolve system scoped dependency with test cases --- .../DependencyNotFoundException.java | 66 ++++++++++++++++++ .../invoker.properties | 19 ++++++ .../pom.xml | 0 .../postbuild.groovy | 30 +++++++++ .../729-system-scope-resolved/prebuild.groovy | 17 +++++ .../invoker.properties | 0 .../src/it/729-system-scope-skipped/pom.xml | 34 ++++++++++ .../postbuild.groovy | 0 .../prebuild.groovy | 0 .../maven/BaseDependencyCheckMojo.java | 67 +++++++++++++++++-- 10 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/DependencyNotFoundException.java create mode 100644 dependency-check-maven/src/it/729-system-scope-resolved/invoker.properties rename dependency-check-maven/src/it/{729-system-scope => 729-system-scope-resolved}/pom.xml (100%) create mode 100644 dependency-check-maven/src/it/729-system-scope-resolved/postbuild.groovy create mode 100644 dependency-check-maven/src/it/729-system-scope-resolved/prebuild.groovy rename dependency-check-maven/src/it/{729-system-scope => 729-system-scope-skipped}/invoker.properties (100%) create mode 100644 dependency-check-maven/src/it/729-system-scope-skipped/pom.xml rename dependency-check-maven/src/it/{729-system-scope => 729-system-scope-skipped}/postbuild.groovy (100%) rename dependency-check-maven/src/it/{729-system-scope => 729-system-scope-skipped}/prebuild.groovy (100%) diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/DependencyNotFoundException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/DependencyNotFoundException.java new file mode 100644 index 000000000..3406b9900 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/DependencyNotFoundException.java @@ -0,0 +1,66 @@ +/* + * 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) 2017 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.exception; + +/** + * An exception used when a dependency could not be found. + * + * @author Jeremy Long + */ +public class DependencyNotFoundException extends Exception { + + /** + * The serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Creates a new DependencyNotFoundException. + */ + public DependencyNotFoundException() { + super(); + } + + /** + * Creates a new DependencyNotFoundException. + * + * @param msg a message for the exception. + */ + public DependencyNotFoundException(String msg) { + super(msg); + } + + /** + * Creates a new DependencyNotFoundException. + * + * @param ex the cause of the exception. + */ + public DependencyNotFoundException(Throwable ex) { + super(ex); + } + + /** + * Creates a new DependencyNotFoundException. + * + * @param msg a message for the exception. + * @param ex the cause of the exception. + */ + public DependencyNotFoundException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/dependency-check-maven/src/it/729-system-scope-resolved/invoker.properties b/dependency-check-maven/src/it/729-system-scope-resolved/invoker.properties new file mode 100644 index 000000000..135fb86f1 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope-resolved/invoker.properties @@ -0,0 +1,19 @@ +# +# This file is part of dependency-check-maven. +# +# 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) 2017 Jeremy Long. All Rights Reserved. +# + +invoker.goals = install ${project.groupId}:${project.artifactId}:${project.version}:check -e -Dformat=JSON diff --git a/dependency-check-maven/src/it/729-system-scope/pom.xml b/dependency-check-maven/src/it/729-system-scope-resolved/pom.xml similarity index 100% rename from dependency-check-maven/src/it/729-system-scope/pom.xml rename to dependency-check-maven/src/it/729-system-scope-resolved/pom.xml diff --git a/dependency-check-maven/src/it/729-system-scope-resolved/postbuild.groovy b/dependency-check-maven/src/it/729-system-scope-resolved/postbuild.groovy new file mode 100644 index 000000000..c1d6476e4 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope-resolved/postbuild.groovy @@ -0,0 +1,30 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2017 Jeremy Long. All Rights Reserved. + */ + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import java.nio.charset.Charset; +import groovy.json.JsonSlurper; + +def slurper = new JsonSlurper() +def json = slurper.parse(new File(basedir, "target/dependency-check-report.json"), "UTF-8") + +assert json instanceof Map +assert json.analysis.dependencies instanceof List +assert json.analysis.dependencies.size()==1 +return true; diff --git a/dependency-check-maven/src/it/729-system-scope-resolved/prebuild.groovy b/dependency-check-maven/src/it/729-system-scope-resolved/prebuild.groovy new file mode 100644 index 000000000..9ec3a0a91 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope-resolved/prebuild.groovy @@ -0,0 +1,17 @@ +/* + * This file is part of dependency-check-maven. + * + * 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) 2017 Jeremy Long. All Rights Reserved. + */ diff --git a/dependency-check-maven/src/it/729-system-scope/invoker.properties b/dependency-check-maven/src/it/729-system-scope-skipped/invoker.properties similarity index 100% rename from dependency-check-maven/src/it/729-system-scope/invoker.properties rename to dependency-check-maven/src/it/729-system-scope-skipped/invoker.properties diff --git a/dependency-check-maven/src/it/729-system-scope-skipped/pom.xml b/dependency-check-maven/src/it/729-system-scope-skipped/pom.xml new file mode 100644 index 000000000..6f2d06950 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope-skipped/pom.xml @@ -0,0 +1,34 @@ + + + + 4.0.0 + org.owasp.test + test-system-scope + 1.0.0-SNAPSHOT + jar + + + system + com.sun + tools + 1.8 + ${java.home}/../lib/tools.jar + + + \ No newline at end of file diff --git a/dependency-check-maven/src/it/729-system-scope/postbuild.groovy b/dependency-check-maven/src/it/729-system-scope-skipped/postbuild.groovy similarity index 100% rename from dependency-check-maven/src/it/729-system-scope/postbuild.groovy rename to dependency-check-maven/src/it/729-system-scope-skipped/postbuild.groovy diff --git a/dependency-check-maven/src/it/729-system-scope/prebuild.groovy b/dependency-check-maven/src/it/729-system-scope-skipped/prebuild.groovy similarity index 100% rename from dependency-check-maven/src/it/729-system-scope/prebuild.groovy rename to dependency-check-maven/src/it/729-system-scope-skipped/prebuild.groovy diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index 741e832a3..3c1e89fd0 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -55,6 +55,7 @@ import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.DependencyNotFoundException; import org.owasp.dependencycheck.exception.ExceptionCollection; import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.reporting.ReportGenerator; @@ -643,16 +644,45 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma } exCol = collectDependencies(engine, project, dependencyNode.getChildren(), buildingRequest); try { - final ArtifactCoordinate coordinate = TransferUtils.toArtifactCoordinate(dependencyNode.getArtifact()); - final Artifact result = artifactResolver.resolveArtifact(buildingRequest, coordinate).getArtifact(); - if (result.isResolved() && result.getFile() != null) { - final List deps = engine.scan(result.getFile().getAbsoluteFile(), + boolean isResolved = false; + File artifactFile = null; + String artifactId = null; + String groupId = null; + String version = null; + if (org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dependencyNode.getArtifact().getScope())) { + for (org.apache.maven.model.Dependency d : project.getDependencies()) { + Artifact a = dependencyNode.getArtifact(); + if (d.getSystemPath() != null && artifactsMatch(d, a)) { + + artifactFile = new File(d.getSystemPath()); + isResolved = artifactFile.isFile(); + groupId = a.getGroupId(); + artifactId = a.getArtifactId(); + version = a.getVersion(); + break; + } + } + if (!isResolved) { + getLog().error("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString()); + exCol.addException(new DependencyNotFoundException("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString())); + } + } else { + final ArtifactCoordinate coordinate = TransferUtils.toArtifactCoordinate(dependencyNode.getArtifact()); + final Artifact result = artifactResolver.resolveArtifact(buildingRequest, coordinate).getArtifact(); + isResolved = result.isResolved(); + artifactFile = result.getFile(); + groupId = result.getGroupId(); + artifactId = result.getArtifactId(); + version = result.getVersion(); + } + if (isResolved && artifactFile != null) { + final List deps = engine.scan(artifactFile.getAbsoluteFile(), project.getName() + ":" + dependencyNode.getArtifact().getScope()); if (deps != null) { if (deps.size() == 1) { final Dependency d = deps.get(0); if (d != null) { - final MavenArtifact ma = new MavenArtifact(result.getGroupId(), result.getArtifactId(), result.getVersion()); + final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version); d.addAsEvidence("pom", ma, Confidence.HIGHEST); if (getLog().isDebugEnabled()) { getLog().debug(String.format("Adding project reference %s on dependency %s", @@ -690,6 +720,33 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma return exCol; } + /** + * Determines if the groupId, artifactId, and version of the Maven + * dependency and artifact match. + * + * @param d the Maven dependency + * @param a the Maven artifact + * @return true if the groupId, artifactId, and version match + */ + private static boolean artifactsMatch(org.apache.maven.model.Dependency d, Artifact a) { + return (isEqualOrNull(a.getArtifactId(), d.getArtifactId())) + && (isEqualOrNull(a.getGroupId(), d.getGroupId())) + && (isEqualOrNull(a.getVersion(), d.getVersion())); + } + + /** + * Compares two strings for equality; if both strings are null they are + * considered equal. + * + * @param left the first string to compare + * @param right the second string to compare + * @return true if the strings are equal or if they are both null; otherwise + * false. + */ + private static boolean isEqualOrNull(String left, String right) { + return (left != null && left.equals(right)) || (left == null && right == null); + } + /** * @return Returns a new ProjectBuildingRequest populated from the current * session and the current project remote repositories, used to resolve