10 NPM Best Practices to Secure your Node.JS App
Web Security is an ever-changing field, with attackers finding new and innovative ways to access an unsuspecting user’s system. An insecure app is bound to be exploited by a seasoned attacker, in one form or another.
We take a look at 10 of the best security practices that you can implement right now to keep your Node.JS application safe. Without further ado, let us dive right in.
1. Run Node.JS as a non-root user.
Most node.js apps don’t need root access and can easily run without root privileges. A user should only be able to access the information and resources that they need, which means granting every user root access will leave you open to attacks. You don’t want the attacker to have unlimited power over the local machine, which they can then use to divert traffic to another server.
The solution: Create a non-root user and then run the process on the user’s behalf, by invoking the container with the flag “-u username.” You can also bake the non-root user into the Docker image.
2. Use Two Factor Authentication.
In the case of a majority of node.js development environments, the access is secured with the help of a password. But passwords such as password, letmein, or donkeykong are easy to guess and leaves the system open to attack. Weak session management policies implemented in applications usually leaves the door wide open to attackers. You may have a user logging in from a public computer that has a password manager, and the password that you or your company spent hours generating is now open to the public.
The solution: Use Two-Factor Authorization to keep all your passwords secure. Adding an extra layer of security, even if an attacker has your user’s login credentials, they still can’t get into your system without access to a secondary device, such as a mobile phone. Use pre-existing solutions such as Okta, 0Auth, or provide Two-Factor Authorization in-app using a package such as speakeasy.
3. Explicitly state when a process should crash.
Denial of Service (DOS) attacks will crash a system or make it inaccessible and is a preferred mode of attack among hackers. Attackers accomplish DOS attacks by sending a huge surge of traffic to your server, bombarding it with useless information that will cause the server to slow down / crash eventually. Node.JS processes will usually crash when errors are not handled properly. If the attackers know which errors will cause the system to crash, they will repeatedly send the same request.
The solution: Solutions include alerting any time a process crashes due to an unhandled error, avoiding crashing the process when the user input is invalid, and wrapping all routes with a catch. You can also limit the payload that a user can submit to your app/ api / service. You can also set a maximum number of requests that a user can make, and after all the requests, lock the user out for a set period.
4. Keep track of all the vulnerabilities.
With a large number of open-source packages available for the JavaScript ecosystem, it is easy for an attacker to sneak in malicious code into one of our applications. Keeping track of all the various versions and licensing of these packages is often tedious, leaving a window of opportunity to a dedicated attacker. It is important to keep all the dependencies in check as and when new vulnerabilities are found. Otherwise, an attacker can assess your entire web framework and attack all its known vulnerabilities.
The solution: Use readily available tools such as npm audit, snyk, or nsp to monitor and patch all known vulnerabilities. Package managers such as Yarn can also help to block these outlets by locking versions of packages installed. By running $ npm audit, you can find an audit report with all the installed dependencies.
5. Use TLS
Transport Layer Security (TLS) is critical if your app deals with sensitive data, to secure the connection and the data. Before data is transmitted between the client and the server, TLS encrypts it so that there is no pocket sniffing or man-in-the-middle attacks. A man-in-the-middle attack is a type of attack when an attacker is able to put themselves between two parties and intercept and influence the traffic between them. This type of attack can compromise the integrity of data in your application and is commonly carried out when you work over a public Wi fi network.
The solution: Avoiding your Node.JS application from being directly exposed to the internet is a brutal but effective way to stop MITM attacks. Terminating the SSL prior to the Node.JS stage, using a tool such as NGINX, is also a viable option. TLS is just an advanced version of the SSL, and we highly recommend using TLS to prevent any type of attack.
6. Escape JS, HTML and CSS output.
Web languages such as HTML generally mix content with executable code. Your code may be in the form of an HTML paragraph that may contain a visual representation of the data along with JS executable instructions. Sometimes, JS code that was not intended to be displayed while rendering the HTML may actually get interpreted and executed by the browser. This commonly happens when content that was inserted into the database by an attacker is rendered.
The solution: Instruct the browser to interpret any chunk of untrusted data as content only and never to interpret it. This technique is called Escaping. Escaping capabilities are provided by many NPM libraries and templating engines. Escaping is also sometimes called encoding, and it is a way of representing data that will not be executed or interpreted.
7. Don’t publish secrets to the NPM registry.
When working with the NPM registry, there is a high chance that you may publish some of the secrets such as API keys, passwords, or other secrets on the npm public registry. Files such as .env, which should ideally be added to a .gitignore to avoid committing it to an SCM, may find its way into your working directory. This is because files you do not want to commit to your repository also usually files you do not want to be published. There is also a file called .npmignore, which behaves exactly like .gitignore. This comes with the additional challenge that you will most likely publish a file you thought you had excluded if you try to use both.
The solution: Use the files property in package.json, that works as a whitelist and specifies the array of files that are to be included in the package to be created and installed. Also, add a — dry-run argument to your publish command in order to see how the tarball is actually created without publishing it to the registry.
8. Be careful working with child processes.
Shell injection attacks are one of the most common exploits on an unsecured Node.js application. In a shell injection attack, the attacker takes over the Operating System(OS) on the server and asks it to execute arbitrary commands, fully compromising the application and all its data. Using child processes and not sanitizing input data often leaves your server vulnerable to shell injection attacks. We don’t want attackers to run a command with a child process instance, which we have created from our app.
The solution: Use a child_process.exec file which will execute only a single command with a set of attributes and will not allow shell parameter expansion.
9. Deserialization
Serialization is the process of turning some object into a data format that can be later restored into the original format, and deserialization is the reversal of that process. Insecure deserialization allows deserialization and execution of malicious objects via API calls or remote code execution. The attacker can modify the application by executing remote code on application classes, which will cause a change in the behaviour during serialization. The attacker can also tamper data objects such as cookies with malicious intent.
The solution: In order to mitigate such attacks, we use something called cross-site request forgery (CSRF). It is as simple as generating a CSRF token from our server and adding it to a hidden form field. The CSRF middleware checks if the incoming token matches what was sent before and rejects requests where the tokens don’t match.
10. Hide error messages from clients
If you do not handle errors properly in a Node.JS application, sensitive application details such as third party modules in use, server file paths, and other internal workflows of the application can be exploited. An attacker can also access the said information from a leak in the stack trace. Hence we recommend using an integrated express error handler that hides the error details by default.
The solution: If not using an integrated express error handler, you can implement your own error handling logic using custom Error objects. In case you use this approach, make sure that you don’t return the entire Error object to the client. This will prevent the leak of sensitive application details.
There you have it! Ten of the best Node.JS security practices you can implement today that can make your app more secure. For further reading, there is a tonne of information available online, and we are pretty sure our friends at Google will be more than happy to help you out.