The perennial number 1 on the OWASP Top Ten list is injection. Originally SQL Injection, but nowadays when databases might not be relational, also NoSQL, including the MongoDB used in these materials.
There serious issues with injections are all derived from the program taking the user entered data at face value. First look at the following MongoDB code:
db.user.find().pretty()
{
"_id" : ObjectId("5e79fd3131f468145cb92c2b"),
"email" : "nmla@iba.dk",
"__v" : 0,
"created" : ISODate("2020-03-24T12:29:37.690Z"),
"firstName" : "Niels",
"lastName" : "Larsen",
"password" : "$2a$10$4qaT09Yyy9CdREncdaGGleA1b7pCjuWj7Do0kF8IZFfab1GXDuo0O"
}
{
"_id" : ObjectId("5ea18bb90cb6c8975e345410"),
"email" : "admin@iba.dk",
"__v" : 0,
"created" : ISODate("2020-04-23T12:36:09.718Z"),
"firstName" : "admin",
"lastName" : "Adm",
"password" : "$2a$10$z.q1y1DtKVpQl9JAjksl7uq.Hyn6e0AX/4dBBqSkm5jbgi0MHENV."
}
{
"_id" : ObjectId("5ea18bf90cb6c8975e345411"),
"email" : "doe@iba.dk",
"__v" : 0,
"created" : ISODate("2020-04-23T12:37:13.799Z"),
"firstName" : "anonymous",
"lastName" : "Anon",
"password" : "$2a$10$6tlgjPflyF0BqA2vYUB3WelLFVmUQjoy7Po1Y1oW1A7wsDKj.KKMu"
}
This is a read of all objects in the collection.
Now if we supply a query parameter such as
{email: "nmla@iba.dk"} we get
db.user.find({email: "nmla@iba.dk"}).pretty()
{
"_id" : ObjectId("5e79fd3131f468145cb92c2b"),
"email" : "nmla@iba.dk",
"__v" : 0,
"created" : ISODate("2020-03-24T12:29:37.690Z"),
"firstName" : "Niels",
"lastName" : "Larsen",
"password" : "$2a$10$4qaT09Yyy9CdREncdaGGleA1b7pCjuWj7Do0kF8IZFfab1GXDuo0O"
}
Let us assume we do not know any users, we could
enter the query param {email: {$ne:""}} to get
db.user.find({email: {$ne:""}}).pretty()
{
"_id" : ObjectId("5ea18bb90cb6c8975e345410"),
"email" : "admin@iba.dk",
"__v" : 0,
"created" : ISODate("2020-04-23T12:36:09.718Z"),
"firstName" : "admin",
"lastName" : "Adm",
"password" : "$2a$10$z.q1y1DtKVpQl9JAjksl7uq.Hyn6e0AX/4dBBqSkm5jbgi0MHENV."
}
{
"_id" : ObjectId("5ea18bf90cb6c8975e345411"),
"email" : "doe@iba.dk",
"__v" : 0,
"created" : ISODate("2020-04-23T12:37:13.799Z"),
"firstName" : "anonymous",
"lastName" : "Anon",
"password" : "$2a$10$6tlgjPflyF0BqA2vYUB3WelLFVmUQjoy7Po1Y1oW1A7wsDKj.KKMu"
}
{
"_id" : ObjectId("5e79fd3131f468145cb92c2b"),
"email" : "nmla@iba.dk",
"__v" : 0,
"created" : ISODate("2020-03-24T12:29:37.690Z"),
"firstName" : "Niels",
"lastName" : "Larsen",
"password" : "$2a$10$4qaT09Yyy9CdREncdaGGleA1b7pCjuWj7Do0kF8IZFfab1GXDuo0O"
}
Now this is very interesting with a node/Express application in mind. In such a case we might do
obj.find(query, null, sort);
Look at the way the query might be built.
let query = { email: req.body.email };
Whatever collection obj represents will be
queried with the query parameters held in query.
The normal way of getting the email address is letting the
user key it into a form and we, the developers get it in
req.body.email which we use directly to
build out query object. If the user is required
to key in an email address we build
query = {email: "nmla@iba.dk"}
and we are
at the second example above. But if the devious user enters
{$ne: ""} we build in stead
query = {email: {$ne: ""}}
and we must expect to read all object where the email addresses are not blank. Indeed if we do this natively in the MongoDB client we get the results as in the third example above. We have been had.
It seems however, that MongoDB with mongoose,
and also with mongo protects the naive
programmer by, in stead of the feared
query = {email: {$ne: ""}}
the code
{ email: req.body.email }
results in
{ email: '{$ne: 1}' }
the apostrophes make
{$ne: 1} into a string, and since that string does
not match any email address in the database, the devious person
loses.
This has been experimentally verified. There are tales of problems when the frontend is built by Angular, if not others, because the form data are transported as JavaScript objects and thus not protected by apostrophes.
It goes without saying that any decent test of a web application includes asserting that injection attempts fail.