Currently using MongoDB’s free tier, which has shared RAM, and up to 5GB of storage . So far, the overall DB usage has been less than 5MB.
Query Injection
Current State of Affairs
Currently have this protection implemented back in 2018:
/**
* @description Prevent a NoSQL Injection in the search parameters. This is
* achieved by deleting all query values that begin with `$`.
*/
export function sanitizeQuery(query: any) {
const keys = Object.keys(query);
for (let i = 0; i < keys.length; i++) {
if (/^\$/.test(query[keys[i]])) { delete query[keys[i]]; }
}
return query;
}
We can do better than roll out our own NoSQL injection protection; this is a common enough problem that libraries should have protections for.
MongoDB Injection Attacks
For MongoDB in a JS environment, one can set
security.javascriptEnabled
to false
in the mongod
configuration to
disallow JS execution on the server. Otherwise, an attacker can run
arbitrary JS in these MongoDB operations: $where
, mapReduce
,
$accumulator
, and $function
.
CodeQL did flag code like User.findOne({email: payload.email})
as
js/sql-injection
violation, and suggested User.findOne({email: { $eq: payload.email }})
instead. The use of eq
ensures that the input is interpreted as a literal value and not a query
object. Alternatively, checking that typeof payload.email === "string"
also provides protection against NoSQL injection.
has concrete examples of NoSQL injection attacks:
db.myCollection.find({
active: true,
$where: function() { return obj.credits - obj.debits < $userInput; }
});
// A string containing any of these unsanitized characters would cause a
// database error.
const maliciousInput = `' " \ ; { }`;
// If inserted into `$userInput`, the MongoDB instance would execute at
// 100% CPU usage for 10 seconds.
const injection = `0;var date=new Date(); do{curDate = new Date();}while(curDate-date<10000)`;
A sample NoSQL injection is navigating to
/trpc/fetchPublicCard?batch=1&input=%7B%220%22%3A%7B%22cardID%22%3A%7B%22%24ne%22%3A%22000000000000000000000000%22%7D%7D%7D
.
The user should not be able to execute a query like {cardID: {$ne: "000000000000000000000000" }
and fetch a card. The tRPC endpoint should
be able to strip out the non-literal card ID.
What about vanilla Express endpoints? CodeQL highlighted the injection
attack surfaces for endpoints like /login
, /account
,
/reset-password
, and /send-validation-email
and we fixed them. Seems like CodeQL’s queries are good at finding
vulnerabilities in Express, but not in tRPC.
NoSQL Injection Prevention Libraries
is similar to in
that it strips out keys that start with $
, but goes one step further
by stripping out such keys recursively.
sanitizes keys that begin with
either $
or .
. It can also be used as middleware allowing it to
sanitize fields such as req.body
, req.params
, req.headers
, and
req.query
. It can also be used in a non-middleware context, allowing
it to be a drop-in replacement for .
Doing app.use(mongoSanitize())
as advised by doesn’t prevent injection attacks via tRPC
.
Reading , I don’t think I’d have gotten to without the hint from . Does Mongoose’s
Model.findOne
internally use$where
and that’s why flags it?