25 vulnerabilities to look out for in Node JS applications: Directory traversal, prototype pollution, XSSI, and more…
Securing applications is not the easiest thing to do. An application has many components: server-side logic, client-side logic, data storage, data transportation, API, and more. With all these components to secure, building a secure application can seem really daunting.
Thankfully, most real-life vulnerabilities share the same root causes. And by studying these common vulnerability types, why they happen, and how to spot them, you can learn to prevent them and secure your application.
The use of every language, framework, or environment exposes the application to a unique set of vulnerabilities. The first step to fixing vulnerabilities in your application is to know what to look for.
Today, let’s take a look at 25 of the most common vulnerabilities that affect Node.js applications, and how you can find and prevent them. The vulnerabilities I will cover in this post are:
- Prototype pollution
- Cross-site script inclusion (XSSI)
- Insecure puppeteer settings
- Security misconfiguration
- Remote code execution (RCE)
- SQL injection
- Log injection
- Mail injection
- Template injection (SSTI)
- Regex injection
- Header injection
- Session injection
- Host header poisoning
- Sensitive data leaks or information leaks
- Authentication bypass
- Improper access control
- Directory traversal or path traversal
- Arbitrary file writes
- Denial of service attacks (DoS)
- Encryption vulnerabilities
- Mass assignment
- Open redirects
- Cross-site request forgery (CSRF)
- Server-side request forgery (SSRF)
- Trust boundary violations
Prototype Pollution
JavaScript is a unique language with many idiosyncrasies. One of these characteristics that set it apart from other mainstream languages is how objects are created in Javascript. Rather than being instantiated from classes, objects in Javascript inherit their properties from an existing object, or a “prototype”.
From a security perspective, this means that if an attacker can modify the prototype object and its properties, the prototype object can then affect the properties of all objects created from that prototype. This can lead to anything from cross-site scripting (XSS) attacks in the browser, to remote code execution (RCE) attacks in Node.js applications. Learn how these attacks work and how to prevent them here.
Cross-Site Script Inclusion
Cross-site script inclusion attacks are also referred to as XSSI. These attacks happen when a malicious site includes Javascript from a victim site to extract sensitive info from the script.
The same-origin policy (SOP) usually controls data access cross-origins. But the SOP does not limit javascript code, and the HTML tag is allowed to load Javascript code from any origin. This is an extremely convenient feature that allows JavaScript files to be reused across domains. But this feature also poses a security risk: attackers might be able to steal data written in JavaScript files by loading the JS files of their victims.
For example, imagine that a website stores and transports sensitive data for logged-in users via Javascript files. If a user visits a malicious site in the same browser, the malicious site can import that JavaScript file and gain access to sensitive information associated with the user’s session, all thanks to the user’s cookies stored in the browser. See an example of this vulnerability and learn how to prevent them here.
Insecure Puppeteer Settings
Insecure Puppeteer settings are another concern for Node applications. Puppeteer is a Node library that allows applications to control a headless build of Chrome or Chromium programmatically. Puppeteer helps automate user testing by mimicking activities that users can do in the browser. For example, you can automate the testing of form submissions, keyboard input, and many other user actions.
It’s important to sandbox the browser that Puppeteer runs, since the headless browser may have access to the disk or the internal network. Read how to do that in this post.
Security Misconfiguration
Insecure Puppeteer settings are essentially a type of security misconfiguration. There are many other types of security misconfigurations that can compromise the security of Node applications. These can include using default credentials, using the wrong HTTP security headers, exposing sensitive system information via error messages, or disabling built-in safety measures. Learn about some of the most common security misconfigurations in Node applications here.
Remote Code Execution
Remote code execution vulnerabilities, or RCE, are a class of vulnerabilities that happen when attackers can execute their code on your machine. One of the ways this can happen is through code injection vulnerabilities. They are a type of remote code execution that happens when user input is concatenated directly into executed code. The application cannot distinguish between where the user input is and where the executable code is, so the application executes the user input as code. The attacker will be able to execute arbitrary Javascript code through the application.
One of the easiest ways to prevent code injection is to implement robust input validation in the form of an allowlist. Read about how to implement allowlists and which Javascript methods you should avoid to prevent RCE here.
Injection
Code injection is also a type of injection issue. Injection happens when an application cannot properly distinguish between untrusted user data and code. When injection happens in Javascript code, it leads to code injection. But injection vulnerabilities manifest in other ways too.
SQL Injection
In an SQL injection attack, for example, the attacker injects data to manipulate SQL commands. When the application does not validate user input properly, attackers can insert characters special to the SQL language to mess with the query’s logic, thereby executing arbitrary SQL code. Learn more about how these SQL injection attacks work here.
SQL injections allow attacker code to change the structure of your application’s SQL queries to steal data, modify data, or potentially execute arbitrary commands in the underlying operating system. The best way to prevent SQL injections is to use parameterized statements, which makes SQL injection virtually impossible. Learn about how to use parameterized statements in this article.
Log Injection
You probably conduct system logging to monitor for malicious activities going on in your network. But have you ever considered that your log file entries could be lying to you? Log files, like other system files, could be tampered with by malicious actors. Attackers often modify log files to cover up their tracks during an attack. Log injection is one of the ways attackers can change your log files. It happens when the attacker tricks the application into writing fake entries in your log files.
Log injection often happens when the application does not sanitize newline characters “\n” in input written to logs. Attackers can make use of the new line character to insert new entries into application logs. Another way attackers can exploit user input in logs is that they can inject malicious HTML into log entries to attempt to trigger an XSS on the browser of the admin who views the logs.
To prevent log injection attacks, you need a way to distinguish between real log entries, and fake log entries injected by the attacker. One way to do this is by prefixing each log entry with extra meta-data like a timestamp, process ID, and hostname. You should also treat the contents of log files as untrusted input and validate it before accessing or operating on it.
Mail Injection
Many web applications send emails to users based on their actions. For instance, if you subscribed to a feed on a news outlet, the website might send you a confirmation with the name of the feed.
Mail injection happens when the application employs user input to determine which addresses to send emails to. This can allow spammers to use your server to send bulk emails to users or enable scammers to conduct social engineering campaigns via your email address. Learn how attackers can achieve mail injection and how you can prevent it here.
Template Injection
Template engines are a type of software used to determine the appearance of a web page. These web templates, written in template languages such as Jinja, provide developers with a way to specify how a page should be rendered by combining application data with web templates. Together, web templates and template engines allow developers to separate server-side application logic from client-side presentation code during web development.
Template injection refers to injection into web templates. Depending on the permissions of the compromised application, attackers might be able to use the template injection vulnerability to read sensitive files, execute code, or escalate their privileges on the system. Learn how template injection work and how to prevent them in this post.
Regex Injection
A regular expression, or regex, is a special string that describes a search pattern in text. Sometimes, applications let users provide their own regex patterns for the server to execute or build a regex with user input. A regex injection attack, or a regular expression denial of service attack (ReDoS), happens when an attacker provides a regex engine with a pattern that takes a long time to evaluate. You can find examples of these patterns in my post here.
Thankfully, regex injection can be reliably prevented by not generating regex patterns from user input, and by constructing well-designed regex patterns whose required computing time does not grow exponentially as the text string grows. You can find some examples of these preemptive measures here.
Header Injection
Header injection happens when HTTP response headers are dynamically constructed from untrusted input. Depending on which response header the vulnerability affects, header injection can lead to cross-site scripting, open redirect, and session fixation.
For instance, if the Location
header can be controlled by a URL parameter, attackers can cause an open redirect by specifying their malicious site in the parameter. Attackers might even be able to execute malicious scripts on the victim’s browser, or force victims to download malware by sending completely controlled HTTP responses to the victim via header injection. More about how these attacks work here.
You can prevent header injections by avoiding writing user input into response headers, stripping new-line characters from user input (newline characters are used to create new HTTP response headers), and using an allowlist to validate header values.
Session Injection
Session injection is a type of header injection. If an attacker can manipulate the contents of their session cookie, or steal someone else’s cookies, they can trick the application into thinking that they are someone else. There are three main ways that an attacker can obtain someone else’s session: session hijacking, session tampering, and session spoofing.
Session hijacking refers to the attacker stealing someone else session cookie and using it as their own. Attackers often steal session cookies with XSS or MITM (man-in-the-middle) attacks. Session tampering refers to when attackers can change their session cookie to change how the server interprets their identity. This happens when the session state is communicated in the cookie and the cookie is not properly signed or encrypted. Finally, attackers can “spoof” sessions when session IDs are predictable. If that’s the case, attackers can forge valid session cookies and log in as someone else. Preventing these session management pitfalls requires multiple layers of defense.
Host Header Poisoning
Web servers often host multiple different websites on the same IP address. After an HTTP request arrives at an IP address, the server will forward the request to the host specified in the Host header. Although Host headers are typically set by a user’s browser, it’s still user-provided input and thus should not be trusted.
If a web application does not validate the Host header before using it to construct addresses, attackers can launch a range of attacks, like XSS, server-side request forgery _(_SSRF), and web cache poisoning attacks via the Host header. For instance, if the application uses the Host header to determine the location of scripts, the attacker could submit a malicious Host header to make the application execute a malicious script:
scriptURL = "https://" + properties.getProperty("host") + "/script.js";
Learn more about how Host header attacks work here.
Sensitive Data Leaks
Sensitive data leak occurs when an application fails to properly protect sensitive information, giving users access to information they shouldn’t have available to them. This sensitive information can include technical details that aid an attack, like software version numbers, internal IP addresses, sensitive filenames, and file paths. It could also include source code that allows attackers to conduct a source code review on the application. Sometimes, the application leaks private information of users, such as their bank account numbers, email addresses, and mailing addresses.
Some common ways that an application can leak sensitive technical details are through descriptive response headers, descriptive error messages with stack traces or database error messages, open directory listings on the system’s file system, and revealing comments in HTML and template files. You can learn how to prevent data leaks in Node applications here.
Authentication Bypass
Authentication refers to proving one’s identity before executing sensitive actions or accessing sensitive data. If authentication is not implemented correctly on an application, attackers can exploit these misconfigurations to gain access to functionalities they should not be able to. For more details about how you can configure authentication properly in Node, read this tutorial.
Improper Access Control
Authentication bypass issues are essentially improper access control. Improper access control occurs anytime when access control in an application is improperly implemented and can be bypassed by an attacker. However, access control comprises of more than authentication. While authentication asks a user to prove their identity: “Who are you?”, authorization asks the application “What is this user allowed to do?”. Proper authentication and authorization together ensure that users cannot access functionalities outside of their permissions.
There are several ways of configuring authorization for users: role-based access control, ownership-based access control, access control lists, and more. A good post to reference for implementing access control is here.
Directory Traversal
Directory traversal vulnerabilities are another type of improper access control. They happen when attackers can view, modify, or execute files they shouldn’t have access to by manipulating file paths in user-input fields. This process involves manipulating file path variables the application uses to reference files by adding the ../
characters or other special characters to the file path. The ../
sequence refers to the parent directory of the current directory in Unix systems, so by adding it to a file path, you can often reach system files outside the web directory.
Attackers can often use directory traversals to access sensitive files like configuration files, log files, and source code. To prevent directory traversals, you should validate user input that is inserted into file paths, or avoid direct references to file names and use indirect identifiers instead, read this tutorial for more information.
Arbitrary File Writes
Arbitrary file write vulnerabilities work similarly to directory traversals. If an application writes files to the underlying machine and determines the output file name via user input, attackers might be able to create arbitrary files on any path they want, or overwrite existing system files. Attackers might be able to alter critical system files like password files or log files, or add their own executables into script directories.
The best way to mitigate this risk is by not creating file names based on any user input, including session information, HTTP input, or anything that the user controls. You should control the file name, path, and extension for every created file. For instance, you can generate a random alphanumeric filename every time the user needs to generate a unique file. You can also strip user input of special characters before creating the file. Learn about these techniques in this post.
Denial of Service Attacks
Denial of service attacks, or DoS attacks, disrupts the target machine so that legitimate users cannot access its services. Attackers can launch DoS attacks by exhausting all the server’s resources, crashing processes, or making too many time-consuming HTTP requests at once.
Denial of service attacks are hard to defend against. But there are ways to minimize your risk by making it as difficult as possible for attackers. For instance, you can deploy a firewall that offers DoS protection, and prevent logic-based DoS attacks by setting limits on file sizes and disallowing certain file types. You can find more detailed steps on preventing denial of service attacks here.
Encryption Vulnerabilities
Encryption issues are probably one of the most severe vulnerabilities that can happen in an application. Encryption vulnerabilities refer to when encryption and hashing are not properly implemented. This can lead to widespread data leaks and authentication bypass through session spoofing.
Some common mistakes developers make when implementing encryption on a site are:
- Using weak algorithms
- Using the wrong algorithm for the purpose
- Creating custom algorithms
- Generating weak random numbers
- Mistaking encoding for encryption
A guide to encryption security can be found here.
Mass Assignment
“Mass assignment” refers to the practice of assigning values to multiple variables or object properties all at once. Mass assignment vulnerabilities happen when the application automatically assigns user input to multiple program variables or objects. This is a feature in many application frameworks designed to simplify application development.
However, this feature sometimes allows attackers to overwrite, modify, or create new program variables or object properties at will. This can lead to authentication bypass, and manipulation of program logic. To prevent mass assignments, you can disable the mass assignment feature with the framework you are using, or use a whitelist to only allow assignment on certain properties or variables.
Open Redirects
Websites often need to automatically redirect their users. For example, this scenario happens when unauthenticated users try to access a page that requires logging in. The website will usually redirect those users to the login page, and then return them to their original location after they are authenticated.
During an open-redirect attack, an attacker tricks the user into visiting an external site by providing them with a URL from the legitimate site that redirects somewhere else. This can lead users to believe that they are still on the original site, and help scammers build a more believable phishing campaign.
To prevent open redirects, you need to make sure the application doesn’t redirect users to malicious locations. For instance, you can disallow offsite redirects completely by validating redirect URLs. There are many other ways of preventing open redirects, like checking the referrer of requests, or using page indexes for redirects. But because it’s difficult to validate URLs, open redirects remain a prevalent issue in modern web applications.
Cross-Site Request Forgery
Cross-site request forgery (CSRF) is a client-side technique used to attack other users of a web application. Using CSRF, attackers can send HTTP requests that pretend to come from the victim, carrying out unwanted actions on a victim’s behalf. For example, an attacker could change your password or transfer money from your bank account without your permission.
Unlike open redirects, there is a surefire way of preventing CSRF: using a combination of CSRF tokens and SameSite cookies, and avoid using GET requests for state-changing actions.
Server-Side Request Forgery
SSRF, or Server Side Request Forgery, is a vulnerability that happens when an attacker is able to send requests on behalf of a server. It allows attackers to “forge” the request signatures of the vulnerable server, therefore assuming a privileged position on a network, bypassing firewall controls, and gaining access to internal services.
Depending on the permissions given to the vulnerable server, an attacker might be able to read sensitive files, make internal API calls, and access internal services like hidden admin panels. The easiest way to prevent SSRF vulnerabilities is to never make outbound requests based on user input. But if you do need to make outbound requests based on user input, you’ll need to validate those addresses before initiating the request.
Trust Boundary Violations
“Trust boundaries” refer to where untrusted user input enters a controlled environment. For instance, an HTTP request is considered untrusted input until it has been validated by the server.
There should be a clear distinction between how you store, transport, and process trusted and untrusted input. Trust boundary violations happen when this distinction is not respected, and trusted and untrusted data are confused with each other. For instance, if trusted and untrusted data are stored in the same data structure or database, the application will start confusing the two. In this case, untrusted data might be mistakenly seen as validated.
A good way to prevent trust boundary violation is to never write untrusted input into session stores until it is verified. See an example of this mitigation implemented here.
What other security concepts do you want to learn about? I’d love to know. Feel free to connect on Twitter @vickieli7.
Now that you know how to fix these vulnerabilities, secure your Node.js application by scanning for these vulnerabilities! ShiftLeft CORE can find these vulnerabilities in your application, show you how to fix these bugs, and protect you from Node.js security issues.