Find the NoSQL injection in the Mongoose login
This Express login route passes the request body straight to a MongoDB query via Mongoose. Identify the vulnerability and how it is exploited, then describe the fix.
Constraints:
req.bodyis parsed JSON and fully attacker-controlledUser.findOneaccepts whatever object it is given
router.post("/login", async (req, res) => {
const login = req.body.username; // expected to be a string
const user = await User.findOne(login); // object passed straight through
// ...password check follows
});
Diagnose the cause.
NoSQL injection via incorrect type handling — username is never coerced to a string, so an attacker sends an object of MongoDB operators that changes query semantics and bypasses the lookup. Fix: cast to a string and query an explicit field instead of passing the raw request object.
- ✗Believing NoSQL is immune to injection because it lacks SQL syntax
- ✗Passing the raw request object into the DB driver without type coercion
- ✗Confusing it with a classic SQL injection or a race condition
- →How does a $regex operator on the password field let it be brute-forced character by character?
- →Why is casting to a string at the source more reliable than filtering out operators?
The vulnerability
req.body.username flows into findOne without a type cast. If the body is JSON, the client can send an object of MongoDB operators instead of a string:
const login = req.body.username; // expected a string
const user = await User.findOne(login); // got an object
The payload {"username": {"$ne": null}} returns the first matching user; a $regex on the password field lets an attacker brute-force it character by character. This is NoSQL injection through incorrect type handling.
The fix
Never pass the raw request object into the driver. Cast to a string and query an explicit field:
const login = String(req.body.username);
const user = await User.findOne({ username: login });
✅ Casting at the source guarantees a string reaches the query, not an object of operators.