|
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