Skip to content

Commit 1ffd9c9

Browse files
authored
Merge pull request github#6086 from asgerf/js/knex
Approved by esbena
2 parents 32f6a46 + af9cc07 commit 1ffd9c9

File tree

6 files changed

+494
-0
lines changed

6 files changed

+494
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* SQL injection sinks from the `knex` library are now recognized.

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ import semmle.javascript.frameworks.JWT
9696
import semmle.javascript.frameworks.Handlebars
9797
import semmle.javascript.frameworks.History
9898
import semmle.javascript.frameworks.Immutable
99+
import semmle.javascript.frameworks.Knex
99100
import semmle.javascript.frameworks.LazyCache
100101
import semmle.javascript.frameworks.LodashUnderscore
101102
import semmle.javascript.frameworks.Logging
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Provides classes and predicates for working with [knex](https://knexjs.org).
3+
*/
4+
5+
private import javascript
6+
7+
/**
8+
* Provides classes and predicates for working with [knex](https://knexjs.org).
9+
*/
10+
module Knex {
11+
/** Gets an API node referring to the `knex` library. */
12+
API::Node knexLibrary() { result = API::moduleImport("knex") }
13+
14+
/** Gets a method name on Knex objects which return a Knex object. */
15+
bindingset[result]
16+
private string chainableKnexMethod() {
17+
not result in [
18+
"toString", "valueOf", "then", "catch", "finally", "toSQL", "asCallback", "stream"
19+
]
20+
}
21+
22+
/** Gets an API node referring to a `knex` object, such as `knex.from('foo')`. */
23+
API::Node knexObject() {
24+
result = knexLibrary().getReturn()
25+
or
26+
result = knexObject().getReturn()
27+
or
28+
result = knexObject().getMember("schema")
29+
or
30+
result = knexObject().getMember(chainableKnexMethod()).getReturn()
31+
or
32+
// callback for building inner queries, such as `knex.join(function() { this.on('blah') })`
33+
result = knexObject().getMember(chainableKnexMethod()).getParameter(0).getReceiver()
34+
or
35+
// knex.transaction(trx => { ... })
36+
result = knexObject().getMember("transaction").getParameter(0).getParameter(0)
37+
}
38+
39+
/** A call to a Knex method that takes a raw SQL string as input. */
40+
class RawKnexCall extends DataFlow::CallNode {
41+
RawKnexCall() { this = knexObject().getMember(["raw", any(string s) + "Raw"]).getACall() }
42+
}
43+
44+
/** A SQL string passed to a raw Knex method. */
45+
private class RawKnexSqlString extends SQL::SqlString {
46+
RawKnexSqlString() { this = any(RawKnexCall call).getArgument(0).asExpr() }
47+
}
48+
49+
/** A call that triggers a SQL query submission. */
50+
private class KnexDatabaseAccess extends DatabaseAccess {
51+
KnexDatabaseAccess() {
52+
this = knexObject().getMember(["then", "stream", "asCallback"]).getACall()
53+
or
54+
exists(AwaitExpr await |
55+
this = await.flow() and
56+
await.getOperand() = knexObject().getAUse().asExpr()
57+
)
58+
}
59+
60+
override DataFlow::Node getAQueryArgument() { none() }
61+
}
62+
}

0 commit comments

Comments
 (0)