[bookmark] 23 recommendations for the protection of Node.js applications

Original author: Yoni Goldberg, Kyle Martin, Bruno Scheufler
  • Transfer
Nowadays, web services are constantly undergoing a variety of attacks. Therefore, security is something that should be remembered at all stages of the project life cycle. The authors of the material, the translation of which we are publishing today, support the repository on GitHub, which contains about 80 recommendations for securing applications running on the Node.js platform. This material, which was based on many security publications, collected more than two dozen recommendations related to Node.js, and some general tips. At the same time, this material covers the top 10 vulnerabilities from the OWASP project list.



1. Use the rules for the linter, aimed at checking the security code



Recommendations


During development, use a security-oriented linter plugin , such as eslint-plugin-security . This allows you to identify vulnerabilities and security issues very early - at the time of writing the appropriate code. This approach helps to find weak spots in program security. Among them is the use of a command eval, a call to child processes, the import of modules with the transfer to the appropriate command of something different from a string literal (say, a certain string formed on the basis of data transmitted to the server by the user).

Here's a useful material about the linter rules

Adam BaldwinLintting says the following: “A linter should not be just a tool that meticulously follows the application of the rules regarding the number of spaces used, the placement of semicolons and the use of the eval command. ESLint gives the developer a powerful platform that allows you to detect a wide range of potentially dangerous patterns in the code and eliminate them. We are talking, for example, about regular expressions, about checking user input, and so on. I believe that the linter gives developers who are concerned about security issues a new, powerful tool that needs attention. ”

Possible problems


What looks like a minor security bug during development is a serious vulnerability in production. In addition, if all project developers do not follow uniform security rules when working with code, this may lead to the appearance of vulnerabilities in it, or, for example, to confidential data in public repositories.

2. Limit the number of simultaneous requests to the server



Recommendations


DoS-attacks are very popular among attackers, it is relatively easy to conduct them. You can implement a system for limiting the number of requests to an application using an external service, such as a cloud load balancer, cloud firewall, nginx server, or, for small applications that are not critical, using middleware to limit the number of requests like express-rate -limit .

Here is the material on the implementation of the system for limiting the frequency of requests to the server

Possible problems


An application that does not have a system for limiting the number of simultaneous requests may be attacked, which will lead to its failure. This is reflected in the fact that users of such an application will either have difficulty working with it, or will not be able to interact with it at all.

3. Remove confidential information from configuration files or encrypt it.



Recommendations


Never store sensitive data in plain text in configuration files or in code. Instead, use confidential data management systems like Vault products or Kubernetes / Docker Secrets systems, or use environment variables to store such data. Confidential data stored in the version control system must be encrypted, measures must be taken to securely store and use it. Such measures include the use of dynamic keys, the use of password expiration dates, security audits, and so on. Use systems to check the code before committing or before sending it to the repository to prevent confidential data from being sent to the public repository by accident.

Here is a material about managing sensitive data.

Possible problems


Even if the code is stored in a closed repository, one day, by mistake, it may become publicly available. At this moment, all the confidential data stored in it will become public domain. As a result, the access of third parties to the repository with the code will inadvertently lead to the fact that they will get access to the systems associated with it (databases, API, services, and so on).

4. Prevent code injection vulnerabilities.



Recommendations


In order to prevent SQL / NoSQL injections and other similar attacks, always use ORM / ODM libraries or DBMS mechanisms aimed at clearing data, or supporting named or indexed parameterized queries that check what comes from the user. Never use, for the introduction of certain values ​​in the texts of the requests, only javascript string strings, or string concatenation, since this approach opens your application to a wide range of vulnerabilities. All reputable Node.js libraries used to work with data (for example, Sequelize , Knex , mongoose ) contain built-in protection against attacks by injecting code.

here injection prevention material using ORM / ODM libraries

Possible problems


Using untested and unclean data received from the user in queries can lead to an attack by introducing an operator when working with a NoSQL database like MongoDB. If you do not use a data cleaning system or an ORM library when working with a SQL database, this will lead to the possibility of an attack by SQL injection, which creates a huge gap in the application's security system.

5. Dodge DoS attacks by explicitly specifying the abnormal termination conditions of the process.


Recommendations


