|
1 | 1 | # NoSQL database query built from user-controlled sources (experimental) |
2 | | -If a database query (such as a SQL or NoSQL query) is built from user-provided data without sufficient sanitization, a malicious user may be able to run malicious database queries. |
| 2 | +If a database query is built from user-provided data without sufficient sanitization, a user may be able to run malicious database queries. |
3 | 3 |
|
4 | 4 | Note: This CodeQL query is an experimental query. Experimental queries generate alerts using machine learning. They might include more false positives but they will improve over time. |
5 | 5 |
|
6 | 6 |
|
7 | 7 | ## Recommendation |
8 | | -Most database connector libraries offer a way of safely embedding untrusted data into a query by means of query parameters or prepared statements. |
9 | | - |
10 | | -For NoSQL queries, make use of an operator like MongoDB's `$eq` to ensure that untrusted data is interpreted as a literal value and not as a query object. |
11 | | - |
| 8 | +Ensure that untrusted data is interpreted as a literal value and not as a query object, eg., by using an operator like MongoDB's `$eq`. |
12 | 9 |
|
13 | 10 | ## Example |
14 | | -In the following example, assume the function `handler` is an HTTP request handler in a web application, whose parameter `req` contains the request object. |
| 11 | +In the following example, an `express.js` application is defining two endpoints that permit a user to query a MongoDB database. |
15 | 12 |
|
16 | | -The handler constructs two copies of the same SQL query involving user input taken from the request object, once unsafely using string concatenation, and once safely using query parameters. |
| 13 | +In each case, the handler constructs two copies of the same query involving user input taken from the request object. In both handlers, the input is parsed using the `body-parser` library, which will transform the request data that arrives as a string to JSON objects. |
17 | 14 |
|
18 | | -In the first case, the query string `query1` is built by directly concatenating a user-supplied request parameter with some string literals. The parameter may include quote characters, so this code is vulnerable to a SQL injection attack. |
| 15 | +In the first case, `/search1`, the input is used as a query object. This means that a malicious user is able to inject queries that select more data than the developer intended. |
19 | 16 |
|
20 | | -In the second case, the parameter is embedded into the query string `query2` using query parameters. In this example, we use the API offered by the `pg` Postgres database connector library, but other libraries offer similar features. This version is immune to injection attacks. |
| 17 | +In the second case, `/search2`, parts of the input are converted to a string representation and then used with the `$eq` operator to construct a query object. |
21 | 18 |
|
22 | 19 |
|
23 | 20 | ```javascript |
24 | 21 | const app = require("express")(), |
25 | | - pg = require("pg"), |
26 | | - pool = new pg.Pool(config); |
27 | | - |
28 | | -app.get("search", function handler(req, res) { |
29 | | - // BAD: the category might have SQL special characters in it |
30 | | - var query1 = |
31 | | - "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" + |
32 | | - req.params.category + |
33 | | - "' ORDER BY PRICE"; |
34 | | - pool.query(query1, [], function(err, results) { |
35 | | - // process results |
36 | | - }); |
37 | | - |
38 | | - // GOOD: use parameters |
39 | | - var query2 = |
40 | | - "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=$1" + " ORDER BY PRICE"; |
41 | | - pool.query(query2, [req.params.category], function(err, results) { |
42 | | - // process results |
43 | | - }); |
| 22 | + mongodb = require("mongodb"), |
| 23 | + bodyParser = require('body-parser'); |
| 24 | + |
| 25 | +const client = new MongoClient('mongodb://localhost:27017/test'); |
| 26 | + |
| 27 | +app.use(bodyParser.urlencoded({ extended: true })); |
| 28 | + |
| 29 | +app.get("/search1", async function handler(req, res) { |
| 30 | + await client.connect(); |
| 31 | + const db = client.db('test'); |
| 32 | + const doc = db.collection('doc'); |
| 33 | + |
| 34 | + const result = doc.find({ |
| 35 | + // BAD: |
| 36 | + // This is vulnerable. |
| 37 | + // Eg., req.body.title might be the object { $ne: "foobarbaz" }, and the |
| 38 | + // endpoint would return all data. |
| 39 | + title: req.body.title |
| 40 | + }); |
| 41 | + |
| 42 | + res.send(await result); |
44 | 43 | }); |
45 | 44 |
|
| 45 | +app.get("/search2", async function handler(req, res) { |
| 46 | + await client.connect(); |
| 47 | + const db = client.db('test'); |
| 48 | + const doc = db.collection('doc'); |
| 49 | + |
| 50 | + // GOOD: |
| 51 | + // The input is converted to a string, and matched using the $eq operator. |
| 52 | + // At most one datum is returned. |
| 53 | + const result = await doc.find({ title: { $eq: `${req.body.title}` } }); |
| 54 | + |
| 55 | + res.send(await result); |
| 56 | +}); |
46 | 57 | ``` |
47 | 58 |
|
48 | 59 | ## References |
49 | | -* Wikipedia: [SQL injection](https://en.wikipedia.org/wiki/SQL_injection). |
| 60 | +* Acunetix Blog: [NoSQL Injections and How to Avoid Them](https://www.acunetix.com/blog/web-security-zone/nosql-injections/). |
50 | 61 | * MongoDB: [$eq operator](https://docs.mongodb.com/manual/reference/operator/query/eq). |
| 62 | +* MongoDB: [$ne operator](https://docs.mongodb.com/manual/reference/operator/query/ne). |
0 commit comments