# The Power of Secure Coding Practices: Safeguarding MongoDB Against Exploitation

Hello everyone, I need to tell a little story first:

Several months ago I was working on a side project for automating the election results display. I had a quick development process and published the website with [Ampt](https://ampt.dev/) and the database server. My Security Researcher friend Sven immediately sent the URL he exploited the application and I was kind of surprised as I was using default queries nothing very specific but the result was there, he was blocking my application to proceed with data with fun! Thanks, Sven :)

I understood that there are many vulnerabilities out there but even the most common code examples we read, copy and even run out there were not safe enough.

> Kein system ist sicher!

Before starting I would like to suggest taking a look at **CWE-943: Improper Neutralization of Special Elements in Data Query Logic** to understand what is NoSQL injection and how it related to MongoDB.

You may have read my level-up advice for development environment setup post, here is another database with docker-compose:

```yaml
version: '3'

services:
  database:
    image: mongo
    ports:
      - "27017:27017"
```

Run MongoDB without authentication, only for experimental purposes, please activate authentication for production!

I will use [MongoDB Compass GUI](https://www.mongodb.com/products/compass) to show a little bit about MongoDB queries.

I have a document - not a table in NoSQL we call it a document not the same but closest naming - called `users`.

This is how it looks like as I am not filtering any data:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1689103641814/1931664d-9c20-448a-ae8d-b7fa5e8547ba.png align="center")

I would like to filter Jane Smith with email and the query will be:  
`{ $where: "this.email == 'janesmith@example.com'" }`.

So far:

* We know how to write a MongoDB Query.
    
* We know how to filter documents.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1689103662596/be0426ca-abec-4493-ba50-035da6bbe749.png align="center")

As you can see from the query there is a comparison and when the expression for the MongoDB operator `$where` evaluates to `true` the data will be accessed.

We will be replacing the email address of Jane Smith with: `'||'true` to escape from the original query and force every `$where` the operator is to be calculated as `true`.  
The first single quote will create an empty string, which forces the email address to be compared to an empty string and will be evaluated as `false`. We also concatenated Logical OR and `'true'` where the final expression will fallback to `false || true`.

```bash
{ $where: "this.email == 'janesmith@example.com'" }
{ $where: "this.email == ''||'true'" }
```

When you run the injected email value, you will obtain all data from the `sample.users` document as shown below:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1689103702709/fc1cafa9-85d3-464e-a8d1-794be6687df8.png align="center")

So far I showed how to exploit a basic MongoDB query, you should know that this is not the only way to exploit it. **For this post, I will be focusing on this boolean-based injection payload.**

Let's create a MongoDB-Express application to have a real-life example:

```javascript
const express = require('express');
const mongoose = require('mongoose');

const app = express();

const mongoURL = 'mongodb://localhost:27017/sample'; 

const userSchema = new mongoose.Schema({
    name: String,
    email: String,
    admin: Boolean
});

const User = mongoose.model('User', userSchema);

async function connectToDatabase() {
    try {
        await mongoose.connect(mongoURL, {
            useNewUrlParser: true,
            useUnifiedTopology: true
        });
        console.log('Connected to MongoDB');
    } catch (err) {
        console.error('Failed to connect to MongoDB:', err);
        process.exit(1);
    }
}

app.get('/', (req, res) => {
    res.send('Running MongoDB with Node.js');
});

app.get('/users', async (req, res) => {
    await connectToDatabase();

    const email = req.query.email;

    try {
        const query = { $where: `this.email == '${email}'` };
        const users = await User.find(query);
        if ( users.length === 0 ) {
            res.status(404).json({ error: 'User not found' });
            return;
        }
        res.status(200).json(users);
    } catch (err) {
        console.error('Failed to retrieve users:', err.message);
        console.group('Stack trace');
        console.error(err);
        console.groupEnd();
        res.status(500).json({ error: 'Internal server error' });
    }
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});
```

For this application we will have an endpoint:

* [http://localhost:3000/users?email=janesmith@example.com](http://localhost:3000/users?email=janesmith@example.com) will return "User not found" error.
    
* [http://localhost:3000/users?email=%27%7C%7C%27true](http://localhost:3000/users?email=%27%7C%7C%27true) will return all of the data from users document
    

The decoded query value `%27%7C%7C%27true` is equal to `'||'true`. Browsers will decode automatically.

Let's focus on the code:  
We are assigning email variable with this line:  
`const email = req.query.email;` Here is the first mistake most developers do. We are getting data from outside of the application, users, customers, frontend whatever you call it. We should sanitize the data to be sure it is ready to be used.  
There are some libraries to sanitize the data, for Express we can use [mongo-sanitize](https://www.npmjs.com/package/mongo-sanitize). Basically, we need to escape from the special characters before executing database queries. If we sanitize the boolean injection payload the query and the result will be like:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1689107790062/4dc66c67-7610-45f4-bd4e-727c0e97f313.png align="center")

Here is another suggestion for this case lets take a look at query construction:

```javascript
const query = { $where: `this.email == '${email}'` };
```

We are using single quotes for email value and it results in queries below:

```bash
{ $where: "this.email == 'janesmith@example.com'" }
{ $where: "this.email == ''||'true'" }
```

However, we could escape by using backticks to avoid injection payloads:

```bash
{ $where: "this.email == `janesmith@example.com`" }
{ $where: "this.email == `'||'true`" }
```

To conclude we are sanitizing inputs and using default query methods advised to avoid injections.

```javascript
const email = sanitize(req.query.email); // Sanitize input to avoid NoSQL injection
try {
    const users = await User.find({ email }); // use default query options to avoid NoSQL injection
    if ( users.length === 0 ) {
        res.status(404).json({ error: 'User not found' });
        return;
    }
    res.status(200).json(users);
} catch (err) {
```

  
I wanted to draw attention to the importance of writing secure code, as it is susceptible to vulnerabilities. Vulnerabilities arise from the improper neutralization of special elements in data query logic. Attackers can exploit this weakness to manipulate queries, modify selection criteria, append unauthorized commands, or obtain unintended results. To mitigate the risk, developers must focus on implementing rigorous input validation and sanitization techniques for No/SQL Injections. By adopting secure coding practices, such as parameterized queries and proper input handling, developers can fortify MongoDB applications and protect against NoSQL injection attacks.

Thanks for reading so far! Thanks, Sven for exploiting!
