Skip to content

Commit 132e0bf

Browse files
author
Stephan Brandauer
committed
add database accesses as additional (heuristic) remote flow sources
1 parent 2a36744 commit 132e0bf

File tree

9 files changed

+546
-21
lines changed

9 files changed

+546
-21
lines changed

javascript/ql/lib/semmle/javascript/Concepts.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@ abstract class FileNameSource extends DataFlow::Node { }
8484
abstract class DatabaseAccess extends DataFlow::Node {
8585
/** Gets an argument to this database access that is interpreted as a query. */
8686
abstract DataFlow::Node getAQueryArgument();
87+
88+
/** Gets a node to which a result of the access may flow. */
89+
DataFlow::Node getAResult() {
90+
none() // Overridden in subclass
91+
}
92+
93+
/**
94+
* Holds if the data returned can be a user-controlled object,
95+
* such as a JSON object parsed from user-controlled data.
96+
*/
97+
predicate returnsUserControlledObject() {
98+
// NB: Most data bases support JSON data (some via plugins),
99+
// which is why this has a default implementation.
100+
any()
101+
}
87102
}
88103

89104
/**

javascript/ql/lib/semmle/javascript/frameworks/Knex.qll

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,35 @@ module Knex {
4646
RawKnexSqlString() { this = any(RawKnexCall call).getArgument(0).asExpr() }
4747
}
4848

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()
49+
/** A call that triggers a SQL query submission by calling then/stream/asCallback. */
50+
private class KnexDatabaseCallback extends DatabaseAccess, DataFlow::CallNode {
51+
string member;
52+
53+
KnexDatabaseCallback() {
54+
member = ["then", "stream", "asCallback"] and
55+
this = knexObject().getMember(member).getACall()
56+
}
57+
58+
override DataFlow::Node getAResult() {
59+
member = "then" and
60+
result = this.getCallback(0).getParameter(0)
5361
or
54-
exists(AwaitExpr await |
55-
this = await.flow() and
56-
await.getOperand() = knexObject().getAUse().asExpr()
62+
member = "asCallback" and
63+
result = this.getCallback(0).getParameter(1)
64+
}
65+
66+
override DataFlow::Node getAQueryArgument() { none() }
67+
}
68+
69+
private class KnexDatabaseAwait extends DatabaseAccess, DataFlow::ValueNode {
70+
KnexDatabaseAwait() {
71+
exists(AwaitExpr enclosingAwait | this = enclosingAwait.flow() |
72+
enclosingAwait.getOperand() = knexObject().getAUse().asExpr()
5773
)
5874
}
5975

76+
override DataFlow::Node getAResult() { result = this }
77+
6078
override DataFlow::Node getAQueryArgument() { none() }
6179
}
6280
}

javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import javascript
6+
import semmle.javascript.Promises
67

78
module NoSQL {
89
/** An expression that is interpreted as a NoSQL query. */
@@ -65,6 +66,10 @@ private module MongoDB {
6566

6667
override DataFlow::Node getAQueryArgument() { result = this.getArgument(queryArgIdx) }
6768

69+
override DataFlow::Node getAResult() {
70+
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
71+
}
72+
6873
DataFlow::Node getACodeOperator() {
6974
result = getADollarWhereProperty(this.getParameter(queryArgIdx))
7075
}
@@ -537,12 +542,29 @@ private module Mongoose {
537542
// NB: the complete information is not easily accessible for deeply chained calls
538543
f.getQueryArgument().getARhs() = result
539544
}
545+
546+
override DataFlow::Node getAResult() {
547+
result = this.getCallback(this.getNumArgument() - 1).getParameter(1)
548+
}
540549
}
541550

542-
class ExplicitQueryEvaluation extends DatabaseAccess {
551+
class ExplicitQueryEvaluation extends DatabaseAccess, DataFlow::CallNode {
552+
string member;
553+
543554
ExplicitQueryEvaluation() {
544555
// explicit execution using a Query method call
545-
Query::getAMongooseQuery().getMember(["exec", "then", "catch"]).getACall() = this
556+
member = ["exec", "then", "catch"] and
557+
Query::getAMongooseQuery().getMember(member).getACall() = this
558+
}
559+
560+
private int resultParamIndex() {
561+
member = "then" and result = 0
562+
or
563+
member = "exec" and result = 1
564+
}
565+
566+
override DataFlow::Node getAResult() {
567+
result = this.getCallback(_).getParameter(this.resultParamIndex())
546568
}
547569

548570
override DataFlow::Node getAQueryArgument() {
@@ -588,6 +610,10 @@ private module Minimongo {
588610

589611
override DataFlow::Node getAQueryArgument() { result = this.getArgument(queryArgIdx) }
590612

613+
override DataFlow::Node getAResult() {
614+
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
615+
}
616+
591617
DataFlow::Node getACodeOperator() {
592618
result = getADollarWhereProperty(this.getParameter(queryArgIdx))
593619
}
@@ -609,7 +635,7 @@ private module Minimongo {
609635
* Provides classes modeling the MarsDB library.
610636
*/
611637
private module MarsDB {
612-
private class MarsDBAccess extends DatabaseAccess {
638+
private class MarsDBAccess extends DatabaseAccess, DataFlow::CallNode {
613639
string method;
614640

615641
MarsDBAccess() {
@@ -623,21 +649,29 @@ private module MarsDB {
623649

624650
string getMethod() { result = method }
625651

652+
override DataFlow::Node getAResult() {
653+
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
654+
}
655+
626656
override DataFlow::Node getAQueryArgument() { none() }
627657
}
628658

629659
/** A call to a MarsDB query method. */
630-
private class QueryCall extends DatabaseAccess, API::CallNode {
660+
private class QueryCall extends MarsDBAccess, API::CallNode {
631661
int queryArgIdx;
632662

633663
QueryCall() {
634664
exists(string m |
635-
this.(MarsDBAccess).getMethod() = m and
665+
this.getMethod() = m and
636666
// implements parts of the Minimongo interface
637667
Minimongo::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
638668
)
639669
}
640670

671+
override DataFlow::Node getAResult() {
672+
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
673+
}
674+
641675
override DataFlow::Node getAQueryArgument() { result = this.getArgument(queryArgIdx) }
642676

643677
DataFlow::Node getACodeOperator() {
@@ -744,9 +778,13 @@ private module Redis {
744778
/**
745779
* An access to a database through redis
746780
*/
747-
class RedisDatabaseAccess extends DatabaseAccess {
781+
class RedisDatabaseAccess extends DatabaseAccess, DataFlow::CallNode {
748782
RedisDatabaseAccess() { this = redis().getMember(_).getACall() }
749783

784+
override DataFlow::Node getAResult() {
785+
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
786+
}
787+
750788
override DataFlow::Node getAQueryArgument() { none() }
751789
}
752790
}
@@ -768,9 +806,13 @@ private module IoRedis {
768806
/**
769807
* An access to a database through ioredis
770808
*/
771-
class IoRedisDatabaseAccess extends DatabaseAccess {
809+
class IoRedisDatabaseAccess extends DatabaseAccess, DataFlow::CallNode {
772810
IoRedisDatabaseAccess() { this = ioredis().getMember(_).getACall() }
773811

812+
override DataFlow::Node getAResult() {
813+
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
814+
}
815+
774816
override DataFlow::Node getAQueryArgument() { none() }
775817
}
776818
}

0 commit comments

Comments
 (0)