Skip to content

Commit ed0ef36

Browse files
authored
Merge pull request #1035 from asger-semmle/firebase
Approved by xiemaisi
2 parents 752ca94 + 0eb9231 commit ed0ef36

File tree

10 files changed

+411
-1
lines changed

10 files changed

+411
-1
lines changed

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import semmle.javascript.frameworks.CryptoLibraries
6969
import semmle.javascript.frameworks.DigitalOcean
7070
import semmle.javascript.frameworks.Electron
7171
import semmle.javascript.frameworks.Files
72+
import semmle.javascript.frameworks.Firebase
7273
import semmle.javascript.frameworks.jQuery
7374
import semmle.javascript.frameworks.LodashUnderscore
7475
import semmle.javascript.frameworks.Logging

javascript/ql/src/semmle/javascript/StandardLibrary.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
189189
/**
190190
* Holds if taint propagates from `pred` to `succ` through promises.
191191
*/
192-
private predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
192+
predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
193193
// from `x` to `new Promise((res, rej) => res(x))`
194194
pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0)
195195
or
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
/**
2+
* Provides classes and predicates for reasoning about code using the Firebase API.
3+
*/
4+
import javascript
5+
6+
module Firebase {
7+
/** Gets a reference to the Firebase API object. */
8+
private DataFlow::SourceNode firebase(DataFlow::TypeTracker t) {
9+
t.start() and
10+
(
11+
result = DataFlow::moduleImport("firebase/app")
12+
or
13+
result = DataFlow::moduleImport("firebase-admin")
14+
or
15+
result = DataFlow::globalVarRef("firebase")
16+
)
17+
or
18+
exists (DataFlow::TypeTracker t2 |
19+
result = firebase(t2).track(t2, t)
20+
)
21+
}
22+
23+
/** Gets a reference to the `firebase/app` or `firebase-admin` API object. */
24+
DataFlow::SourceNode firebase() {
25+
result = firebase(DataFlow::TypeTracker::end())
26+
}
27+
28+
/** Gets a reference to a Firebase app created with `initializeApp`. */
29+
private DataFlow::SourceNode initApp(DataFlow::TypeTracker t) {
30+
result = firebase().getAMethodCall("initializeApp") and t.start()
31+
or
32+
exists (DataFlow::TypeTracker t2 |
33+
result = initApp(t2).track(t2, t)
34+
)
35+
}
36+
37+
/**
38+
* Gets a reference to a Firebase app, either the `firebase` object or an
39+
* app created explicitly with `initializeApp()`.
40+
*/
41+
DataFlow::SourceNode app() {
42+
result = firebase(DataFlow::TypeTracker::end()) or result = initApp(DataFlow::TypeTracker::end())
43+
}
44+
45+
module Database {
46+
47+
/** Gets a reference to a Firebase database object, such as `firebase.database()`. */
48+
private DataFlow::SourceNode database(DataFlow::TypeTracker t) {
49+
result = app().getAMethodCall("database") and t.start()
50+
or
51+
exists (DataFlow::TypeTracker t2 |
52+
result = database(t2).track(t2, t)
53+
)
54+
}
55+
56+
/** Gets a reference to a Firebase database object, such as `firebase.database()`. */
57+
DataFlow::SourceNode database() {
58+
result = database(DataFlow::TypeTracker::end())
59+
}
60+
61+
/** Gets a node that refers to a `Reference` object, such as `firebase.database().ref()`. */
62+
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
63+
t.start() and
64+
(
65+
exists (string name | result = database().getAMethodCall(name) |
66+
name = "ref" or
67+
name = "refFromURL"
68+
)
69+
or
70+
exists (string name | result = ref().getAMethodCall(name) |
71+
name = "push" or
72+
name = "child"
73+
)
74+
or
75+
exists (string name | result = ref().getAPropertyRead(name) |
76+
name = "parent" or
77+
name = "root"
78+
)
79+
or
80+
result = snapshot().getAPropertyRead("ref")
81+
)
82+
or
83+
exists (DataFlow::TypeTracker t2 |
84+
result = ref(t2).track(t2, t)
85+
)
86+
}
87+
88+
/** Gets a node that refers to a `Reference` object, such as `firebase.database().ref()`. */
89+
DataFlow::SourceNode ref() {
90+
result = ref(DataFlow::TypeTracker::end())
91+
}
92+
93+
/** Gets a node that refers to a `Query` or `Reference` object. */
94+
private DataFlow::SourceNode query(DataFlow::TypeTracker t) {
95+
t.start() and
96+
(
97+
result = ref(t) // a Reference can be used as a Query
98+
or
99+
exists (string name | result = query().getAMethodCall(name) |
100+
name = "endAt" or
101+
name = "limitTo" + any(string s) or
102+
name = "orderBy" + any(string s) or
103+
name = "startAt"
104+
)
105+
)
106+
or
107+
exists (DataFlow::TypeTracker t2 |
108+
result = query(t2).track(t2, t)
109+
)
110+
}
111+
112+
/** Gets a node that refers to a `Query` or `Reference` object. */
113+
DataFlow::SourceNode query() {
114+
result = query(DataFlow::TypeTracker::end())
115+
}
116+
117+
/**
118+
* A call of form `query.on(...)` or `query.once(...)`.
119+
*/
120+
class QueryListenCall extends DataFlow::MethodCallNode {
121+
QueryListenCall() {
122+
this = query().getAMethodCall() and
123+
(getMethodName() = "on" or getMethodName() = "once")
124+
}
125+
126+
/**
127+
* Gets the argument in which the callback is passed.
128+
*/
129+
DataFlow::Node getCallbackNode() {
130+
result = getArgument(1)
131+
}
132+
}
133+
134+
/**
135+
* Gets a node that is passed as the callback to a `Reference.transaction` call.
136+
*/
137+
private DataFlow::SourceNode transactionCallback(DataFlow::TypeBackTracker t) {
138+
t.start() and
139+
result = ref().getAMethodCall("transaction").getArgument(0).getALocalSource()
140+
or
141+
exists (DataFlow::TypeBackTracker t2 |
142+
result = transactionCallback(t2).backtrack(t2, t)
143+
)
144+
}
145+
146+
/**
147+
* Gets a node that is passed as the callback to a `Reference.transaction` call.
148+
*/
149+
DataFlow::SourceNode transactionCallback() {
150+
result = transactionCallback(DataFlow::TypeBackTracker::end())
151+
}
152+
}
153+
154+
/**
155+
* Provides predicates for reasoning about the the Firebase Cloud Functions API,
156+
* sometimes referred to just as just "Firebase Functions".
157+
*/
158+
module CloudFunctions {
159+
/** Gets a reference to the Cloud Functions namespace. */
160+
private DataFlow::SourceNode namespace(DataFlow::TypeTracker t) {
161+
t.start() and
162+
result = DataFlow::moduleImport("firebase-functions")
163+
or
164+
exists (DataFlow::TypeTracker t2 |
165+
result = namespace(t2).track(t2, t)
166+
)
167+
}
168+
169+
/** Gets a reference to the Cloud Functions namespace. */
170+
DataFlow::SourceNode namespace() {
171+
result = namespace(DataFlow::TypeTracker::end())
172+
}
173+
174+
/** Gets a reference to a Cloud Functions database object. */
175+
private DataFlow::SourceNode database(DataFlow::TypeTracker t) {
176+
t.start() and
177+
result = namespace().getAPropertyRead("database")
178+
or
179+
exists (DataFlow::TypeTracker t2 |
180+
result = database(t2).track(t2, t)
181+
)
182+
}
183+
184+
/** Gets a reference to a Cloud Functions database object. */
185+
DataFlow::SourceNode database() {
186+
result = database(DataFlow::TypeTracker::end())
187+
}
188+
189+
/** Gets a data flow node holding a `RefBuilder` object. */
190+
private DataFlow::SourceNode refBuilder(DataFlow::TypeTracker t) {
191+
t.start() and
192+
result = database().getAMethodCall("ref")
193+
or
194+
exists (DataFlow::TypeTracker t2 |
195+
result = refBuilder(t2).track(t2, t)
196+
)
197+
}
198+
199+
/** Gets a data flow node holding a `RefBuilder` object. */
200+
DataFlow::SourceNode ref() {
201+
result = refBuilder(DataFlow::TypeTracker::end())
202+
}
203+
204+
/** Gets a call that registers a listener on a `RefBuilder`, such as `ref.onCreate(...)`. */
205+
class RefBuilderListenCall extends DataFlow::MethodCallNode {
206+
RefBuilderListenCall() {
207+
this = ref().getAMethodCall() and
208+
getMethodName() = "on" + any(string s)
209+
}
210+
211+
/**
212+
* Gets the data flow node holding the listener callback.
213+
*/
214+
DataFlow::Node getCallbackNode() {
215+
result = getArgument(0)
216+
}
217+
}
218+
}
219+
220+
/**
221+
* Gets a value that will be invoked with a `DataSnapshot` value as its first parameter.
222+
*/
223+
private DataFlow::SourceNode snapshotCallback(DataFlow::TypeBackTracker t) {
224+
t.start() and
225+
(
226+
result = any(Database::QueryListenCall call).getCallbackNode().getALocalSource()
227+
or
228+
result = any(CloudFunctions::RefBuilderListenCall call).getCallbackNode().getALocalSource()
229+
)
230+
or
231+
exists (DataFlow::TypeBackTracker t2 |
232+
result = snapshotCallback(t2).backtrack(t2, t)
233+
)
234+
}
235+
236+
/**
237+
* Gets a value that will be invoked with a `DataSnapshot` value as its first parameter.
238+
*/
239+
DataFlow::SourceNode snapshotCallback() {
240+
result = snapshotCallback(DataFlow::TypeBackTracker::end())
241+
}
242+
243+
/**
244+
* Gets a node that refers to a `DataSnapshot` value or a promise or `Change`
245+
* object containing `DataSnapshot`s.
246+
*/
247+
private DataFlow::SourceNode snapshot(DataFlow::TypeTracker t) {
248+
t.start() and
249+
(
250+
result = snapshotCallback().(DataFlow::FunctionNode).getParameter(0)
251+
or
252+
result instanceof Database::QueryListenCall // returns promise
253+
or
254+
result = snapshot().getAMethodCall("child")
255+
or
256+
result = snapshot().getAMethodCall("forEach").getCallback(0).getParameter(0)
257+
or
258+
exists (string prop | result = snapshot().getAPropertyRead(prop) |
259+
prop = "before" or // only defined on Change objects
260+
prop = "after"
261+
)
262+
)
263+
or
264+
promiseTaintStep(snapshot(t), result)
265+
or
266+
exists (DataFlow::TypeTracker t2 |
267+
result = snapshot(t2).track(t2, t)
268+
)
269+
}
270+
271+
/**
272+
* Gets a node that refers to a `DataSnapshot` value, such as `x` in
273+
* `firebase.database().ref().on('value', x => {...})`.
274+
*/
275+
DataFlow::SourceNode snapshot() {
276+
result = snapshot(DataFlow::TypeTracker::end())
277+
}
278+
279+
/**
280+
* A reference to a value obtained from a Firebase database.
281+
*/
282+
class FirebaseVal extends RemoteFlowSource {
283+
FirebaseVal() {
284+
exists (string name | this = snapshot().getAMethodCall(name) |
285+
name = "val" or
286+
name = "exportVal"
287+
)
288+
or
289+
this = Database::transactionCallback().(DataFlow::FunctionNode).getParameter(0)
290+
}
291+
292+
override string getSourceType() {
293+
result = "Firebase database"
294+
}
295+
}
296+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
| tst.js:5:1:5:22 | fb.data ... ef('x') |
2+
| tst.js:7:3:7:7 | x.ref |
3+
| tst.js:7:3:7:14 | x.ref.parent |
4+
| tst.js:10:1:10:25 | admin.d ... ef('x') |
5+
| tst.js:12:3:12:7 | x.ref |
6+
| tst.js:12:3:12:14 | x.ref.parent |
7+
| tst.js:17:3:17:7 | x.ref |
8+
| tst.js:17:3:17:14 | x.ref.parent |
9+
| tst.js:23:3:23:7 | x.ref |
10+
| tst.js:23:3:23:14 | x.ref.parent |
11+
| tst.js:32:12:32:42 | this.fi ... .ref(x) |
12+
| tst.js:46:12:46:42 | this.fi ... .ref(x) |
13+
| tst.js:50:12:50:25 | this.getRef(x) |
14+
| tst.js:50:12:50:34 | this.ge ... hild(x) |
15+
| tst.js:54:5:54:37 | this.fi ... ef('x') |
16+
| tst.js:58:1:58:61 | new Fir ... /news') |
17+
| tst.js:59:1:59:38 | new Fir ... /news') |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import javascript
2+
3+
select Firebase::Database::ref()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
| tst.js:5:1:8:2 | fb.data ... ent;\\n}) |
2+
| tst.js:5:38:5:38 | x |
3+
| tst.js:10:1:13:2 | admin.d ... ent;\\n}) |
4+
| tst.js:10:41:10:41 | x |
5+
| tst.js:15:38:15:38 | x |
6+
| tst.js:20:38:20:38 | x |
7+
| tst.js:21:3:21:10 | x.before |
8+
| tst.js:22:3:22:9 | x.after |
9+
| tst.js:50:12:50:48 | this.ge ... value') |
10+
| tst.js:60:1:60:39 | new Fir ... em('x') |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import javascript
2+
3+
select Firebase::snapshot()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| tst.js:6:3:6:9 | x.val() |
2+
| tst.js:11:3:11:9 | x.val() |
3+
| tst.js:16:3:16:9 | x.val() |
4+
| tst.js:21:3:21:16 | x.before.val() |
5+
| tst.js:22:3:22:15 | x.after.val() |
6+
| tst.js:61:36:61:36 | x |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import javascript
2+
3+
from Firebase::FirebaseVal val
4+
select val

0 commit comments

Comments
 (0)