This Open Web Application Security Project (OWASP) Top 10 risk highlights the threats from using external dependencies in an application. For Node applications, these dependencies range from Node.js binary itself to frameworks such as Express and thousands of modules available on npm. Security vulnerabilities in these dependencies directly affect the application. As a result, it is a critical task for Node developers to stay aware of any vulnerabilities discovered in an application’s external dependencies and diligently keep the application safe against it.
Let’s review how attackers can exploit these vulnerabilities.
As a first step of an attack, malicious actors typically perform application foot-printing to discover the platform, frameworks, and server on which the application is built. When this is known, an attacker can look up publicly known common vulnerabilities and exposures (CVEs) published for that platform and apply them toward the target application. In other cases, attackers automate exploits based on CVEs and spray them across the internet to catch the prey.
Exploiting publicly known vulnerabilities often yields success for attackers. As per a research study done by Verizon, 99 percent of close to 80,000 security incidents in the year 2014 involved exploiting vulnerabilities for which CVEs were published at least a year earlier, some published as far back as 1999. Thus, even decade-old CVEs often prove useful to attackers and are actively exploited in the wild.
For Node apps, the applicable CVEs include Node.js vulnerabilities, as well as advisories found in Node modules.
The npm code of conduct has an explicit policy against malware, stating that “a package which is designed to maliciously exploit or damage computer systems is not allowed.” However, in the spirit of being an open and community-driven environment, npm doesn’t impose a formal audit or an approval process before a module can be published.
Although the following are not very common, here are some possible ways by which an attacker could sneak in a malicious module on victim user’s application:
By publishing a module that provides some useful features, but performs malicious activities under the hood.
By naming a malicious module with a spelling that is very close to an existing popular module, targeting npm users who fat-finger and mistype the actual module name.
By compromising popular module author’s npm account (by using brute-force attack, or exploiting HTTP Bearer Token Vulnerability in npm <v2.15.1 or <v3.8.3, for example), and publishing a new version of a module with malicious code.
To implement a malicious package, an attacker could take advantage of the npm-script hooks in the package.json that allow executing a script or command on the server. As an alternate option, an attacker could have the module send files or data to an external location, delete files, or install additional malicious files by using Node core APIs such as filesystem, child process, and HTTP. The possibilities of what an attacker could do after the malicious package is installed are limited only by permissions of the user the application is running as.
This mechanism involves an attacker using unpublished vulnerabilities in Node.js or Node modules for which no patch or release with fixes exists.
Because it is unlikely that you can avoid external dependencies of a Node application, let’s go over some ways to protect against related threats.
As vulnerabilities in any npm module that an application uses directly affect the security of the application, you need to be diligent in reviewing the module for potential security issues before installing and using it.
As a heuristic approach, you should prefer using a module that is written by a reputable author, actively maintained by the community, has a healthy amount of download stats, and comes with an extensive test suite.
If these general guidelines fail to establish a good confidence level, but you have good reasons to use a module, you should undertake to review the source code of the module. The following are a few ideas about what to look for during the code review.
Before installing a module, check the scripts section in the package.json, either from the source code or by running the npm view <pkgname> scripts
command.
Ensure that hooks such as preinstall, install, postinstall, preuninstall, uninstall, or postuninstall do not invoke any unexpected command or script. Verify that no commands have a “sudo” prefix.
Inside the module code under review, check whether Node core APIs and bindings are being used that could potentially help an attacker conducting malicious activities such as deleting, updating, or adding files, sending data over the network to a remote location, executing commands on the server, and so on. The commonly used core modules for such activities are fs
, dns
, http
, https
, net
, os
, child_process
, and zlib
. If you notice these modules being used in the source code, verify its necessity for the module’s intended functionality and the way it is used.
N|Solid is a Node.js runtime that has been enhanced to address the needs of the enterprise. It provides a way to blacklist Node core modules and bindings that could be harmful. It allows specifying severity levels to ignore, warn, throw an error, or exit an application when a function on a blacklisted core module or binding is used by the application code or external modules, thus helping to detect possibly harmful code.
There is an excellent article with more details on blacklisting.
Keep watch on vulnerabilities found and patched in Node.js runtime and npm modules that your project uses. Here are some useful tools and resources to keep track of these security updates:
The nodejs-sec forum and Node.js blog
The Node wiki with information on breaking changes in releases
Tools: fortunately, the daunting task of keeping track of new versions and vulnerabilities discovered in numerous direct and indirect project dependencies is much easier with the help of excellent tools at the disposal of node developers:
Tools for checking for outdated, incorrect, and unused dependencies: npm-check, david-dm, and npm outdated command
Tools for scanning external modules used in a project for known vulnerabilities: snyk, and npm audit
You can incorporate most of these tools in the build script and run them as part of the continuous integration.
In addition, maintaining a good test suite for application code is extremely useful to ensure functionalities work as intended after upgrading the dependencies.
The privileges of a user that an application is running under are important in limiting the extent of the damage an unsecured external dependency could cause. Therefore, limit these access privileges to an absolute minimum.
The majority of code in a Node application comprises external dependencies. Because this is inevitable, the security of the application is only as strong as the weakest link in its dependencies.
In this chapter, we covered strategies to vet such external dependencies, along with tools and resources useful to stay on top of ever-emerging vulnerabilities and security updates for these dependencies.
Here are some additional articles and resources on this topic:
Yarn—Security as a core value Explains the package integrity check run by yarn to ensure that you always get the same bits for a specified version of a package.