Skip to main content

Command Palette

Search for a command to run...

NOSQL INJECTIONS

Updated
6 min readView as Markdown
NOSQL INJECTIONS
J
Love security all kinds of infrastructure

WHAT IS A NOSQL DATABASE?

NoSQL databases are designed for semi-structured, flexible data. They store and retrieve data in a different format from SQL.


TYPES OF NOSQL DATABASE MODELS

  • Document stores: Stores data in flexible, semi-structured documents. Typically use formats such as JSON, BSON or XML. They are queried in an API or query language. Examples of Document stores include MongoDB, Couchbase.

  • Key-value stores: Stores data in key:value format. Each data field is associated with a unique key string and values are retrieved based on the unique key. Examples include Redis, Amazon DynamoDB.

  • Wide-column stores: Organise related data into flexible column families rather than traditional rows. Examples include Apache Cassandra, Apache HBase.

  • Graph Databases: These databases use nodes to store data entities, edges to store relationships between data entities and properties to store extra details or attributes on both nodes and edges. Examples include Neo4j, Amazon Neptune.


WHAT IS NOSQL INJECTION?

NoSQL injection is a vulnerability where the attacker is able to interfere with queries an application makes to a NoSQL database. NoSQL injections may enable the attacker to:

  • Extract or edit data.

  • Bypass authentication or protection mechanisms.

  • DoS attacks.

  • Execute code on the server.


TYPES OF NOSQL INJECTION

There are 2 types of NoSQL injection

  1. Syntax injection: This occurs when the attacker can break the NoSQL query syntax, enabling them to inject their payload into the database.

  2. Operator injection: This occurs when the attacker uses NoSQL query operators to manipulate queries.


SYNTAX INJECTION

The attacker can potentially detect NoSQL injections by trying to break the query syntax. To achieve this, the attacker systematically tests each input by submitting a variety of fuzz strings and special characters to target multiple API languages which triggers a database error or an unexpected behaviour.

Let's consider a web application that looks up products by category using a MongoDB query. The server-side code might look like this:

// Vulnerable code
const category = req.query.category;
db.collection('products').find({ category: category });

A normal request might look like:

GET /products?category=electronics

This produces the MongoDB query:

db.collection('products').find({ category: "electronics" });

However, an attacker could submit a crafted input to manipulate the query logic:

GET /products?category[$ne]=null

This transforms the query into:

db.collection('products').find({ category: { $ne: null } });

Instead of returning products in a single category, this query returns every product where the category field is not null.


OPERATOR INJECTION

NoSQL databases often use query operators, which provide ways to specify conditions that the data must meet to be included in the query result. It is possible to inject query operators to manipulate NoSQL queries. To do this, the attacker systematically submits different operators into a range of user inputs, then reviews the responses for error messages.

For example, consider an application that allows users to search for accounts by providing an account balance threshold:

// Vulnerable code
const minBalance = req.query.minBalance;

db.collection('accounts').find({ balance: { $gt: minBalance } });

An attacker could manipulate this by injecting a $gt operator with a value of 0 to return all accounts with any positive balance, exposing sensitive financial data.

The $where operator is dangerous because it allows arbitrary JavaScript execution on the server:

// Attacker submits this payload
{
  "$where": "sleep(5000)"
}

This forces the database to execute a five-second sleep on every document evaluation, enabling a denial-of-service attack. More severe payloads could extract data character by character:

{
  "$where": "this.password.charAt(0) === 'a'"
}

By iterating through each character position and testing every possible value, an attacker can reconstruct sensitive fields like passwords one character at a time.


ROCKET.CHAT NOSQL INJECTION ATTACK

In 2021, Rocket.Chat, an open-source communications platform, was involved in a NoSQL injection attack. Their backend did not properly validate or restrict user input into their NoSQL database. Because of this, malicious actors were able to reset the administrator's password, gain full administrative privileges, and take over the host server.

The vulnerability was assigned CVE-2021-22911. To read more about this, visit the original disclosure: https://hackerone.com/reports/1130874


PREVENTING NOSQL INJECTION

Security measures depend on the specific NoSQL database used. As such, developers are recommended to read the security documentation for their NoSQL database. The following general guidelines should be followed for all NoSQL databases:

  • Sanitize and validate user input using an allow list of accepted characters and reject any input that does not conform.

    // Example: reject any input containing query operator characters
    function sanitizeInput(input) {
      if (typeof input !== 'string') {
        throw new Error('Invalid input type');
      }
      if (input.match(/[\$\{\}]/)) {
        throw new Error('Invalid characters in input');
      }
      return input;
    }
    
  • Insert user input using parameterized queries instead of concatenating user input directly into the queries.

    // Vulnerable: directly inserting user input
    db.collection('users').findOne({ username: req.body.username });
    
    // Secure: using MongoDB's query builder with sanitized, typed input
    const sanitizedUser = String(req.body.username);
    db.collection('users').findOne({ username: { $eq: sanitizedUser } });
    
  • To prevent operator injection, apply an allow list of accepted keys. Reject any keys that start with $ or are not on the predefined allow list.

    function rejectOperators(obj) {
      for (const key of Object.keys(obj)) {
        if (key.startsWith('$')) {
          throw new Error('Query operators are not allowed in user input');
        }
        if (typeof obj[key] === 'object' && obj[key] !== null) {
          rejectOperators(obj[key]); // recursively check nested objects
        }
      }
    }
    
    rejectOperators(req.body);
    
  • Configure the database accounts used by the application with the minimum permissions required. This limits the damage if an injection succeeds.

    // Create a restricted MongoDB user for the application
    db.createUser({
      user: "appReadOnly",
      pwd: "securepassword",
      roles: [{ role: "read", db: "production" }]
    });
    
  • Enforce strict type checking. Every input should be the expected type before it reaches the database.

    // Vulnerable: accepts whatever the client sends
    const username = req.body.username;
    
    // Secure: explicitly cast to string
    const username = String(req.body.username);
    
    // Even better: use a validation library like Joi or Zod
    const schema = z.object({
      username: z.string().min(1).max(50),
      password: z.string().min(8).max(128),
    });
    
    const validated = schema.parse(req.body);
    
  • Disable server-side JavaScript execution. Only disable if the application does not require $where operator or mapReduce. This can be configured at the server-level in MongoDB.

    # mongod.conf
    security:
      javascriptEnabled: false
    
  • Log and monitor for injection attempts. Enable alerting so the team can investigate potential attacks in real time.