Back to Blog
HackTheBox · 04 Jun 2026 · ~5 min read

Secure Notes

NoSQL Injection via MongoDB's $rename operator leading to Prototype Pollution — bypassing a localhost-only access control to retrieve the flag.

Medium Node.js MongoDB NoSQL Injection Prototype Pollution Access Control Bypass

Initial Recon

When opening the application, we can see a very simple note-taking system where users can create and edit notes.

Note-taking application frontend

Nothing particularly interesting appears from the frontend, so the next step is reviewing the source code.

Application source code

Finding the Flag Endpoint

Looking through the code, we find the endpoint responsible for returning the flag:

Flag endpoint source code

The /flag route is only accessible if the request comes from localhost.

Goal: Find a way to manipulate req.connection.remoteAddress so the application believes the request originates from 127.0.0.1.

Finding the Injection Point

Further down in the code we find the note update functionality:

Note update function source code

The application directly passes req.body into findByIdAndUpdate() without any validation or filtering.

NoSQL Injection: Since req.body is passed unsanitized into the MongoDB update call, we can inject MongoDB operators — giving us arbitrary control over the update behavior.

Testing MongoDB Operators

First, we create a note containing the values we want to inject later:

Creating a note with target values

After some testing, we discover that the $rename operator is allowed. This operator renames a field — but crucially, it also accepts dot-notation paths, which allows writing into nested objects like __proto__.

$rename operator test result

This renames the existing fields into properties inside __proto__:

{
    "$rename": {
        "address": "__proto__._peername.address",
        "family":  "__proto__._peername.family"
    }
}
json

Prototype Pollution

By abusing $rename, we write directly into __proto__, polluting the prototype chain. After the operation, any newly created object inherits:

{
    _peername: {
        address: "127.0.0.1",
        family:  "IPv4"
    }
}
js

The application uses these values when handling connections. As a result, req.connection.remoteAddress resolves to 127.0.0.1 for subsequent requests — even though we are a remote client.

Why it works: Node.js reads remoteAddress from the socket's _peername object. Polluting Object.prototype._peername causes every object that doesn't define _peername explicitly to inherit our injected value.

Getting the Flag

With the prototype polluted, we simply request the protected endpoint:

Request to /flag endpoint

Since the application now believes the request originated from localhost, the access control check is bypassed and the flag is returned.

Flag returned

Conclusion

The vulnerability exists because user-controlled data is passed directly into MongoDB update operations without any restrictions on which operators can be used.

By combining NoSQL Injection with the $rename operator, we perform Prototype Pollution — overwriting internal properties that Node.js uses to identify the client address — and bypass the localhost-only protection on /flag.

Vulnerabilities Used