From 9da95e592cfbc70a0578af3eeac9662f1c33ce58 Mon Sep 17 00:00:00 2001
From: stevespringett
Date: Wed, 26 Apr 2017 00:40:15 -0500
Subject: [PATCH] Added NSP Analyzer Support
---
.../dependencycheck/analyzer/NspAnalyzer.java | 329 +++++++++++++++++
.../dependencycheck/data/nsp/Advisory.java | 344 ++++++++++++++++++
.../dependencycheck/data/nsp/NspSearch.java | 144 ++++++++
.../data/nsp/SanitizePackage.java | 71 ++++
.../data/nsp/package-info.java | 7 +
.../dependency/Dependency.java | 19 +
.../dependency/Vulnerability.java | 26 ++
...rg.owasp.dependencycheck.analyzer.Analyzer | 1 +
.../main/resources/dependencycheck.properties | 4 +
.../main/resources/templates/HtmlReport.vsl | 68 +++-
.../templates/VulnerabilityReport.vsl | 19 +-
.../main/resources/templates/XmlReport.vsl | 27 +-
.../data/nsp/NspSearchTest.java | 73 ++++
.../data/nsp/SanitizePackageTest.java | 65 ++++
.../test/resources/dependencycheck.properties | 4 +
.../src/test/resources/nsp/package.json | 59 +++
.../owasp/dependencycheck/utils/Settings.java | 8 +
17 files changed, 1233 insertions(+), 35 deletions(-)
create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NspAnalyzer.java
create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/Advisory.java
create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/NspSearch.java
create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/SanitizePackage.java
create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/package-info.java
create mode 100644 dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nsp/NspSearchTest.java
create mode 100644 dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nsp/SanitizePackageTest.java
create mode 100644 dependency-check-core/src/test/resources/nsp/package.json
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NspAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NspAnalyzer.java
new file mode 100644
index 000000000..dd1b01bd6
--- /dev/null
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NspAnalyzer.java
@@ -0,0 +1,329 @@
+/*
+ * 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.analyzer;
+
+import org.apache.commons.io.FileUtils;
+import org.owasp.dependencycheck.Engine;
+import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
+import org.owasp.dependencycheck.data.nsp.Advisory;
+import org.owasp.dependencycheck.data.nsp.NspSearch;
+import org.owasp.dependencycheck.data.nsp.SanitizePackage;
+import org.owasp.dependencycheck.dependency.Confidence;
+import org.owasp.dependencycheck.dependency.Dependency;
+import org.owasp.dependencycheck.dependency.EvidenceCollection;
+import org.owasp.dependencycheck.dependency.Identifier;
+import org.owasp.dependencycheck.dependency.Vulnerability;
+import org.owasp.dependencycheck.dependency.VulnerableSoftware;
+import org.owasp.dependencycheck.utils.FileFilterBuilder;
+import org.owasp.dependencycheck.utils.Settings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import javax.json.Json;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonReader;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import org.owasp.dependencycheck.exception.InitializationException;
+
+/**
+ * Used to analyze Node Package Manager (npm) package.json files via
+ * Node Security Platform (nsp).
+ *
+ * @author Steve Springett
+ */
+public class NspAnalyzer extends AbstractFileTypeAnalyzer {
+
+ /**
+ * The logger.
+ */
+ private static final Logger LOGGER = LoggerFactory.getLogger(NspAnalyzer.class);
+
+ /**
+ * The default URL to the NSP check API.
+ */
+ public static final String DEFAULT_URL = "https://api.nodesecurity.io/check";
+
+ /**
+ * The file name to scan.
+ */
+ private static final String PACKAGE_JSON = "package.json";
+
+ /**
+ * Filter that detects files named "package.json".
+ */
+ private static final FileFilter PACKAGE_JSON_FILTER = FileFilterBuilder.newInstance()
+ .addFilenames(PACKAGE_JSON).build();
+
+ /**
+ * The NSP Searcher.
+ */
+ private NspSearch searcher;
+
+ /**
+ * Returns the FileFilter
+ *
+ * @return the FileFilter
+ */
+ @Override
+ protected FileFilter getFileFilter() {
+ return PACKAGE_JSON_FILTER;
+ }
+
+ /**
+ * Initializes the analyzer once before any analysis is performed.
+ *
+ * @throws InitializationException if there's an error during initialization
+ */
+ @Override
+ public void initializeFileTypeAnalyzer() throws InitializationException {
+ LOGGER.debug("Initializing " + getName());
+ final String searchUrl = Settings.getString(Settings.KEYS.ANALYZER_NSP_URL, DEFAULT_URL);
+ try {
+ searcher = new NspSearch(new URL(searchUrl));
+ } catch (MalformedURLException ex) {
+ setEnabled(false);
+ throw new InitializationException("The configured URL to Node Security Platform is malformed: " + searchUrl, ex);
+ }
+ }
+
+ /**
+ * Returns the name of the analyzer.
+ *
+ * @return the name of the analyzer.
+ */
+ @Override
+ public String getName() {
+ return "Node Security Platform Analyzer";
+ }
+
+ /**
+ * Returns the phase that the analyzer is intended to run in.
+ *
+ * @return the phase that the analyzer is intended to run in.
+ */
+ @Override
+ public AnalysisPhase getAnalysisPhase() {
+ return AnalysisPhase.FINDING_ANALYSIS;
+ }
+
+ /**
+ * Returns the key used in the properties file to reference the analyzer's
+ * enabled property.x
+ *
+ * @return the analyzer's enabled property setting key
+ */
+ @Override
+ protected String getAnalyzerEnabledSettingKey() {
+ return Settings.KEYS.ANALYZER_NSP_PACKAGE_ENABLED;
+ }
+
+ @Override
+ protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
+ final File file = dependency.getActualFile();
+ try (JsonReader jsonReader = Json.createReader(FileUtils.openInputStream(file))) {
+
+ // Retrieves the contents of package.json from the Dependency
+ final JsonObject packageJson = jsonReader.readObject();
+
+ // Create a sanitized version of the package.json
+ final JsonObject sanitizedJson = SanitizePackage.sanitize(packageJson);
+
+ // Create a new 'package' object that acts as a container for the sanitized package.json
+ final JsonObjectBuilder builder = Json.createObjectBuilder();
+ final JsonObject nspPayload = builder.add("package", sanitizedJson).build();
+
+ // Submits the package payload to the nsp check service
+ final List advisories = searcher.submitPackage(nspPayload);
+
+ for (Advisory advisory: advisories) {
+ /*
+ * Create a new vulnerability out of the advisory returned by nsp.
+ */
+ final Vulnerability vuln = new Vulnerability();
+ vuln.setCvssScore(advisory.getCvssScore());
+ vuln.setDescription(advisory.getOverview());
+ vuln.setName(String.valueOf(advisory.getId()));
+ vuln.setSource(Vulnerability.Source.NSP);
+ vuln.addReference(
+ "NSP",
+ "Advisory " + advisory.getId() + ": " + advisory.getTitle(),
+ advisory.getAdvisory()
+ );
+
+ /*
+ * Create a single vulnerable software object - these do not use CPEs unlike the NVD.
+ */
+ final VulnerableSoftware vs = new VulnerableSoftware();
+ //vs.setVersion(advisory.getVulnerableVersions());
+ vs.setUpdate(advisory.getPatchedVersions());
+ vs.setName(advisory.getModule() + ":" + advisory.getVulnerableVersions());
+ vuln.setVulnerableSoftware(new HashSet<>(Arrays.asList(vs)));
+
+ // Add the vulnerability to package.json
+ dependency.getVulnerabilities().add(vuln);
+ }
+
+ /*
+ * Adds evidence about the node package itself, not any of the modules.
+ */
+ final EvidenceCollection productEvidence = dependency.getProductEvidence();
+ final EvidenceCollection vendorEvidence = dependency.getVendorEvidence();
+ if (packageJson.containsKey("name")) {
+ final Object value = packageJson.get("name");
+ if (value instanceof JsonString) {
+ final String valueString = ((JsonString) value).getString();
+ productEvidence.addEvidence(PACKAGE_JSON, "name", valueString, Confidence.HIGHEST);
+ vendorEvidence.addEvidence(PACKAGE_JSON, "name_project", String.format("%s_project", valueString), Confidence.LOW);
+ } else {
+ LOGGER.warn("JSON value not string as expected: {}", value);
+ }
+ }
+
+ /*
+ * Processes the dependencies objects in package.json and adds all the modules as related dependencies
+ */
+ if (packageJson.containsKey("dependencies")) {
+ final JsonObject dependencies = packageJson.getJsonObject("dependencies");
+ processPackage(dependency, dependencies, "dependencies");
+ }
+ if (packageJson.containsKey("devDependencies")) {
+ final JsonObject dependencies = packageJson.getJsonObject("devDependencies");
+ processPackage(dependency, dependencies, "devDependencies");
+ }
+ if (packageJson.containsKey("optionalDependencies")) {
+ final JsonObject dependencies = packageJson.getJsonObject("optionalDependencies");
+ processPackage(dependency, dependencies, "optionalDependencies");
+ }
+ if (packageJson.containsKey("peerDependencies")) {
+ final JsonObject dependencies = packageJson.getJsonObject("peerDependencies");
+ processPackage(dependency, dependencies, "peerDependencies");
+ }
+ if (packageJson.containsKey("bundleDependencies")) {
+ final JsonObject dependencies = packageJson.getJsonObject("bundleDependencies");
+ processPackage(dependency, dependencies, "bundleDependencies");
+ }
+ if (packageJson.containsKey("bundledDependencies")) {
+ final JsonObject dependencies = packageJson.getJsonObject("bundledDependencies");
+ processPackage(dependency, dependencies, "bundledDependencies");
+ }
+
+ /*
+ * Adds the license if defined in package.json
+ */
+ if (packageJson.containsKey("license")) {
+ dependency.setLicense(packageJson.getString("license"));
+ }
+
+ /*
+ * Adds general evidence to about the package.
+ */
+ addToEvidence(packageJson, productEvidence, "description");
+ addToEvidence(packageJson, vendorEvidence, "author");
+ addToEvidence(packageJson, dependency.getVersionEvidence(), "version");
+ dependency.setDisplayFileName(String.format("%s/%s", file.getParentFile().getName(), file.getName()));
+
+ } catch (IOException e) {
+ LOGGER.debug("Error reading dependency or connecting to Node Security Platform /check API", e);
+ } catch (JsonException e) {
+ LOGGER.warn("Failed to parse package.json file.", e);
+ }
+ }
+
+ /**
+ * Processes a part of package.json (as defined by JsobObject) and
+ * update the specified dependency with relevant info.
+ *
+ * @param dependency the Dependency to update
+ * @param jsonObject the jsonObject to parse
+ */
+ private void processPackage(Dependency dependency, JsonObject jsonObject, String depType) {
+ for (int i=0; i entry : jsonObject.entrySet()) {
+ /*
+ * Create identifies that include the npm module and version. Since these are defined,
+ * assign the highest confidence.
+ */
+ final Identifier moduleName = new Identifier("npm", "Module", null, entry.getKey());
+ moduleName.setConfidence(Confidence.HIGHEST);
+ String version = "";
+ if (entry.getValue() != null && entry.getValue().getValueType() == JsonValue.ValueType.STRING) {
+ version = ((JsonString)entry.getValue()).getString();
+ }
+ final Identifier moduleVersion = new Identifier("npm", "Version", null, version);
+ moduleVersion.setConfidence(Confidence.HIGHEST);
+
+ final Identifier moduleDepType = new Identifier("npm", "Scope", null, depType);
+ moduleVersion.setConfidence(Confidence.HIGHEST);
+
+ /*
+ * Create related dependencies for each module defined in package.json. The path to the related
+ * dependency will not actually exist but needs to be unique (due to the use of Set in Dependency).
+ * The use of related dependencies is a way to specify the actual software BOM in package.json.
+ */
+ Dependency nodeModule = new Dependency(new File(dependency.getActualFile() + "#" + entry.getKey()), true);
+ nodeModule.setDisplayFileName(entry.getKey());
+ nodeModule.setIdentifiers(new HashSet<>(Arrays.asList(moduleName, moduleVersion, moduleDepType)));
+ dependency.addRelatedDependency(nodeModule);
+ }
+ }
+ }
+
+ /**
+ * Adds information to an evidence collection from the node json
+ * configuration.
+ *
+ * @param json information from node.js
+ * @param collection a set of evidence about a dependency
+ * @param key the key to obtain the data from the json information
+ */
+ private void addToEvidence(JsonObject json, EvidenceCollection collection, String key) {
+ if (json.containsKey(key)) {
+ final JsonValue value = json.get(key);
+ if (value instanceof JsonString) {
+ collection.addEvidence(PACKAGE_JSON, key, ((JsonString) value).getString(), Confidence.HIGHEST);
+ } else if (value instanceof JsonObject) {
+ final JsonObject jsonObject = (JsonObject) value;
+ for (final Map.Entry entry : jsonObject.entrySet()) {
+ final String property = entry.getKey();
+ final JsonValue subValue = entry.getValue();
+ if (subValue instanceof JsonString) {
+ collection.addEvidence(PACKAGE_JSON,
+ String.format("%s.%s", key, property),
+ ((JsonString) subValue).getString(),
+ Confidence.HIGHEST);
+ } else {
+ LOGGER.warn("JSON sub-value not string as expected: {}", subValue);
+ }
+ }
+ } else {
+ LOGGER.warn("JSON value not string or JSON object as expected: {}", value);
+ }
+ }
+ }
+}
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/Advisory.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/Advisory.java
new file mode 100644
index 000000000..adce5e334
--- /dev/null
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/Advisory.java
@@ -0,0 +1,344 @@
+/*
+ * 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.data.nsp;
+
+/**
+ * The response from NSP check API will respond with 0 or more advisories.
+ * This class defines the Advisory objects returned.
+ *
+ * @author Steve Springett
+ */
+public class Advisory {
+
+ /**
+ * The unique ID of the advisory as issued by Node Security Platform.
+ */
+ private int id;
+
+ /**
+ * The timestamp of the last update to the advisory.
+ */
+ private String updatedAt;
+
+ /**
+ * The timestamp of which the advisory was created.
+ */
+ private String createdAt;
+
+ /**
+ * The timestamp of when the advisory was published.
+ */
+ private String publishDate;
+
+ /**
+ * A detailed description of the advisory.
+ */
+ private String overview;
+
+ /**
+ * Recommendations for mitigation. Typically involves updating to a newer release.
+ */
+ private String recommendation;
+
+ /**
+ * The CVSS vector used to calculate the score.
+ */
+ private String cvssVector;
+
+ /**
+ * The CVSS score.
+ */
+ private float cvssScore;
+
+ /**
+ * The name of the Node module the advisory is for.
+ */
+ private String module;
+
+ /**
+ * The version of the Node module the advisory is for.
+ */
+ private String version;
+
+ /**
+ * A string representation of the versions containing the vulnerability.
+ */
+ private String vulnerableVersions;
+
+ /**
+ * A string representation of the versions that have been patched.
+ */
+ private String patchedVersions;
+
+ /**
+ * The title/name of the advisory.
+ */
+ private String title;
+
+ /**
+ * The linear dependency path that lead to this module.
+ * [0] is the root with each subsequent array member leading up to the
+ * final (this) module.
+ */
+ private String[] path;
+
+ /**
+ * The URL to the advisory.
+ */
+ private String advisory;
+
+ /**
+ * Returns the unique ID of the advisory as issued by Node Security Platform.
+ * @return a unique ID
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Sets the unique ID of the advisory as issued by Node Security Platform.
+ * @param id a unique ID
+ */
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the timestamp of the last update to the advisory.
+ * @return a timestamp
+ */
+ public String getUpdatedAt() {
+ return updatedAt;
+ }
+
+ /**
+ * Sets the timestamp of the last update to the advisory.
+ * @param updatedAt a timestamp
+ */
+ public void setUpdatedAt(String updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ /**
+ * Returns the timestamp of which the advisory was created.
+ * @return a timestamp
+ */
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ /**
+ * Sets the timestamp of which the advisory was created.
+ * @param createdAt a timestamp
+ */
+ public void setCreatedAt(String createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ /**
+ * Returns the timestamp of when the advisory was published.
+ * @return a timestamp
+ */
+ public String getPublishDate() {
+ return publishDate;
+ }
+
+ /**
+ * Sets the timestamp of when the advisory was published.
+ * @param publishDate a timestamp
+ */
+ public void setPublishDate(String publishDate) {
+ this.publishDate = publishDate;
+ }
+
+ /**
+ * Returns a detailed description of the advisory.
+ * @return the overview
+ */
+ public String getOverview() {
+ return overview;
+ }
+
+ /**
+ * Sets the detailed description of the advisory.
+ * @param overview the overview
+ */
+ public void setOverview(String overview) {
+ this.overview = overview;
+ }
+
+ /**
+ * Returns recommendations for mitigation. Typically involves updating to a newer release.
+ * @return recommendations
+ */
+ public String getRecommendation() {
+ return recommendation;
+ }
+
+ /**
+ * Sets recommendations for mitigation. Typically involves updating to a newer release.
+ * @param recommendation recommendations
+ */
+ public void setRecommendation(String recommendation) {
+ this.recommendation = recommendation;
+ }
+
+ /**
+ * Returns the CVSS vector used to calculate the score.
+ * @return the CVSS vector
+ */
+ public String getCvssVector() {
+ return cvssVector;
+ }
+
+ /**
+ * Sets the CVSS vector used to calculate the score.
+ * @param cvssVector the CVSS vector
+ */
+ public void setCvssVector(String cvssVector) {
+ this.cvssVector = cvssVector;
+ }
+
+ /**
+ * Returns the CVSS score.
+ * @return the CVSS score
+ */
+ public float getCvssScore() {
+ return cvssScore;
+ }
+
+ /**
+ * Sets the CVSS score.
+ * @param cvssScore the CVSS score
+ */
+ public void setCvssScore(float cvssScore) {
+ this.cvssScore = cvssScore;
+ }
+
+ /**
+ * Returns the name of the Node module the advisory is for.
+ * @return the name of the module
+ */
+ public String getModule() {
+ return module;
+ }
+
+ /**
+ * Sets the name of the Node module the advisory is for.
+ * @param module the name of the4 module
+ */
+ public void setModule(String module) {
+ this.module = module;
+ }
+
+ /**
+ * Returns the version of the Node module the advisory is for.
+ * @return the module version
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the version of the Node module the advisory is for.
+ * @param version the module version
+ */
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ /**
+ * Returns a string representation of the versions containing the vulnerability.
+ * @return the affected versions
+ */
+ public String getVulnerableVersions() {
+ return vulnerableVersions;
+ }
+
+ /**
+ * Sets the string representation of the versions containing the vulnerability.
+ * @param vulnerableVersions the affected versions
+ */
+ public void setVulnerableVersions(String vulnerableVersions) {
+ this.vulnerableVersions = vulnerableVersions;
+ }
+
+ /**
+ * Returns a string representation of the versions that have been patched.
+ * @return the patched versions
+ */
+ public String getPatchedVersions() {
+ return patchedVersions;
+ }
+
+ /**
+ * Sets the string representation of the versions that have been patched.
+ * @param patchedVersions the patched versions
+ */
+ public void setPatchedVersions(String patchedVersions) {
+ this.patchedVersions = patchedVersions;
+ }
+
+ /**
+ * Returns the title/name of the advisory.
+ * @return the title/name of the advisory
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Sets the title/name of the advisory.
+ * @param title the title/name of the advisory
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ /**
+ * Returns the linear dependency path that lead to this module.
+ * @return the dependency path
+ */
+ public String[] getPath() {
+ return path;
+ }
+
+ /**
+ * Sets the linear dependency path that lead to this module.
+ * @param path the dependency path
+ */
+ public void setPath(String[] path) {
+ this.path = path;
+ }
+
+ /**
+ * Returns the URL to the advisory.
+ * @return the advisory URL
+ */
+ public String getAdvisory() {
+ return advisory;
+ }
+
+ /**
+ * Sets the URL to the advisory.
+ * @param advisory the advisory URL
+ */
+ public void setAdvisory(String advisory) {
+ this.advisory = advisory;
+ }
+}
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/NspSearch.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/NspSearch.java
new file mode 100644
index 000000000..b285dab79
--- /dev/null
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/NspSearch.java
@@ -0,0 +1,144 @@
+/*
+ * 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.data.nsp;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import org.owasp.dependencycheck.utils.Settings;
+import org.owasp.dependencycheck.utils.URLConnectionFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
+/**
+ * Class of methods to search via Node Security Platform.
+ *
+ * @author Steve Springett
+ */
+public class NspSearch {
+
+ /**
+ * The URL for the public NSP check API.
+ */
+ private final URL nspCheckUrl;
+
+ /**
+ * Whether to use the Proxy when making requests.
+ */
+ private final boolean useProxy;
+
+ /**
+ * Used for logging.
+ */
+ private static final Logger LOGGER = LoggerFactory.getLogger(NspSearch.class);
+
+ /**
+ * Creates a NspSearch for the given repository URL.
+ *
+ * @param nspCheckUrl the URL to the public NSP check API
+ */
+ public NspSearch(URL nspCheckUrl) {
+ this.nspCheckUrl = nspCheckUrl;
+ if (null != Settings.getString(Settings.KEYS.PROXY_SERVER)) {
+ useProxy = true;
+ LOGGER.debug("Using proxy");
+ } else {
+ useProxy = false;
+ LOGGER.debug("Not using proxy");
+ }
+ }
+
+ /**
+ * Submits the package.json file to the NSP public /check API and returns
+ * a list of zero or more Advisories.
+ *
+ * @param packageJson the package.json file retrieved from the Dependency
+ * @return a List of zero or more Advisory object
+ * @throws IOException if it's unable to connect to Node Security Platform
+ */
+ public List submitPackage(JsonObject packageJson) throws IOException {
+ List result = new ArrayList<>();
+ byte[] packageDatabytes = packageJson.toString().getBytes(StandardCharsets.UTF_8);
+
+ final HttpURLConnection conn = URLConnectionFactory.createHttpURLConnection(nspCheckUrl, useProxy);
+ conn.setDoOutput(true);
+ conn.setDoInput(true);
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("X-NSP-VERSION", "2.6.2");
+ conn.setRequestProperty("Content-Type", "application/json");
+ conn.setRequestProperty("Content-Length", Integer.toString(packageDatabytes.length));
+ conn.connect();
+
+ try (OutputStream os = new BufferedOutputStream(conn.getOutputStream())) {
+ os.write(packageDatabytes);
+ os.flush();
+ }
+
+ if (conn.getResponseCode() == 200) {
+ try (InputStream in = new BufferedInputStream(conn.getInputStream())) {
+ JsonReader jsonReader = Json.createReader(in);
+ JsonArray array = jsonReader.readArray();
+ if (array != null) {
+ for (int i=0; i stringPath = new ArrayList<>();
+ for (int j=0; j WHITELIST = new ArrayList<>(Arrays.asList(
+ "name",
+ "version",
+ "engine",
+ "dependencies",
+ "devDependencies",
+ "optionalDependencies",
+ "peerDependencies",
+ "bundleDependencies",
+ "bundledDependencies"
+ ));
+
+ /**
+ * The NSP API only accepts a subset of objects typically found in package.json.
+ * This method accepts a JsonObject of a raw package.json file and returns a
+ * new 'sanitized' version based on a pre-defined whitelist of allowable object
+ * NSP accepts.
+ *
+ * @param rawPackage a raw package.json file
+ * @return a sanitized version of the package.json file
+ */
+ public static JsonObject sanitize(JsonObject rawPackage) {
+ JsonObjectBuilder builder = Json.createObjectBuilder();
+ for (Map.Entry entry: rawPackage.entrySet()) {
+ if (WHITELIST.contains(entry.getKey())) {
+ builder.add(entry.getKey(), entry.getValue());
+ }
+ }
+ return builder.build();
+ }
+
+}
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/package-info.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/package-info.java
new file mode 100644
index 000000000..fc0193536
--- /dev/null
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/package-info.java
@@ -0,0 +1,7 @@
+/**
+ *
+ * Contains classes related to searching Node Security Platform (nsp).
+ *
+ * These are used to abstract NSP searching away from OWASP Dependency Check so they can be reused elsewhere.
+ */
+package org.owasp.dependencycheck.data.nsp;
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
index 9d7662ebb..08705b62d 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
@@ -138,6 +138,11 @@ public class Dependency implements Serializable, Comparable {
*/
private List availableVersions = new ArrayList<>();
+ /**
+ * Defines an actual or virtual dependency.
+ */
+ private boolean isVirtual = false;
+
/**
* Returns the package path.
*
@@ -175,7 +180,18 @@ public class Dependency implements Serializable, Comparable {
* @param file the File to create the dependency object from.
*/
public Dependency(File file) {
+ this(file, false);
+ }
+
+ /**
+ * Constructs a new Dependency object.
+ *
+ * @param file the File to create the dependency object from.
+ * @param isVirtual specifies if the dependency is virtual indicating the file doesn't actually exist.
+ */
+ public Dependency(File file, boolean isVirtual) {
this();
+ this.isVirtual = isVirtual;
this.actualFilePath = file.getAbsolutePath();
this.filePath = this.actualFilePath;
this.fileName = file.getName();
@@ -591,6 +607,9 @@ public class Dependency implements Serializable, Comparable {
private void determineHashes(File file) {
String md5 = null;
String sha1 = null;
+ if (isVirtual) {
+ return;
+ }
try {
md5 = Checksum.getMD5Checksum(file);
sha1 = Checksum.getSHA1Checksum(file);
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java
index 8d14cd9b8..98ea466b2 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java
@@ -32,6 +32,11 @@ import org.apache.commons.lang3.builder.CompareToBuilder;
*/
public class Vulnerability implements Serializable, Comparable {
+ public enum Source {
+ NVD, // National Vulnerability Database
+ NSP // Node Security Platform
+ }
+
/**
* The serial version uid.
*/
@@ -100,6 +105,11 @@ public class Vulnerability implements Serializable, Comparable {
*/
private String notes;
+ /**
+ * The source that identified the vulnerability.
+ */
+ private Source source = Source.NVD;
+
/**
* Get the value of name.
*
@@ -516,4 +526,20 @@ public class Vulnerability implements Serializable, Comparable {
public boolean hasMatchedAllPreviousCPE() {
return matchedAllPreviousCPE != null;
}
+
+ /**
+ * Retruns the source that identified the vulnerability.
+ * @return the source
+ */
+ public Source getSource() {
+ return source;
+ }
+
+ /**
+ * Sets the source that identified the vulnerability.
+ * @param source the source
+ */
+ public void setSource(Source source) {
+ this.source = source;
+ }
}
diff --git a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer
index f9bb4b811..7c657572c 100644
--- a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer
+++ b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer
@@ -19,6 +19,7 @@ org.owasp.dependencycheck.analyzer.AutoconfAnalyzer
org.owasp.dependencycheck.analyzer.OpenSSLAnalyzer
org.owasp.dependencycheck.analyzer.CMakeAnalyzer
org.owasp.dependencycheck.analyzer.NodePackageAnalyzer
+org.owasp.dependencycheck.analyzer.NspAnalyzer
org.owasp.dependencycheck.analyzer.RubyGemspecAnalyzer
org.owasp.dependencycheck.analyzer.RubyBundlerAnalyzer
org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer
diff --git a/dependency-check-core/src/main/resources/dependencycheck.properties b/dependency-check-core/src/main/resources/dependencycheck.properties
index bf2797f85..a1da6e2ee 100644
--- a/dependency-check-core/src/main/resources/dependencycheck.properties
+++ b/dependency-check-core/src/main/resources/dependencycheck.properties
@@ -76,6 +76,9 @@ analyzer.nexus.proxy=true
analyzer.central.enabled=true
analyzer.central.url=https://search.maven.org/solrsearch/select
+# the URL for searching api.nodesecurity.io
+analyzer.nsp.url=https://api.nodesecurity.io/check
+
# the number of nested archives that will be searched.
archive.scan.depth=3
@@ -87,6 +90,7 @@ analyzer.experimental.enabled=false
analyzer.jar.enabled=true
analyzer.archive.enabled=true
analyzer.node.package.enabled=true
+analyzer.nsp.package.enabled=true
analyzer.composer.lock.enabled=true
analyzer.python.distribution.enabled=true
analyzer.python.package.enabled=true
diff --git a/dependency-check-core/src/main/resources/templates/HtmlReport.vsl b/dependency-check-core/src/main/resources/templates/HtmlReport.vsl
index 8283a443a..182698ef6 100644
--- a/dependency-check-core/src/main/resources/templates/HtmlReport.vsl
+++ b/dependency-check-core/src/main/resources/templates/HtmlReport.vsl
@@ -759,8 +759,8 @@ Getting Help: $enc.html($related.DisplayFileName)