Avoiding mass assignment vulnerabilities in Node.js
March 28, 2023
0 mins readMass assignment is a vulnerability that allows attackers to exploit predictable record patterns and invoke illegal actions. Mass assignment usually occurs when properties are not filtered when binding client-provided data-to-data models. Vulnerabilities of this type allow an attacker to create additional objects in POST request payloads, allowing them to modify properties that should be immutable.
Node.js is an open source server environment that can be used cross-platform for web application development. It enables developers to build powerful and scalable applications quickly. At its core, Node.js is a battle-tested and production-ready platform — application security challenges come with supply chain security associated with third-party packages installed to build applications. Creating a Node.js application can sometimes use thousands of open source npm packages from the npm registry, opening it up to many different attack vectors.
Mass assignment vulnerabilities are common in Node.js applications that send payloads in POST requests to the database. An attacker can use these vulnerabilities to orchestrate SQL injection attacks or other attacks directed at data contained in databases. An attacker can use a mass assignment vulnerability to take complete control of a system or steal sensitive data, making it essential to defend against this kind of attack.
This article will demonstrate a mass assignment vulnerability in a Node.js project, how an attacker can exploit it, and ways to protect a web application against it.
Removing mass assignment vulnerabilities from Node.js
This example uses Mongoose, an Object Data Modelling (ODM) library for Node.js and MongoDB. It coordinates objects in code and objects in MongoDB, validates schema, and helps manage data relationships.
Based on the information above, if you want to protect mass assignment from a Node.js MongoDB application, you must do it in Mongoose. Below is an example of exploiting mass assignment vulnerability in a Node.js application.
Prerequisites
To follow along with this project, you will need the following:
Node.js installed
Access to a MongoDB database and a valid connection string. This tutorial relies on a MongoDB Atlas cloud environment, but you can spin-off a local MongoDB server and update the connection string respectively.
Mass assignment in Node.js
To get started with this project, open your terminal and run this command:
Next, you need to install the dependencies:
To handle the data in the form above, we need a newUser.js file under the models folder to handle the user schema.
This model captures all the fields in the registration form. The extra field isAdmin, of type Boolean defines a user’s role. If you set this field to true, you create a user with administrator privileges, and if you set it to false, you create a user without administrator privileges.
Create a new directory called routes and add the file routes.js to handle user details:
The code above captures the data in the form, then structures it with the newUSer model.
This code has two flaws that introduce a mass assignment vulnerability. First, it uses a common name and type for a sensitive field — isAdmin. Second, the user model contains a sensitive, unused field. To exploit the vulnerability, let’s start a server.
Create a file named server.js in the project root folder and add the following code, replacing the <APP_ID> with the value in your connection string:
Start the server by running node server.js.
A malicious actor can exploit this vulnerability by creating a curl POST request containing the isAdmin field with a value set to true, as shown in the code below:
The request above creates a user, Mike, with admin privileges. This user can now use their credentials to log in to the system and do anything an administrator can.
Defending against mass assignment
We can make three adjustments to increase the application's defense against mass assignment attacks.
Create lean models
Lean models only include what a user is expected to add as inputs, and they exclude sensitive fields.
From the example above, the first and most essential step we can take is to remove the sensitive field from the newUser model, as shown below:
Now, if attackers gain access to the source code, they won’t be able to view the sensitive field and use it to orchestrate an attack. You should also avoid using predictable terms in the database to define a user's role — such as isAdmin, admin, and role — because an attacker may still be able to guess them and launch an attack.
Use schema validation for user input
Validating using underscore
Another way to protect your app is to specify and limit the fields a POST request can handle. This is known as allow-listing and uses the underscore library. First, install the library:
Navigate to the routes folder and replace the route.js code with this:
In the code above, we use the pick function to specify the variables extracted from a POST request. This prevents an attacker from using the sensitive isAdmin field.
The challenge with using underscore is that the application must process the data to know whether it's valid. In other words, it doesn’t catch the errors early enough.
Validating using Zod
For robust schema validation, you should use Zod. It validates the schema assuring that the data strictly meets the specified structure, pattern, and data type. Zod identifies incomplete or incorrect data early enough to prevent application errors. Additionally, you can easily create an error message to guide the user on the inputs causing an error and the type of error.
The code snippet demonstrates how Zod works. Note that the following snippet is in Typescript, which requires some extra configuration.
If a user tries to enter data that doesn’t match the schema, they will get an error stating they’ll get an invalid_type_error. If they leave a field empty, they’ll get a required_error.
More mass assignment vulnerability examples in ORMs
Let’s look at three more examples of mass assignment vulnerabilities in common ORM solutions.
Mass assignment vulnerability in Sequelize code
Let’s look at an example of Sequelize code that’s vulnerable to mass assignment. This code defines a user model, creates a user, and saves user details to the database:
When creating a new user, the controller calls the model above, as shown below:
The code above is vulnerable because it exposes the sensitive field is_verified. An attacker can create a POST request with the is_verified field set as true, allowing them to bypass the verification step.
We can remove the mass assignment vulnerability in this code by removing the is_verified field when creating the user. The safe code would look like this:
This limits the acceptable POST request variables to username, email, and password.
Mass assignment vulnerability in Prisma code
Below is an example of a mass assignment vulnerable Prisma code that exposes the sensitive field Role.
To add a user, we create a route add_user.
This POST request will expect the body with a user object. The code above is vulnerable to mass assignment attacks because a hacker can add the field role to ADMIN to gain administrator privileges.
We can remove the vulnerability in the code above by excluding the sensitive field Role from the model.
Mass assignment vulnerability in MySQL code
Here’s an example of mass assignment-vulnerable MySQL code that exposes the sensitive field isAdmin:
This code adds the user defined above:
The code above has a mass assignment vulnerability because it exposes a sensitive field — isAdmin — and allows us to include it in a POST request. An attacker can create a POST request that sets the isAdmin field to be true, creating an admin user.
We can fix this vulnerability by excluding the sensitive field when creating the user object. Here’s the safe code:
The default value of false for the isAdmin field can be set by altering the table using the following command:
Keeping your code safe in Node.js
Node.js is secure, but sometimes the packages required to build a web application aren’t. For Node.js applications that send requests to a database, the mass assignment vulnerability is one of the most common attack vectors. It allows malicious users to perform SQL injection attacks, which involve sending malicious requests to a database.
However, sanitizing inputs can easily prevent mass assignment in Node.js. You can do this by not exposing sensitive fields, limiting acceptable fields, or blocking the editing of some fields.
Failure to implement robust user input validation exposes a system to attacks such as prototype pollution. Such attacks can bypass weak validation mechanisms such as underscore. Therefore, always ensure you use robust solutions such as Zod.
Check out the complete source code example in our GitHub repository, and run your own local security experiments for how to avoid mass assignment vulnerability in Node.js.
Follow Snyk’s Top 10 Node.js Security Best Practices to optimize the security of your Node.js application.
Get started in capture the flag
Learn how to solve capture the flag challenges by watching our virtual 101 workshop on demand.