The Node process crashes when an unhandled error occurs. But in many of the recommendations reflecting the best development practices for Node, it is recommended that processes be completed even when the resulting error was intercepted and processed. Express, for example, will crash if any asynchronous error occurs - unless the routes are wrapped in expressionscatch. This fact opens up a very attractive opportunity to attackers. They, having found out that at receipt of a certain request the process is terminated abnormally, they start to send him such requests. There is no recommendation that would allow to solve this problem in one fell swoop, however, some techniques can soften it. So, at the end of the process due to an unhandled error, you need to notify the administrator, giving such notification the highest priority priority. It is necessary to check what comes to the process in the requests and to avoid situations of a process crash due to requests that are accidentally or intentionally formed incorrectly. All routes need to be wrapped in expressions.catch and configure the system so that if the cause of the error is a request, the process would not terminate abnormally (as opposed to what happens at the global application level).

Possible problems


Let's analyze the following situation. There are many Node.js applications. What happens if we start sending them POST requests with empty JSON as the request body? This will cause many of these applications to crash.

Now, if we played the role of intruders, we, in order for applications to continue to fail, it would be enough to continue to send them similar requests.

6. Configure HTTP response headers to enhance project security.



Recommendations


The application should use security-oriented HTTP headers to prevent attackers from resorting to such common attack techniques as cross-site scripting (XSS), clickjacking, and others. Customizing the headers is easy using special modules such as helmet .

Here is a story about using secure headers.

Possible problems


If secure HTTP headers are not used, attackers will be able to carry out attacks on users of your applications, which leads to huge vulnerabilities.

7. Constantly, automatically, check your projects for the use of vulnerable dependencies in them.



Recommendations


In the NPM ecosystem, projects with many dependencies are a very common phenomenon. Dependencies should always be monitored, given the discovery of new vulnerabilities. Use tools like npm audit , nsp, or snyk to detect, monitor, and fix vulnerable dependencies . Embed these tools into your continuous integration system. This will allow you to detect vulnerable dependencies before they fall into production.

Here is a material about project dependency security

Possible problems


An attacker can identify the web framework used in the project and carry out attacks on all of its known vulnerabilities.

8. Try not to use the standard Node.js crypto module for password handling, use Bcrypt instead.



Recommendations


Passwords or other sensitive data (API keys, for example) need to be stored, processing them with cryptographic functions using “salt”, such as Bcrypt. It is worth using something similar, and not a standard Node.js module cryptofor security and performance reasons.

Here is the Bcrypt material

Possible problems


Passwords or some confidential data that is stored without the use of appropriate measures to protect them are vulnerable to brute force attacks and dictionary attacks, which ultimately lead to the disclosure of such data.

9. Use character shielding systems in HTML, JS and CSS data sent to the user.



Recommendations


If some data from an untrusted source is sent to the user's browser, then even if it should just be displayed on the screen, such data can be a code that can be executed. This is commonly referred to as cross-site scripting (XSS). The risk of such attacks can be reduced by using special libraries that process data so that they cannot be executed. This is called data encoding or escaping.

Here is the screening of the output.

Possible problems


If you do not care about data shielding, an attacker may, for example, save malicious JavaScript code in your database, which can be transmitted to clients unchanged and run.

10. Check JSON data coming to server



Recommendations


Monitor the contents of the bodies of incoming requests, checking whether they correspond to what you expect to see in such requests. If the request does not look as expected, quickly stop processing it. To avoid the cumbersome operation of writing request verification code for each route, you can use lightweight JSON tools to validate data, such as jsonschema or joi .

Here is a material about checking incoming JSON data

Possible problems


If the server cordially accepts any requests without thoroughly checking them, this greatly increases the attack surface of the application and inspires attackers to try out a lot of requests to find those that cause the system to crash.

11. Maintain black lists of JWT tokens.



Recommendations


When using JWT tokens (for example, if you work with Passport.js ), by default, there is no standard mechanism for revoking system access privileges for already issued tokens. Even if you find that a user is doing something obviously abnormal, you do not have a way, through the mechanism of tokens, to close his access to the system, as long as he has a valid token. This problem can be mitigated by implementing a black list of untrusted tokens, which are validated upon each request.

Here is a material about blacklists of JWT tokens

