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) @@ -833,7 +836,11 @@ Getting Help: #foreach($vuln in $dependency.getVulnerabilities()) #set($vsctr=$vsctr+1) -

$enc.html($vuln.name)  

+ #if($vuln.getSource().name().equals("NVD")) +

$enc.html($vuln.name)  

+ #elseif($vuln.getSource().name().equals("NSP")) +

NSP-$enc.html($vuln.name)

+ #end

Severity: #if ($vuln.cvssScore<4.0) Low @@ -842,7 +849,11 @@ Getting Help: CVSS Score: $vuln.cvssScore (AV:$enc.html($vuln.cvssAccessVector.substring(0,1))/AC:$enc.html($vuln.cvssAccessComplexity.substring(0,1))/Au:$enc.html($vuln.cvssAuthentication.substring(0,1))/C:$enc.html($vuln.cvssConfidentialityImpact.substring(0,1))/I:$enc.html($vuln.cvssIntegrityImpact.substring(0,1))/A:$enc.html($vuln.cvssAvailabilityImpact.substring(0,1))) +
CVSS Score: $vuln.cvssScore + #if ($vuln.getSource().name().equals("NVD")) + + (AV:$enc.html($vuln.cvssAccessVector.substring(0,1))/AC:$enc.html($vuln.cvssAccessComplexity.substring(0,1))/Au:$enc.html($vuln.cvssAuthentication.substring(0,1))/C:$enc.html($vuln.cvssConfidentialityImpact.substring(0,1))/I:$enc.html($vuln.cvssIntegrityImpact.substring(0,1))/A:$enc.html($vuln.cvssAvailabilityImpact.substring(0,1))) + #end #if ($vuln.cwe)
CWE: $vuln.cwe #end @@ -859,18 +870,28 @@ Getting Help:
- #if ($vuln.getVulnerableSoftware().size()<2) -

Vulnerable Software & Versions:

- #else -

Vulnerable Software & Versions: (show all)

    -
  • $enc.html($vuln.matchedCPE) #if($vuln.hasMatchedAllPreviousCPE()) and all previous versions#end
  • -
  • ...
  • - #foreach($vs in $vuln.getVulnerableSoftware(true)) - + #if ($vuln.getSource().name().equals("NVD")) + #if ($vuln.getVulnerableSoftware().size()<2) +

    Vulnerable Software & Versions:

    + #else +

    Vulnerable Software & Versions: (show all)

      +
    • $enc.html($vuln.matchedCPE) #if($vuln.hasMatchedAllPreviousCPE()) and all previous versions#end
    • +
    • ...
    • + #foreach($vs in $vuln.getVulnerableSoftware(true)) + + #end +

    #end -

+ #elseif ($vuln.getSource().name().equals("NSP")) +

Vulnerable Software & Versions: +

    + #foreach($vs in $vuln.getVulnerableSoftware()) +
  • $enc.html($vs.name)
  • + #end +
+

#end #end @@ -925,8 +946,8 @@ Getting Help: $enc.html($related.DisplayFileName)
  • File Path: $enc.html($related.FilePath)
  • -
  • SHA1: $enc.html($related.Sha1sum)
  • -
  • MD5: $enc.html($related.Md5sum)
  • +
  • SHA1: #if($related.Sha1sum)$enc.html($related.Sha1sum)#end
  • +
  • MD5: #if($related.Md5sum)$enc.html($related.Md5sum)#end
#end @@ -978,7 +999,11 @@ Getting Help:
#foreach($vuln in $dependency.getSuppressedVulnerabilities()) #set($vsctr=$vsctr+1) -

$enc.html($vuln.name)  suppressed

+ #if($vuln.getSource().name().equals("NVD")) +

$enc.html($vuln.name)  suppressed

+ #elseif($vuln.getSource().name().equals("NSP")) +

NSP-$enc.html($vuln.name)  suppressed

+ #end

Severity: #if ($vuln.cvssScore<4.0) Low @@ -1027,6 +1052,11 @@ Getting Help: -

+
+

+ This report contains data retrieved from the National Vulnerability Database. +
+ This report may contain data retrieved from the Node Security Platform. +
diff --git a/dependency-check-core/src/main/resources/templates/VulnerabilityReport.vsl b/dependency-check-core/src/main/resources/templates/VulnerabilityReport.vsl index f0ee4806f..f3b23e41c 100644 --- a/dependency-check-core/src/main/resources/templates/VulnerabilityReport.vsl +++ b/dependency-check-core/src/main/resources/templates/VulnerabilityReport.vsl @@ -131,7 +131,7 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. table.lined tr:nth-child(even) { background-color: #fbfbfb; } - th.cve { + th.name { width: 60px; text-align: left; cursor: pointer; @@ -200,7 +200,7 @@ have been reported. Additionally, the HTML report provides many features not fou #set($cnt=0) - + @@ -210,7 +210,13 @@ have been reported. Additionally, the HTML report provides many features not fou #if($dependency.getVulnerabilities().size()>0) #foreach($vuln in $dependency.getVulnerabilities()) - +
CVENAME CWE Severity (CVSS) Dependency
$enc.html($vuln.name) + #if($vuln.getSource().name().equals("NVD")) + $enc.html($vuln.name) + #elseif($vuln.getSource().name().equals("NSP")) + NSP-$enc.html($vuln.name) + #end + #if ($vuln.cwe) $vuln.cwe @@ -241,6 +247,11 @@ have been reported. Additionally, the HTML report provides many features not fou
-



This report contains data retrieved from the National Vulnerability Database.

+

+

+ This report contains data retrieved from the National Vulnerability Database. +
+ This report may contain data retrieved from the Node Security Platform. +

diff --git a/dependency-check-core/src/main/resources/templates/XmlReport.vsl b/dependency-check-core/src/main/resources/templates/XmlReport.vsl index 143826c57..9b34f59c2 100644 --- a/dependency-check-core/src/main/resources/templates/XmlReport.vsl +++ b/dependency-check-core/src/main/resources/templates/XmlReport.vsl @@ -32,7 +32,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. $enc.xml($applicationName) $scanDateXML - This report contains data retrieved from the National Vulnerability Database: http://nvd.nist.gov + This report contains data retrieved from the National Vulnerability Database: https://nvd.nist.gov and from the Node Security Platform: https://nodesecurity.io #foreach($dependency in $dependencies) @@ -52,15 +52,18 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. #foreach($related in $dependency.getRelatedDependencies()) $enc.xml($related.FilePath) - $enc.xml($related.Sha1sum) - $enc.xml($related.Md5sum) + #if($related.Sha1sum)$enc.xml($related.Sha1sum)#end + #if($related.Md5sum)$enc.xml($related.Md5sum)#end #foreach($id in $related.getIdentifiers()) -#if ($id.type=="maven") +#if ($id.type=="maven" || $id.type=="npm") - ($id.value) + $enc.xml($id.value) #if( $id.url ) $enc.xml($id.url) #end +#if( $id.description ) + $enc.xml($id.description) +#end #if ($id.notes) $enc.xml($id.notes) #end @@ -130,14 +133,14 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. #foreach($vuln in $dependency.getVulnerabilities()) - $enc.xml($vuln.name) + #if($vuln.getSource().name().equals("NSP"))NSP-#end$enc.xml($vuln.name) $vuln.cvssScore - $enc.xml($vuln.cvssAccessVector) - $enc.xml($vuln.cvssAccessComplexity) - $enc.xml($vuln.cvssAuthentication) - $enc.xml($vuln.cvssConfidentialityImpact) - $enc.xml($vuln.cvssIntegrityImpact) - $enc.xml($vuln.cvssAvailabilityImpact) + #if($vuln.cvssAccessVector)$enc.xml($vuln.cvssAccessVector)#end + #if($vuln.cvssAccessComplexity)$enc.xml($vuln.cvssAccessComplexity)#end + #if($vuln.cvssAuthentication)$enc.xml($vuln.cvssAuthentication)#end + #if($vuln.cvssConfidentialityImpact)$enc.xml($vuln.cvssConfidentialityImpact)#end + #if($vuln.cvssIntegrityImpact)$enc.xml($vuln.cvssIntegrityImpact)#end + #if($vuln.cvssAvailabilityImpact)$enc.xml($vuln.cvssAvailabilityImpact)#end #if ($vuln.cvssScore<4.0) Low #elseif ($vuln.cvssScore>=7.0) diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nsp/NspSearchTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nsp/NspSearchTest.java new file mode 100644 index 000000000..935caeb64 --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nsp/NspSearchTest.java @@ -0,0 +1,73 @@ +/* + * 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 org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; +import org.owasp.dependencycheck.utils.Settings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonReader; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.List; + +public class NspSearchTest extends BaseTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(NspSearchTest.class); + private NspSearch searcher; + + @Before + public void setUp() throws Exception { + String url = Settings.getString(Settings.KEYS.ANALYZER_NSP_URL); + LOGGER.debug(url); + searcher = new NspSearch(new URL(url)); + } + + //@Test + //todo: this test does not work in Java 7 - UNABLE TO FIND VALID CERTIFICATION PATH TO REQUESTED TARGET + public void testNspSearchPositive() throws Exception { + InputStream in = BaseTest.getResourceAsStream(this, "nsp/package.json"); + try (JsonReader jsonReader = Json.createReader(in)) { + final JsonObject packageJson = jsonReader.readObject(); + final JsonObject sanitizedJson = SanitizePackage.sanitize(packageJson); + final JsonObjectBuilder builder = Json.createObjectBuilder(); + final JsonObject nspPayload = builder.add("package", sanitizedJson).build(); + final List advisories = searcher.submitPackage(nspPayload); + Assert.assertTrue(advisories.size() > 0); + } + } + + //@Test(expected = IOException.class) + //todo: this test does not work in Java 7 - UNABLE TO FIND VALID CERTIFICATION PATH TO REQUESTED TARGET + public void testNspSearchNegative() throws Exception { + InputStream in = BaseTest.getResourceAsStream(this, "nsp/package.json"); + try (JsonReader jsonReader = Json.createReader(in)) { + final JsonObject packageJson = jsonReader.readObject(); + final JsonObject sanitizedJson = SanitizePackage.sanitize(packageJson); + searcher.submitPackage(sanitizedJson); + } + } + +} diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nsp/SanitizePackageTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nsp/SanitizePackageTest.java new file mode 100644 index 000000000..0c17f3318 --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nsp/SanitizePackageTest.java @@ -0,0 +1,65 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +public class SanitizePackageTest { + + @Test + public void testSanitizer() throws Exception { + JsonObjectBuilder builder = Json.createObjectBuilder(); + builder + .add("name", "my app") + .add("version", "1.0.0") + .add("description", "my app does amazing things") + .add("keywords", "best, app, ever") + .add("homepage", "http://example.com") + .add("bugs", "http://example.com/bugs") + .add("license", "Apache-2.0") + .add("main", "myscript") + .add("dependencies", "{ \"foo\" : \"1.0.0 - 2.9999.9999\"}") + .add("devDependencies", "{ \"foo\" : \"1.0.0 - 2.9999.9999\"}") + .add("peerDependencies", "{ \"foo\" : \"1.0.0 - 2.9999.9999\"}") + .add("bundledDependencies", "{ \"foo\" : \"1.0.0 - 2.9999.9999\"}") + .add("optionalDependencies", "{ \"foo\" : \"1.0.0 - 2.9999.9999\"}"); + + JsonObject packageJson = builder.build(); + JsonObject sanitized = SanitizePackage.sanitize(packageJson); + + Assert.assertTrue(sanitized.containsKey("name")); + Assert.assertTrue(sanitized.containsKey("version")); + Assert.assertTrue(sanitized.containsKey("dependencies")); + Assert.assertTrue(sanitized.containsKey("devDependencies")); + Assert.assertTrue(sanitized.containsKey("peerDependencies")); + Assert.assertTrue(sanitized.containsKey("bundledDependencies")); + Assert.assertTrue(sanitized.containsKey("optionalDependencies")); + + Assert.assertFalse(sanitized.containsKey("description")); + Assert.assertFalse(sanitized.containsKey("keywords")); + Assert.assertFalse(sanitized.containsKey("homepage")); + Assert.assertFalse(sanitized.containsKey("bugs")); + Assert.assertFalse(sanitized.containsKey("license")); + Assert.assertFalse(sanitized.containsKey("main")); + } + +} diff --git a/dependency-check-core/src/test/resources/dependencycheck.properties b/dependency-check-core/src/test/resources/dependencycheck.properties index 449e1bc5f..6caad0b4d 100644 --- a/dependency-check-core/src/test/resources/dependencycheck.properties +++ b/dependency-check-core/src/test/resources/dependencycheck.properties @@ -72,6 +72,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 @@ -83,6 +86,7 @@ analyzer.experimental.enabled=true 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/test/resources/nsp/package.json b/dependency-check-core/src/test/resources/nsp/package.json new file mode 100644 index 000000000..391ec9061 --- /dev/null +++ b/dependency-check-core/src/test/resources/nsp/package.json @@ -0,0 +1,59 @@ +{ + "name": "owasp-nodejs-goat", + "private": true, + "version": "1.3.0", + "description": "A tool to learn OWASP Top 10 for node.js developers", + "main": "server.js", + "dependencies": { + "bcrypt-nodejs": "0.0.3", + "body-parser": "^1.15.1", + "consolidate": "^0.14.1", + "csurf": "^1.8.3", + "dont-sniff-mimetype": "^1.0.0", + "express": "^4.13.4", + "express-session": "^1.13.0", + "forever": "^0.15.1", + "helmet": "^2.0.0", + "marked": "0.3.5", + "mongodb": "^2.1.18", + "serve-favicon": "^2.3.0", + "swig": "^1.4.2", + "underscore": "^1.8.3" + }, + "comments": { + "//": "do not upgrade the marked package version it is set by purpose", + "//": "to be a vulnerable package to demonstrate an xss introduced through", + "//": "a9 insecure components" + }, + "engines": { + "node": "4.4.x", + "npm": "2.15.x" + }, + "scripts": { + "start": "node server.js", + "test": "node node_modules/grunt-cli/bin/grunt test", + "db:seed": "grunt db-reset", + "precommit": "grunt precommit" + }, + "devDependencies": { + "async": "^2.0.0-rc.4", + "grunt": "^1.0.1", + "grunt-cli": "^1.2.0", + "grunt-concurrent": "^2.3.0", + "grunt-contrib-jshint": "^1.0.0", + "grunt-contrib-watch": "^1.0.0", + "grunt-env": "latest", + "grunt-jsbeautifier": "^0.2.12", + "grunt-mocha-test": "^0.12.7", + "grunt-nodemon": "^0.4.2", + "grunt-if": "https://github.com/binarymist/grunt-if/tarball/master", + "grunt-npm-install": "^0.3.0", + "grunt-retire": "^0.3.12", + "mocha": "^2.4.5", + "selenium-webdriver": "^2.53.2", + "should": "^8.3.1", + "zaproxy": "^0.2.0" + }, + "repository": "https://github.com/OWASP/NodejsGoat", + "license": "Apache 2.0" +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java index aac1d40ed..5484f7253 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java @@ -252,6 +252,14 @@ public final class Settings { * enabled. */ public static final String ANALYZER_NODE_PACKAGE_ENABLED = "analyzer.node.package.enabled"; + /** + * The properties key for whether the Node Security Platform (nsp) analyzer is enabled. + */ + public static final String ANALYZER_NSP_PACKAGE_ENABLED = "analyzer.nsp.package.enabled"; + /** + * The properties key for whether the Nexus analyzer is enabled. + */ + public static final String ANALYZER_NSP_URL = "analyzer.nsp.url"; /** * The properties key for whether the composer lock file analyzer is * enabled.