1 /* 2 * This file is part of dependency-check-core. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * Copyright (c) 2017 Steve Springett. All Rights Reserved. 17 */ 18 package org.owasp.dependencycheck.analyzer; 19 20 import org.apache.commons.io.FileUtils; 21 import org.owasp.dependencycheck.Engine; 22 import org.owasp.dependencycheck.analyzer.exception.AnalysisException; 23 import org.owasp.dependencycheck.data.nsp.Advisory; 24 import org.owasp.dependencycheck.data.nsp.NspSearch; 25 import org.owasp.dependencycheck.data.nsp.SanitizePackage; 26 import org.owasp.dependencycheck.dependency.Dependency; 27 import org.owasp.dependencycheck.dependency.Vulnerability; 28 import org.owasp.dependencycheck.dependency.VulnerableSoftware; 29 import org.owasp.dependencycheck.utils.FileFilterBuilder; 30 import org.owasp.dependencycheck.utils.Settings; 31 import org.slf4j.Logger; 32 import org.slf4j.LoggerFactory; 33 import java.io.File; 34 import java.io.FileFilter; 35 import java.io.IOException; 36 import java.net.MalformedURLException; 37 import java.util.Arrays; 38 import java.util.HashSet; 39 import java.util.List; 40 import javax.annotation.concurrent.ThreadSafe; 41 import javax.json.Json; 42 import javax.json.JsonException; 43 import javax.json.JsonObject; 44 import javax.json.JsonObjectBuilder; 45 import javax.json.JsonReader; 46 import org.owasp.dependencycheck.exception.InitializationException; 47 import org.owasp.dependencycheck.utils.InvalidSettingException; 48 import org.owasp.dependencycheck.utils.URLConnectionFailureException; 49 50 /** 51 * Used to analyze Node Package Manager (npm) package.json files via Node 52 * Security Platform (nsp). 53 * 54 * @author Steve Springett 55 */ 56 @ThreadSafe 57 public class NspAnalyzer extends AbstractNpmAnalyzer { 58 59 /** 60 * The logger. 61 */ 62 private static final Logger LOGGER = LoggerFactory.getLogger(NspAnalyzer.class); 63 64 /** 65 * The default URL to the NSP check API. 66 */ 67 public static final String DEFAULT_URL = "https://api.nodesecurity.io/check"; 68 /** 69 * A descriptor for the type of dependencies processed or added by this 70 * analyzer. 71 */ 72 public static final String DEPENDENCY_ECOSYSTEM = NPM_DEPENDENCY_ECOSYSTEM; 73 /** 74 * The file name to scan. 75 */ 76 private static final String PACKAGE_JSON = "package.json"; 77 78 /** 79 * Filter that detects files named "package.json". 80 */ 81 private static final FileFilter PACKAGE_JSON_FILTER = FileFilterBuilder.newInstance() 82 .addFilenames(PACKAGE_JSON).build(); 83 84 /** 85 * The NSP Searcher. 86 */ 87 private NspSearch searcher; 88 89 /** 90 * Returns the FileFilter 91 * 92 * @return the FileFilter 93 */ 94 @Override getFileFilter()95 protected FileFilter getFileFilter() { 96 return PACKAGE_JSON_FILTER; 97 } 98 99 /** 100 * Initializes the analyzer once before any analysis is performed. 101 * 102 * @param engine a reference to the dependency-check engine 103 * @throws InitializationException if there's an error during initialization 104 */ 105 @Override prepareFileTypeAnalyzer(Engine engine)106 public void prepareFileTypeAnalyzer(Engine engine) throws InitializationException { 107 LOGGER.debug("Initializing {}", getName()); 108 try { 109 searcher = new NspSearch(getSettings()); 110 } catch (MalformedURLException ex) { 111 setEnabled(false); 112 throw new InitializationException("The configured URL to Node Security Platform is malformed", ex); 113 } 114 try { 115 final Settings settings = engine.getSettings(); 116 final boolean nodeEnabled = settings.getBoolean(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED); 117 if (!nodeEnabled) { 118 LOGGER.warn("The Node Package Analyzer has been disabled; the resulting report will only " 119 + " contain the known vulnerable dependency - not a bill of materials for the node project."); 120 } 121 } catch (InvalidSettingException ex) { 122 throw new InitializationException("Unable to read configuration settings", ex); 123 } 124 } 125 126 /** 127 * Returns the name of the analyzer. 128 * 129 * @return the name of the analyzer. 130 */ 131 @Override getName()132 public String getName() { 133 return "Node Security Platform Analyzer"; 134 } 135 136 /** 137 * Returns the phase that the analyzer is intended to run in. 138 * 139 * @return the phase that the analyzer is intended to run in. 140 */ 141 @Override getAnalysisPhase()142 public AnalysisPhase getAnalysisPhase() { 143 return AnalysisPhase.FINDING_ANALYSIS; 144 } 145 146 /** 147 * Returns the key used in the properties file to determine if the analyzer 148 * is enabled. 149 * 150 * @return the enabled property setting key for the analyzer 151 */ 152 @Override getAnalyzerEnabledSettingKey()153 protected String getAnalyzerEnabledSettingKey() { 154 return Settings.KEYS.ANALYZER_NSP_PACKAGE_ENABLED; 155 } 156 157 @Override analyzeDependency(Dependency dependency, Engine engine)158 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException { 159 engine.removeDependency(dependency); 160 final File file = dependency.getActualFile(); 161 if (!file.isFile() || file.length() == 0 || !shouldProcess(file)) { 162 return; 163 } 164 165 try (JsonReader jsonReader = Json.createReader(FileUtils.openInputStream(file))) { 166 167 // Retrieves the contents of package.json from the Dependency 168 final JsonObject packageJson = jsonReader.readObject(); 169 170 // Create a sanitized version of the package.json 171 final JsonObject sanitizedJson = SanitizePackage.sanitize(packageJson); 172 173 // Create a new 'package' object that acts as a container for the sanitized package.json 174 final JsonObjectBuilder builder = Json.createObjectBuilder(); 175 final JsonObject nspPayload = builder.add("package", sanitizedJson).build(); 176 177 // Submits the package payload to the nsp check service 178 final List<Advisory> advisories = searcher.submitPackage(nspPayload); 179 180 for (Advisory advisory : advisories) { 181 /* 182 * Create a new vulnerability out of the advisory returned by nsp. 183 */ 184 final Vulnerability vuln = new Vulnerability(); 185 vuln.setCvssScore(advisory.getCvssScore()); 186 vuln.setDescription(advisory.getOverview()); 187 vuln.setName(String.valueOf(advisory.getId())); 188 vuln.setSource(Vulnerability.Source.NSP); 189 vuln.addReference( 190 "NSP", 191 "Advisory " + advisory.getId() + ": " + advisory.getTitle(), 192 advisory.getAdvisory() 193 ); 194 195 /* 196 * Create a single vulnerable software object - these do not use CPEs unlike the NVD. 197 */ 198 final VulnerableSoftware vs = new VulnerableSoftware(); 199 //TODO consider changing this to available versions on the dependency 200 // - the update is a part of the version, not versions to update to 201 //vs.setUpdate(advisory.getPatchedVersions()); 202 203 vs.setName(advisory.getModule() + ":" + advisory.getVulnerableVersions()); 204 vuln.setVulnerableSoftware(new HashSet<>(Arrays.asList(vs))); 205 206 final Dependency existing = findDependency(engine, advisory.getModule(), advisory.getVersion()); 207 if (existing == null) { 208 final Dependency nodeModule = createDependency(dependency, advisory.getModule(), advisory.getVersion(), "transitive"); 209 nodeModule.addVulnerability(vuln); 210 engine.addDependency(nodeModule); 211 } else { 212 existing.addVulnerability(vuln); 213 } 214 } 215 } catch (URLConnectionFailureException e) { 216 this.setEnabled(false); 217 throw new AnalysisException(e.getMessage(), e); 218 } catch (IOException e) { 219 LOGGER.debug("Error reading dependency or connecting to Node Security Platform - check API", e); 220 this.setEnabled(false); 221 throw new AnalysisException(e.getMessage(), e); 222 } catch (JsonException e) { 223 throw new AnalysisException(String.format("Failed to parse %s file.", file.getPath()), e); 224 } 225 } 226 } 227