Possible problems


Tokens in the wrong hands can be used by an attacker. He will be able to access the application and impersonate a regular user - the owner of the token.

12. Limit the number of login attempts.



Recommendations


In applications based on express, to protect against attacks by brute force and dictionary attacks, you should use the appropriate middleware, such as express-brute . In a similar way it is necessary to protect particularly important routes, such as /adminor /login. Protection should be based on analyzing the properties of the request, such as the user name used in the request or other identifiers, such as parameters of the request body.

Here is a material on limiting the number of login attempts.

Possible problems


If the application does not limit the number of login attempts, then the attacker can, in automatic mode, send your system an unlimited number of login requests, for example, trying to gain access to a privileged account.

13. Run Node.js on behalf of a user who does not have root privileges.



Recommendations


An extremely common scenario is that when using Node.js, it runs as a root user with unlimited privileges. For example, this is how everything is configured by default in Docker containers. It is recommended that you create a user who does not have root-rights, and either build it into the Docker image, or run the process on behalf of this user by calling the container with the flag -u username.

Here is the material on running Node.js on behalf of a non-root user.

Possible problems


If Node.js is running as a root user, then an attacker who was able to run a script on the server gets unlimited possibilities on the local machine. Let's say he can change the settings iptableand redirect traffic to his own computer.

14. Limit the amount of data transmitted in the requests, using a reverse proxy server or middleware



Recommendations


The larger the amount of data in the request body, the more difficult it is for a single-threaded server to process such a request. Using large requests allows an attacker to overwhelm the server with unnecessary work and without sending him a huge number of requests (that is, without performing a DoS / DDoS attack). You can reduce the risk of such attacks by limiting the size of the bodies of incoming requests on a certain border system (on a firewall or load balancer), or by setting body-parser express to receive only packets containing a small amount of data.

Here is a material about limiting the amount of data transmitted in requests

Possible problems


If you do not limit the amount of data transmitted in the requests, the attacker can download the application to handle large requests. At this time, it will not be able to solve the tasks for which it is designed. This leads to a drop in performance and makes the application vulnerable to DoS attacks.

15. Avoid using the eval function in JavaScript



Recommendations


The function evalis evil because it allows you to execute arbitrary JS code passed to it during program execution. Moreover, the discussion here is not only about the fact that this code can slow down the operation of the application. This feature is a serious security threat, as it can get malicious JS-code sent to the server by an attacker.

In addition, you should avoid the constructor new Function. Functions setTimeoutand setIntervalnever need to pass a dynamically generated JS code.

Here is the material about eval

Possible problems


If someone finds a way to pass malicious JS code, in the form of text, a function, evalor some other similar JS mechanism, he will have full access to the page, to everything that can be done using JavaScript. This vulnerability is often associated with XSS attacks.

16. Do not overload single-threaded Node.js applications with malicious regular expressions.



Recommendations


Regular expressions, while convenient, are a threat to JavaScript applications in general, and in particular to the Node.js platform. Processing using a regular expression of what came from the user can create a huge load on the processor. For example, regular expression processing can be so inefficient that checking ten words can block an event cycle for a few seconds and overload the processor. Therefore, it is best to use third-party packages for checking string data, like validator.js , instead of writing your own regular expressions. You can use the safe-regex package to detect vulnerable regular expression patterns.

here anti-malware recurring material

Possible problems


Badly written regular expressions may be subject to a special kind of DoS attacks, during which the cycle of events is completely blocked. For example, in November 2017 it was discovered that the popular package momentis vulnerable to such attacks.

17. Avoid loading modules using variables.



Recommendations


Avoid importing files, the path to which is specified as a parameter, based on the considerations according to which this parameter can be set on the basis of user input. This rule can be expanded by including here and, in general, access to files (using fs.readFile()), and access to any other important resources using parameters obtained from the user. Using eslint-plugin-security allows you to detect such insecure patterns very early.

Here is a material about safe loading of modules.

Possible problems


The data sent to the server by the attacker may be in the parameters that are responsible for importing some files, among which may be a malicious file previously uploaded to the server. This data can also be used to access the system files already on the computer.

18. Run unsafe code in the sandbox.



Recommendations


