Skip to content

Commit e24b45b

Browse files
committed
elaborate on both SQL and NoSQL injection in the js/sql-injection qhelp
1 parent b343dca commit e24b45b

File tree

5 files changed

+86
-20
lines changed

5 files changed

+86
-20
lines changed

javascript/ql/src/Security/CWE-089/SqlInjection.inc.qhelp

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ or prepared statements.
2020
<p>
2121
For NoSQL queries, make use of an operator like MongoDB's <code>$eq</code>
2222
to ensure that untrusted data is interpreted as a literal value and not as
23-
a query object.
23+
a query object. Alternatively, check that the untrusted data is a literal
24+
value and not a query object before using it in a query.
25+
</p>
26+
<p>
27+
For SQL queries, use query parameters or prepared statements to
28+
embed untrusted data into the query string, or use a library like
29+
<code>sqlstring</code> to escape untrusted data.
2430
</p>
2531
</recommendation>
2632

@@ -32,31 +38,47 @@ an HTTP request handler in a web application, whose parameter
3238
</p>
3339

3440
<p>
35-
The handler constructs two copies of the same SQL query involving
36-
user input taken from the request object, once unsafely using
37-
string concatenation, and once safely using query parameters.
41+
The handler constructs constructs an SQL query string from user input
42+
and executes it as a database query using the <code>pg</code> library.
43+
THe user input may contain quote characters, so this code is vulnerable
44+
to a SQL injection attack.
3845
</p>
3946

40-
<p>
41-
In the first case, the query string <code>query1</code> is built by
42-
directly concatenating a user-supplied request parameter with some
43-
string literals. The parameter may include quote characters, so this
44-
code is vulnerable to a SQL injection attack.
45-
</p>
47+
<sample src="examples/SqlInjection.js" />
4648

4749
<p>
48-
In the second case, the parameter is embedded into the query string
49-
<code>query2</code> using query parameters. In this example, we use
50+
To fix this vulnerability, we can use query parameters to embed the
51+
user input into the query string. In this example, we use
5052
the API offered by the <code>pg</code> Postgres database connector
5153
library, but other libraries offer similar features. This version is
5254
immune to injection attacks.
5355
</p>
5456

55-
<sample src="examples/SqlInjection.js" />
57+
<sample src="examples/SqlInjectionFix.js" />
58+
</example>
59+
60+
<example>
61+
<p>
62+
In the following example an express handler attempts to delete
63+
a single document from a MongoDB collection. The document to be
64+
deleted is identified by its <code>_id</code> field, which is
65+
constructed from user input. The user input may contain a query
66+
object, so this code is vulnerable to a NoSQL injection attack.
67+
</p>
68+
69+
<sample src="examples/NoSqlInjection.js" />
70+
71+
<p>
72+
To fix this vulnerability, we can check that the user input is a
73+
literal value and not a query object before using it in a query.
74+
</p>
75+
76+
<sample src="examples/NoSqlInjectionFix.js" />
5677
</example>
5778

5879
<references>
5980
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/SQL_injection">SQL injection</a>.</li>
6081
<li>MongoDB: <a href="https://docs.mongodb.com/manual/reference/operator/query/eq">$eq operator</a>.</li>
82+
<li>OWASP: <a href="https://owasp.org/www-pdf-archive/GOD16-NOSQL.pdf">NoSQL injection</a>.</li>
6183
</references>
6284
</qhelp>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const express = require("express");
2+
const mongoose = require("mongoose");
3+
const Todo = mongoose.model(
4+
"Todo",
5+
new mongoose.Schema({ text: { type: String } }, { timestamps: true })
6+
);
7+
8+
const app = express();
9+
app.use(express.json());
10+
app.use(express.urlencoded({ extended: false }));
11+
12+
app.delete("/api/delete", async (req, res) => {
13+
let id = req.body.id;
14+
15+
await Todo.deleteOne({ _id: id }); // BAD: id might be an object with special properties
16+
17+
res.json({ status: "ok" });
18+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const express = require("express");
2+
const mongoose = require("mongoose");
3+
const Todo = mongoose.model(
4+
"Todo",
5+
new mongoose.Schema({ text: { type: String } }, { timestamps: true })
6+
);
7+
8+
const app = express();
9+
app.use(express.json());
10+
app.use(express.urlencoded({ extended: false }));
11+
12+
app.delete("/api/delete", async (req, res) => {
13+
let id = req.body.id;
14+
if (typeof id !== "string") {
15+
res.status(400).json({ status: "error" });
16+
return;
17+
}
18+
await Todo.deleteOne({ _id: id }); // GOOD: id is guaranteed to be a string
19+
20+
res.json({ status: "ok" });
21+
});

javascript/ql/src/Security/CWE-089/examples/SqlInjection.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,4 @@ app.get("search", function handler(req, res) {
1111
pool.query(query1, [], function(err, results) {
1212
// process results
1313
});
14-
15-
// GOOD: use parameters
16-
var query2 =
17-
"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=$1" + " ORDER BY PRICE";
18-
pool.query(query2, [req.params.category], function(err, results) {
19-
// process results
20-
});
2114
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const app = require("express")(),
2+
pg = require("pg"),
3+
pool = new pg.Pool(config);
4+
5+
app.get("search", function handler(req, res) {
6+
// GOOD: use parameters
7+
var query2 =
8+
"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=$1" + " ORDER BY PRICE";
9+
pool.query(query2, [req.params.category], function(err, results) {
10+
// process results
11+
});
12+
});

0 commit comments

Comments
 (0)