re-enable node analyzer for #993

This commit is contained in:
Jeremy Long
2017-11-18 13:24:23 -05:00
parent dea9fa1145
commit 2a1186c4fa
5 changed files with 179 additions and 120 deletions

View File

@@ -135,6 +135,15 @@ public class DependencyBundlingAnalyzer extends AbstractDependencyComparingAnaly
mergeDependencies(nextDependency, dependency, dependenciesToRemove);
return true; //since we merged into the next dependency - skip forward to the next in mainIterator
}
} else if (ecoSystemIs(NspAnalyzer.DEPENDENCY_ECOSYSTEM, dependency, nextDependency)
&& namesAreEqual(dependency, nextDependency)
&& versionsAreEqual(dependency, nextDependency)) {
if (dependency.isVirtual()) {
DependencyMergingAnalyzer.mergeDependencies(dependency, nextDependency, dependenciesToRemove);
} else {
DependencyMergingAnalyzer.mergeDependencies(nextDependency, dependency, dependenciesToRemove);
return true;
}
}
return false;
}
@@ -452,4 +461,16 @@ public class DependencyBundlingAnalyzer extends AbstractDependencyComparingAnaly
return filePath != null && filePath.matches(".*\\.(ear|war)[\\\\/].*");
}
private boolean ecoSystemIs(String ecoSystem, Dependency dependency, Dependency nextDependency) {
return ecoSystem.equals(dependency.getEcosystem()) && ecoSystem.equals(nextDependency.getEcosystem());
}
private boolean namesAreEqual(Dependency dependency, Dependency nextDependency) {
return dependency.getName() != null && dependency.getName().equals(nextDependency.getName());
}
private boolean versionsAreEqual(Dependency dependency, Dependency nextDependency) {
return dependency.getVersion() != null && dependency.getVersion().equals(nextDependency.getVersion());
}
}

View File