If it is necessary to execute an external code that enters the application during its operation (for example, a certain plug-in), use the sandbox, which isolates and protects the main code from the plug-in code. Such an environment can be created using a dedicated process ( cluster.fork()), serverless environment, or a dedicated npm package acting as a sandbox.

Here's a story about running unsafe code in a sandbox.

Possible problems


Malicious code contained in the plugin can attack the application in a huge variety of ways. Among them are infinite loops, memory overload, access to environment variables, the contents of which should be kept secret.

19. Be especially careful when working with child processes.



Recommendations


Avoid, when possible, the use of child processes. If you can’t do without it, then check and clear the data transmitted by the user and used when launching such processes in order to reduce the risk of an attack on the command shell. In this case, give preference to the command child_process.execFilethat, by default, does not launch the command shell, creating a new process that corresponds to the running executable file passed to it.

Here is a material about the security measures necessary for working with child processes.

Possible problems


Reckless use of child processes can lead to remote execution of commands or an attack on the command shell, when the data transmitted by the user to the server, in an unpurified form, are transmitted to system commands.

20. Hide error information from clients



Recommendations


The express express error handler, by default, hides error information. However, there are great chances that you have implemented your own error handling system, created your own object Error(many recommend this approach). If you did this, make sure that the entire object is not sent to the client, as it may contain any application data that should not fall into the wrong hands.

Here's a material about hiding error information from clients.

Possible problems


Application data that cannot be disclosed to outsiders, such as the paths to files on the server, information about used third-party modules, and other details about the internal device of the application that an attacker can use, may be among the stack trace data.

21. Configure two-factor authentication for npm or yarn.



Recommendations


Each step in the chain of tasks that constitutes the process of developing a web project should be protected by multi-factor authentication (MFA). Access to npm or Yarn can be quite interesting to an attacker who can find out the credentials of the developer. Using them, when it comes to internal resources, it can embed malicious code into libraries that are used in projects and services of the company. If such libraries are published in the public domain, the problem becomes much larger. Using two-factor authentication, for example, in npm, reduces to almost zero the chances of an attacker to intercept access to a developer account and to change the code of his packages.

Possible problems


What happens if an attacker gains access to a developer account? Here is a story about eslint developer who got into this situation.

22. Modify session parameters used by middleware.



Recommendations


Every web framework and every technology has known weaknesses. A great way to help an attacker who wants to attack your system is to inform him of exactly which web framework you are using. Using default session parameters, which is similar to using a header X-Powered-By, middleware can put your application at risk of being targeted by a specific module or framework. Try to hide everything that identifies the technology stack you are using (that is, for example, Node.js and express).

Here is a material about cookies and session security.

Possible problems


Cookies can be transmitted over unprotected connections. By intercepting them, an attacker can use the session data and identify the framework used by the web application, as well as the modules used in it. This will give him the opportunity to organize an attack on the weak points of the frameworks and modules.

23. General safety recommendations


The following list is a list of well-known important safety measures that should be used in any application. These are universal recommendations that apply not only to Node.js. Here you can find an extended list of recommendations grouped according to the OWASP classification .

  • For user accounts with root-rights, be sure to use two-factor or multifactor authentication.
  • Organize frequent change of passwords and access keys (including SSH keys).
  • Use rules that allow you to create crack-resistant passwords that are used by both developers and users of applications. Here are the OWASP password guidelines.
  • Do not deploy applications using the credentials left in the defaults. In particular, this concerns the credentials of system administrators.
  • Use only standard authentication methods like OAuth, OpenID, and so on. Try not to use the Basic Authentication mechanism.
  • Limit the frequency of authentication attempts. This looks like a limitation on the number of operations related to authentication (including password recovery operations and other similar operations) to a certain parameter X over a period of time, duration Y minutes.
  • If the attempt to log in fails, do not inform the user about what is entered incorrectly - the name or password. Just tell him that the login failed.
  • Consider using a reliable, time-tested, centralized user management system. This will avoid the need to manage multiple accounts for each employee (for example, these are GitHub, AWS, Jenkins, and so on).

Results


Security is never too much. We hope this material will help you in protecting your Node.js projects.

Dear readers! Have you encountered web-project vulnerabilities that led to real security problems?


Also popular now: