Understanding NoSQL Injection and How to Prevent it
An injection is a security vulnerability that lets attackers take control of database queries. There is only one thing you can do, "SANITIZATION"
This article starts with "once upon a time" when I was learning MongoDB and thought that the schema less feature could be more secure than SQL Databases (SQL Injections). So I migrated all my projects to MongoDB and for the past few months I have been working on NoSQL Injection and writing a series of tutorials on it.
What Is An Injection
An injection is a security vulnerability that lets attackers take control of database queries through the unsafe use of user input. It can be used by an attacker to expose unauthorized information and odify data.
Let me give you a glimpse of NoSQL Injection first. Suppose, your application is accepting JSON username
and password
, so it can be bypassed by:
{
"username": { "$ne": "[email protected]" },
"password": { "$ne": "mymaliciouspassword" }
}
Now if on the backend you are using:
Model.findOne(req.body)
// or
Model.findOne({ username: req.body.username, password: req.body.password });
Then your application is vulnerable to NoSQL Injection.
How? Let's substitute those values:
Model
.findOne({
username: {
$ne: "[email protected]"
},
password: {
$ne: "mymaliciouspassword"
}
})
Now, if there is at least one document in the collection that doesn't have the same username and password as the attacker has passed, it can log in to your web application with the very first document that matches this criterion
Practical Example: https://mongoplayground.net/p/omLJSlWfR-w
Preventing NoSQL
There is only one thing you can do, "SANITIZATION" by casting the input to in specific type. Like in this case, casting username and password to String()
would work. As you know String()
on any object would be [object Object]
so I am directly substituting the value here:
Model.findOne({
username: "[object Object]",
password: "[object Object]"
})
In production, this would be the rarest document in the collection.
Practical Demonstration: https://mongoplayground.net/p/XZKEXaypJjQ
ExpressJS Middle-Ware Approach
Four months ago I had created a question StackOverflow (https://stackoverflow.com/questions/59394484/expressjs-set-the-depth-of-json-parsing), to which a user named x00 posted the answer about the solution of setting up the depth of parsing nested JSON body.
Practical Demonstration:
...
const depth_limit = 2; // the depth of JSON to parse
app.use(express.json())
const get_depth = (obj) => {
let depth = 0
for (const key in obj) {
if (obj[key] instanceof Object) {
depth = Math.max(get_depth(obj[key]), depth)
}
}
return depth + 1
}
const limit_depth = function(req, res, next) {
if (get_depth(req.body) > depth_limit) throw new Error("Possible NoSQL Injection")
next()
}
app.use(limit_depth)
...
Or if you want to use [object Object]
notation to prevent application crash. I personally recommend you to use this one:
...
let depth_limit = 1; // the depth of JSON to parse
app.use(express.json())
let limit_depth = (obj, current_depth, limit) => {
// traversing each key and then checking the depth
for (const key in obj) {
if (obj[key] instanceof Object) {
if (current_depth + 1 === limit) {
obj[key] = "[object Object]" // or something similar
} else limit_depth(obj[key], current_depth + 1, limit)
}
}
}
// middle-ware in action
app.use(function(req, res, next) {
limit_depth(req.body, 0, depth_limit);
next()
})
...
Middle-ware in action
Practical Demonstration: https://repl.it/@tbhaxor/Preventing-NoSQL-Injection-in-Express
If you have some other cool ideas, I would love to hear from you. You can either comment down here or contact me at the following