@@ -118,7 +118,7 @@ public class DependencyMergingAnalyzer extends AbstractDependencyComparingAnalyz
* removed from the main analysis loop, this function adds to this
* collection
*/
private void mergeDependencies(final Dependency dependency, final Dependency relatedDependency, final Set<Dependency> dependenciesToRemove) {
public static void mergeDependencies(final Dependency dependency, final Dependency relatedDependency, final Set<Dependency> dependenciesToRemove) {
LOGGER.debug("Merging '{}' into '{}'", relatedDependency.getFilePath(), dependency.getFilePath());
dependency.addRelatedDependency(relatedDependency);
for (Evidence e : relatedDependency.getEvidence(EvidenceType.VENDOR)) {
@@ -135,9 +135,8 @@ public class DependencyMergingAnalyzer extends AbstractDependencyComparingAnalyz
dependency.addRelatedDependency(d);
relatedDependency.removeRelatedDependencies(d);
}
if (dependency.getSha1sum().equals(relatedDependency.getSha1sum())) {
dependency.addAllProjectReferences(relatedDependency.getProjectReferences());
}
dependency.addAllProjectReferences(relatedDependency.getProjectReferences());
dependenciesToRemove.add(relatedDependency);
}

View File

@@ -48,7 +48,6 @@ import org.owasp.dependencycheck.dependency.EvidenceType;
* @author Dale Visser
*/
@ThreadSafe
@Retired
public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
/**
@@ -133,22 +132,9 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
}
try (JsonReader jsonReader = Json.createReader(FileUtils.openInputStream(file))) {
final JsonObject json = jsonReader.readObject();
if (json.containsKey("name")) {
final Object value = json.get("name");
if (value instanceof JsonString) {
final String valueString = ((JsonString) value).getString();
dependency.setName(valueString);
dependency.addEvidence(EvidenceType.PRODUCT, PACKAGE_JSON, "name", valueString, Confidence.HIGHEST);
dependency.addEvidence(EvidenceType.VENDOR, PACKAGE_JSON, "name_project",
String.format("%s_project", valueString), Confidence.LOW);
} else {
LOGGER.warn("JSON value not string as expected: {}", value);
}
}
addToEvidence(dependency, EvidenceType.PRODUCT, json, "description");
addToEvidence(dependency, EvidenceType.VENDOR, json, "author");
final String version = addToEvidence(dependency, EvidenceType.VERSION, json, "version");
dependency.setVersion(version);
gatherEvidence(json, dependency);
} catch (JsonException e) {
LOGGER.warn("Failed to parse package.json file.", e);
} catch (IOException e) {
@@ -156,6 +142,38 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
}
}
public static void gatherEvidence(final JsonObject json, Dependency dependency) {
if (json.containsKey("name")) {
final Object value = json.get("name");
if (value instanceof JsonString) {
final String valueString = ((JsonString) value).getString();
dependency.setName(valueString);
dependency.setPackagePath(valueString);
dependency.addEvidence(EvidenceType.PRODUCT, PACKAGE_JSON, "name", valueString, Confidence.HIGHEST);
dependency.addEvidence(EvidenceType.VENDOR, PACKAGE_JSON, "name", valueString, Confidence.HIGH);
} else {
LOGGER.warn("JSON value not string as expected: {}", value);
}
}
addToEvidence(dependency, EvidenceType.PRODUCT, json, "description");
addToEvidence(dependency, EvidenceType.VENDOR, json, "author");
final String version = addToEvidence(dependency, EvidenceType.VERSION, json, "version");
if (version != null) {
dependency.setVersion(version);
dependency.addIdentifier("npm", String.format("%s:%s", dependency.getName(), version), null, Confidence.HIGHEST);
}
// Adds the license if defined in package.json
if (json.containsKey("license")) {
final Object value = json.get("license");
if (value instanceof JsonString) {
dependency.setLicense(json.getString("license"));
} else {
dependency.setLicense(json.getJsonObject("license").getString("type"));
}
}
}
/**
* Adds information to an evidence collection from the node json
* configuration.
@@ -166,7 +184,7 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
* @return the actual string set into evidence
* @param key the key to obtain the data from the json information
*/
private String addToEvidence(Dependency dep, EvidenceType t, JsonObject json, String key) {
private static String addToEvidence(Dependency dep, EvidenceType t, JsonObject json, String key) {
String evidenceStr = null;
if (json.containsKey(key)) {
final JsonValue value = json.get(key);

View File

@@ -36,6 +36,7 @@ import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -71,7 +72,11 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
* The default URL to the NSP check API.
*/
public static final String DEFAULT_URL = "https://api.nodesecurity.io/check";
/**
* A descriptor for the type of dependencies processed or added by this
* analyzer.
*/
public static final String DEPENDENCY_ECOSYSTEM = "npm";
/**
* The file name to scan.
*/
@@ -136,10 +141,10 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns the key used in the properties file to reference the analyzer's
* enabled property.x
* Returns the key used in the properties file to determine if the analyzer
* is enabled.
*
* @return the analyzer's enabled property setting key
* @return the enabled property setting key for the analyzer
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
@@ -152,16 +157,48 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
if (!file.isFile() || file.length() == 0) {
return;
}
try (JsonReader jsonReader = Json.createReader(FileUtils.openInputStream(file))) {
// Retrieves the contents of package.json from the Dependency
final JsonObject packageJson = jsonReader.readObject();
if (dependency.getEcosystem() == null || dependency.getName() == null) {
NodePackageAnalyzer.gatherEvidence(packageJson, dependency);
dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
}
// Do not scan the node_modules directory
if (file.getCanonicalPath().contains(File.separator + "node_modules" + File.separator)) {
LOGGER.debug("Skipping analysis of node module: " + file.getCanonicalPath());
return;
}
// Retrieves the contents of package.json from the Dependency
final JsonObject packageJson = jsonReader.readObject();
//Processes the dependencies objects in package.json and adds all the modules as dependencies
if (packageJson.containsKey("dependencies")) {
final JsonObject dependencies = packageJson.getJsonObject("dependencies");
processPackage(engine, dependency, dependencies, "dependencies");
}
if (packageJson.containsKey("devDependencies")) {
final JsonObject dependencies = packageJson.getJsonObject("devDependencies");
processPackage(engine, dependency, dependencies, "devDependencies");
}
if (packageJson.containsKey("optionalDependencies")) {
final JsonObject dependencies = packageJson.getJsonObject("optionalDependencies");
processPackage(engine, dependency, dependencies, "optionalDependencies");
}
if (packageJson.containsKey("peerDependencies")) {
final JsonObject dependencies = packageJson.getJsonObject("peerDependencies");
processPackage(engine, dependency, dependencies, "peerDependencies");
}
if (packageJson.containsKey("bundleDependencies")) {
final JsonArray dependencies = packageJson.getJsonArray("bundleDependencies");
processPackage(engine, dependency, dependencies, "bundleDependencies");
}
if (packageJson.containsKey("bundledDependencies")) {
final JsonArray dependencies = packageJson.getJsonArray("bundledDependencies");
processPackage(engine, dependency, dependencies, "bundledDependencies");
}
// Create a sanitized version of the package.json
final JsonObject sanitizedJson = SanitizePackage.sanitize(packageJson);
@@ -192,77 +229,21 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
* 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());
//TODO consider changing this to available versions on the dependency
//vs.setUpdate(advisory.getPatchedVersions());
vs.setName(advisory.getModule() + ":" + advisory.getVulnerableVersions());
vuln.setVulnerableSoftware(new HashSet<>(Arrays.asList(vs)));
// Add the vulnerability to package.json
dependency.addVulnerability(vuln);
}
/*
* Adds evidence about the node package itself, not any of the modules.
*/
if (packageJson.containsKey("name")) {
final Object value = packageJson.get("name");
if (value instanceof JsonString) {
final String valueString = ((JsonString) value).getString();
dependency.addEvidence(EvidenceType.PRODUCT, PACKAGE_JSON, "name", valueString, Confidence.HIGHEST);
dependency.addEvidence(EvidenceType.VENDOR, PACKAGE_JSON, "name_project",
String.format("%s_project", valueString), Confidence.LOW);
Dependency existing = findDependency(engine, advisory.getModule(), advisory.getVersion());
if (existing == null) {
Dependency nodeModule = createDependency(dependency, advisory.getModule(), advisory.getVersion(), "transitive");
nodeModule.addVulnerability(vuln);
engine.addDependency(nodeModule);
} else {
LOGGER.warn("JSON value not string as expected: {}", value);
existing.addVulnerability(vuln);
}
}
/*
* 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 JsonArray dependencies = packageJson.getJsonArray("bundleDependencies");
processPackage(dependency, dependencies, "bundleDependencies");
}
if (packageJson.containsKey("bundledDependencies")) {
final JsonArray dependencies = packageJson.getJsonArray("bundledDependencies");
processPackage(dependency, dependencies, "bundledDependencies");
}
/*
* Adds the license if defined in package.json
*/
if (packageJson.containsKey("license")) {
final Object value = packageJson.get("license");
if (value instanceof JsonString) {
dependency.setLicense(packageJson.getString("license"));
} else {
dependency.setLicense(packageJson.getJsonObject("license").getString("type"));
}
}
/*
* Adds general evidence to about the package.
*/
addToEvidence(dependency, EvidenceType.PRODUCT, packageJson, "description");
addToEvidence(dependency, EvidenceType.VENDOR, packageJson, "author");
addToEvidence(dependency, EvidenceType.VERSION, packageJson, "version");
dependency.setDisplayFileName(String.format("%s/%s", file.getParentFile().getName(), file.getName()));
} catch (URLConnectionFailureException e) {
this.setEnabled(false);
throw new AnalysisException(e.getMessage(), e);
@@ -275,62 +256,62 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
}
}
private Dependency createDependency(Dependency dependency, String name, String version, String scope) {
final Dependency nodeModule = new Dependency(new File(dependency.getActualFile() + "?" + name), true);
nodeModule.setEcosystem(DEPENDENCY_ECOSYSTEM);
nodeModule.addEvidence(EvidenceType.PRODUCT, "package.json", "name", name, Confidence.HIGHEST);
nodeModule.addEvidence(EvidenceType.VENDOR, "package.json", "name", name, Confidence.HIGH);
nodeModule.addEvidence(EvidenceType.VERSION, "package.json", "version", version, Confidence.HIGHEST);
nodeModule.addProjectReference(dependency.getName() + ": " + scope);
nodeModule.setName(name);
nodeModule.setVersion(version);
nodeModule.addIdentifier("npm", String.format("%s:%s", name, version), null, Confidence.HIGHEST);
return nodeModule;
}
/**
* Processes a part of package.json (as defined by JsonArray) and update the
* specified dependency with relevant info.
*
* @param engine the dependency-check engine
* @param dependency the Dependency to update
* @param jsonArray the jsonArray to parse
* @param depType the dependency type
*/
private void processPackage(Dependency dependency, JsonArray jsonArray, String depType) {
private void processPackage(Engine engine, Dependency dependency, JsonArray jsonArray, String depType) {
final JsonObjectBuilder builder = Json.createObjectBuilder();
for (JsonString str : jsonArray.getValuesAs(JsonString.class)) {
builder.add(str.toString(), "");
}
final JsonObject jsonObject = builder.build();
processPackage(dependency, jsonObject, depType);
processPackage(engine, dependency, jsonObject, depType);
}
/**
* Processes a part of package.json (as defined by JsonObject) and update
* the specified dependency with relevant info.
*
* @param engine the dependency-check engine
* @param dependency the Dependency to update
* @param jsonObject the jsonObject to parse
* @param depType the dependency type
*/
private void processPackage(Dependency dependency, JsonObject jsonObject, String depType) {
private void processPackage(Engine engine, Dependency dependency, JsonObject jsonObject, String depType) {
for (int i = 0; i < jsonObject.size(); i++) {
for (Map.Entry<String, JsonValue> 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);
final String name = entry.getKey();
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.
*/
//TODO is this actually correct? or should these be transitive dependencies?
final Dependency nodeModule = new Dependency(new File(dependency.getActualFile() + "#" + entry.getKey()), true);
nodeModule.setDisplayFileName(entry.getKey());
nodeModule.addIdentifier(moduleName);
nodeModule.addIdentifier(moduleVersion);
nodeModule.addIdentifier(moduleDepType);
dependency.addRelatedDependency(nodeModule);
Dependency existing = findDependency(engine, name, version);
if (existing == null) {
final Dependency nodeModule = createDependency(dependency, name, version, depType);
engine.addDependency(nodeModule);
} else {
existing.addProjectReference(dependency.getName() + ": " + depType);
}
}
}
}
@@ -368,4 +349,43 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
}
}
}
private Dependency findDependency(Engine engine, String name, String version) {
for (Dependency d : engine.getDependencies()) {
if (DEPENDENCY_ECOSYSTEM.equals(d.getEcosystem()) && name.equals(d.getName()) && version != null && d.getVersion() != null) {
String dependencyVersion = d.getVersion();
if (dependencyVersion.startsWith("^") || dependencyVersion.startsWith("~")) {
dependencyVersion = dependencyVersion.substring(1);
}
if (version.equals(dependencyVersion)) {
return d;
}
if (version.startsWith("^") || version.startsWith("~") || version.contains("*")) {
String type;
String tmp;
if (version.startsWith("^") || version.startsWith("~")) {
type = version.substring(0, 1);
tmp = version.substring(1);
} else {
type = "*";
tmp = version;
}
String[] v = tmp.split(" ")[0].split("\\.");
String[] depVersion = dependencyVersion.split("\\.");
if ("^".equals(type) && v[0].equals(depVersion[0])) {
return d;
} else if ("~".equals(type) && v.length >= 2 && depVersion.length >= 2 && v[0].equals(depVersion[0]) && v[1].equals(depVersion[1])) {
return d;
} else if (v[0].equals("*")
|| (v.length >= 2 && v[0].equals(depVersion[0]) && v[1].equals("*"))
|| (v.length >= 3 && depVersion.length >= 2 && v[0].equals(depVersion[0]) && v[1].equals(depVersion[1]) && v[2].equals("*"))) {
return d;
}
}
}
}
return null;
}
}

View File

@@ -123,6 +123,7 @@ public class NspSearch {
try (InputStream in = new BufferedInputStream(conn.getInputStream());
JsonReader jsonReader = Json.createReader(in)) {
final JsonArray array = jsonReader.readArray();
if (array != null) {
for (int i = 0; i < array.size(); i++) {
final JsonObject object = array.getJsonObject(i);