mirror of
https://github.com/github/codeql.git
synced 2026-05-01 03:35:13 +02:00
Merge pull request #1035 from asger-semmle/firebase
Approved by xiemaisi
This commit is contained in:
@@ -69,6 +69,7 @@ import semmle.javascript.frameworks.CryptoLibraries
|
||||
import semmle.javascript.frameworks.DigitalOcean
|
||||
import semmle.javascript.frameworks.Electron
|
||||
import semmle.javascript.frameworks.Files
|
||||
import semmle.javascript.frameworks.Firebase
|
||||
import semmle.javascript.frameworks.jQuery
|
||||
import semmle.javascript.frameworks.LodashUnderscore
|
||||
import semmle.javascript.frameworks.Logging
|
||||
|
||||
@@ -189,7 +189,7 @@ private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
|
||||
/**
|
||||
* Holds if taint propagates from `pred` to `succ` through promises.
|
||||
*/
|
||||
private predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// from `x` to `new Promise((res, rej) => res(x))`
|
||||
pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0)
|
||||
or
|
||||
|
||||
296
javascript/ql/src/semmle/javascript/frameworks/Firebase.qll
Normal file
296
javascript/ql/src/semmle/javascript/frameworks/Firebase.qll
Normal file
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about code using the Firebase API.
|
||||
*/
|
||||
import javascript
|
||||
|
||||
module Firebase {
|
||||
/** Gets a reference to the Firebase API object. */
|
||||
private DataFlow::SourceNode firebase(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = DataFlow::moduleImport("firebase/app")
|
||||
or
|
||||
result = DataFlow::moduleImport("firebase-admin")
|
||||
or
|
||||
result = DataFlow::globalVarRef("firebase")
|
||||
)
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = firebase(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reference to the `firebase/app` or `firebase-admin` API object. */
|
||||
DataFlow::SourceNode firebase() {
|
||||
result = firebase(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/** Gets a reference to a Firebase app created with `initializeApp`. */
|
||||
private DataFlow::SourceNode initApp(DataFlow::TypeTracker t) {
|
||||
result = firebase().getAMethodCall("initializeApp") and t.start()
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = initApp(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a Firebase app, either the `firebase` object or an
|
||||
* app created explicitly with `initializeApp()`.
|
||||
*/
|
||||
DataFlow::SourceNode app() {
|
||||
result = firebase(DataFlow::TypeTracker::end()) or result = initApp(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
module Database {
|
||||
|
||||
/** Gets a reference to a Firebase database object, such as `firebase.database()`. */
|
||||
private DataFlow::SourceNode database(DataFlow::TypeTracker t) {
|
||||
result = app().getAMethodCall("database") and t.start()
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = database(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reference to a Firebase database object, such as `firebase.database()`. */
|
||||
DataFlow::SourceNode database() {
|
||||
result = database(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/** Gets a node that refers to a `Reference` object, such as `firebase.database().ref()`. */
|
||||
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
exists (string name | result = database().getAMethodCall(name) |
|
||||
name = "ref" or
|
||||
name = "refFromURL"
|
||||
)
|
||||
or
|
||||
exists (string name | result = ref().getAMethodCall(name) |
|
||||
name = "push" or
|
||||
name = "child"
|
||||
)
|
||||
or
|
||||
exists (string name | result = ref().getAPropertyRead(name) |
|
||||
name = "parent" or
|
||||
name = "root"
|
||||
)
|
||||
or
|
||||
result = snapshot().getAPropertyRead("ref")
|
||||
)
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = ref(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that refers to a `Reference` object, such as `firebase.database().ref()`. */
|
||||
DataFlow::SourceNode ref() {
|
||||
result = ref(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/** Gets a node that refers to a `Query` or `Reference` object. */
|
||||
private DataFlow::SourceNode query(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = ref(t) // a Reference can be used as a Query
|
||||
or
|
||||
exists (string name | result = query().getAMethodCall(name) |
|
||||
name = "endAt" or
|
||||
name = "limitTo" + any(string s) or
|
||||
name = "orderBy" + any(string s) or
|
||||
name = "startAt"
|
||||
)
|
||||
)
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = query(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that refers to a `Query` or `Reference` object. */
|
||||
DataFlow::SourceNode query() {
|
||||
result = query(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* A call of form `query.on(...)` or `query.once(...)`.
|
||||
*/
|
||||
class QueryListenCall extends DataFlow::MethodCallNode {
|
||||
QueryListenCall() {
|
||||
this = query().getAMethodCall() and
|
||||
(getMethodName() = "on" or getMethodName() = "once")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument in which the callback is passed.
|
||||
*/
|
||||
DataFlow::Node getCallbackNode() {
|
||||
result = getArgument(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is passed as the callback to a `Reference.transaction` call.
|
||||
*/
|
||||
private DataFlow::SourceNode transactionCallback(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = ref().getAMethodCall("transaction").getArgument(0).getALocalSource()
|
||||
or
|
||||
exists (DataFlow::TypeBackTracker t2 |
|
||||
result = transactionCallback(t2).backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is passed as the callback to a `Reference.transaction` call.
|
||||
*/
|
||||
DataFlow::SourceNode transactionCallback() {
|
||||
result = transactionCallback(DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides predicates for reasoning about the the Firebase Cloud Functions API,
|
||||
* sometimes referred to just as just "Firebase Functions".
|
||||
*/
|
||||
module CloudFunctions {
|
||||
/** Gets a reference to the Cloud Functions namespace. */
|
||||
private DataFlow::SourceNode namespace(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::moduleImport("firebase-functions")
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = namespace(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reference to the Cloud Functions namespace. */
|
||||
DataFlow::SourceNode namespace() {
|
||||
result = namespace(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/** Gets a reference to a Cloud Functions database object. */
|
||||
private DataFlow::SourceNode database(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = namespace().getAPropertyRead("database")
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = database(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reference to a Cloud Functions database object. */
|
||||
DataFlow::SourceNode database() {
|
||||
result = database(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/** Gets a data flow node holding a `RefBuilder` object. */
|
||||
private DataFlow::SourceNode refBuilder(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = database().getAMethodCall("ref")
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = refBuilder(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a data flow node holding a `RefBuilder` object. */
|
||||
DataFlow::SourceNode ref() {
|
||||
result = refBuilder(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/** Gets a call that registers a listener on a `RefBuilder`, such as `ref.onCreate(...)`. */
|
||||
class RefBuilderListenCall extends DataFlow::MethodCallNode {
|
||||
RefBuilderListenCall() {
|
||||
this = ref().getAMethodCall() and
|
||||
getMethodName() = "on" + any(string s)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data flow node holding the listener callback.
|
||||
*/
|
||||
DataFlow::Node getCallbackNode() {
|
||||
result = getArgument(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value that will be invoked with a `DataSnapshot` value as its first parameter.
|
||||
*/
|
||||
private DataFlow::SourceNode snapshotCallback(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = any(Database::QueryListenCall call).getCallbackNode().getALocalSource()
|
||||
or
|
||||
result = any(CloudFunctions::RefBuilderListenCall call).getCallbackNode().getALocalSource()
|
||||
)
|
||||
or
|
||||
exists (DataFlow::TypeBackTracker t2 |
|
||||
result = snapshotCallback(t2).backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value that will be invoked with a `DataSnapshot` value as its first parameter.
|
||||
*/
|
||||
DataFlow::SourceNode snapshotCallback() {
|
||||
result = snapshotCallback(DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that refers to a `DataSnapshot` value or a promise or `Change`
|
||||
* object containing `DataSnapshot`s.
|
||||
*/
|
||||
private DataFlow::SourceNode snapshot(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = snapshotCallback().(DataFlow::FunctionNode).getParameter(0)
|
||||
or
|
||||
result instanceof Database::QueryListenCall // returns promise
|
||||
or
|
||||
result = snapshot().getAMethodCall("child")
|
||||
or
|
||||
result = snapshot().getAMethodCall("forEach").getCallback(0).getParameter(0)
|
||||
or
|
||||
exists (string prop | result = snapshot().getAPropertyRead(prop) |
|
||||
prop = "before" or // only defined on Change objects
|
||||
prop = "after"
|
||||
)
|
||||
)
|
||||
or
|
||||
promiseTaintStep(snapshot(t), result)
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = snapshot(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that refers to a `DataSnapshot` value, such as `x` in
|
||||
* `firebase.database().ref().on('value', x => {...})`.
|
||||
*/
|
||||
DataFlow::SourceNode snapshot() {
|
||||
result = snapshot(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a value obtained from a Firebase database.
|
||||
*/
|
||||
class FirebaseVal extends RemoteFlowSource {
|
||||
FirebaseVal() {
|
||||
exists (string name | this = snapshot().getAMethodCall(name) |
|
||||
name = "val" or
|
||||
name = "exportVal"
|
||||
)
|
||||
or
|
||||
this = Database::transactionCallback().(DataFlow::FunctionNode).getParameter(0)
|
||||
}
|
||||
|
||||
override string getSourceType() {
|
||||
result = "Firebase database"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
| tst.js:5:1:5:22 | fb.data ... ef('x') |
|
||||
| tst.js:7:3:7:7 | x.ref |
|
||||
| tst.js:7:3:7:14 | x.ref.parent |
|
||||
| tst.js:10:1:10:25 | admin.d ... ef('x') |
|
||||
| tst.js:12:3:12:7 | x.ref |
|
||||
| tst.js:12:3:12:14 | x.ref.parent |
|
||||
| tst.js:17:3:17:7 | x.ref |
|
||||
| tst.js:17:3:17:14 | x.ref.parent |
|
||||
| tst.js:23:3:23:7 | x.ref |
|
||||
| tst.js:23:3:23:14 | x.ref.parent |
|
||||
| tst.js:32:12:32:42 | this.fi ... .ref(x) |
|
||||
| tst.js:46:12:46:42 | this.fi ... .ref(x) |
|
||||
| tst.js:50:12:50:25 | this.getRef(x) |
|
||||
| tst.js:50:12:50:34 | this.ge ... hild(x) |
|
||||
| tst.js:54:5:54:37 | this.fi ... ef('x') |
|
||||
| tst.js:58:1:58:61 | new Fir ... /news') |
|
||||
| tst.js:59:1:59:38 | new Fir ... /news') |
|
||||
@@ -0,0 +1,3 @@
|
||||
import javascript
|
||||
|
||||
select Firebase::Database::ref()
|
||||
@@ -0,0 +1,10 @@
|
||||
| tst.js:5:1:8:2 | fb.data ... ent;\\n}) |
|
||||
| tst.js:5:38:5:38 | x |
|
||||
| tst.js:10:1:13:2 | admin.d ... ent;\\n}) |
|
||||
| tst.js:10:41:10:41 | x |
|
||||
| tst.js:15:38:15:38 | x |
|
||||
| tst.js:20:38:20:38 | x |
|
||||
| tst.js:21:3:21:10 | x.before |
|
||||
| tst.js:22:3:22:9 | x.after |
|
||||
| tst.js:50:12:50:48 | this.ge ... value') |
|
||||
| tst.js:60:1:60:39 | new Fir ... em('x') |
|
||||
@@ -0,0 +1,3 @@
|
||||
import javascript
|
||||
|
||||
select Firebase::snapshot()
|
||||
@@ -0,0 +1,6 @@
|
||||
| tst.js:6:3:6:9 | x.val() |
|
||||
| tst.js:11:3:11:9 | x.val() |
|
||||
| tst.js:16:3:16:9 | x.val() |
|
||||
| tst.js:21:3:21:16 | x.before.val() |
|
||||
| tst.js:22:3:22:15 | x.after.val() |
|
||||
| tst.js:61:36:61:36 | x |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from Firebase::FirebaseVal val
|
||||
select val
|
||||
70
javascript/ql/test/library-tests/frameworks/Firebase/tst.js
Normal file
70
javascript/ql/test/library-tests/frameworks/Firebase/tst.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import * as fb from 'firebase/app';
|
||||
import * as admin from 'firebase-admin';
|
||||
import * as functions from 'firebase-functions';
|
||||
|
||||
fb.database().ref('x').once('value', x => {
|
||||
x.val();
|
||||
x.ref.parent;
|
||||
});
|
||||
|
||||
admin.database().ref('x').once('value', x => {
|
||||
x.val();
|
||||
x.ref.parent;
|
||||
});
|
||||
|
||||
functions.database.ref('x').onCreate(x => {
|
||||
x.val();
|
||||
x.ref.parent;
|
||||
});
|
||||
|
||||
functions.database.ref('x').onUpdate(x => {
|
||||
x.before.val();
|
||||
x.after.val();
|
||||
x.ref.parent;
|
||||
});
|
||||
|
||||
class FirebaseWrapper {
|
||||
constructor(firebase) {
|
||||
this.firebase = firebase;
|
||||
}
|
||||
|
||||
getRef(x) {
|
||||
return this.firebase.database().ref(x);
|
||||
}
|
||||
}
|
||||
|
||||
class FirebaseWrapper2 {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.firebase = fb.initializeApp();
|
||||
}
|
||||
|
||||
getRef(x) {
|
||||
return this.firebase.database().ref(x);
|
||||
}
|
||||
|
||||
getNewsItem(x) {
|
||||
return this.getRef(x).child(x).once('value');
|
||||
}
|
||||
|
||||
adjustValue(fn) {
|
||||
this.firebase.database().ref('x').transaction(fn);
|
||||
}
|
||||
}
|
||||
|
||||
new FirebaseWrapper(firebase.initializeApp()).getRef('/news');
|
||||
new FirebaseWrapper2().getRef('/news');
|
||||
new FirebaseWrapper2().getNewsItem('x');
|
||||
new FirebaseWrapper2().adjustValue(x => x + 1);
|
||||
|
||||
class Box {
|
||||
constructor(x) {
|
||||
this.x = x;
|
||||
}
|
||||
}
|
||||
let box1 = new Box(fb.database());
|
||||
let box2 = new Box(whatever());
|
||||
box2.x.ref(); // not a firebase ref
|
||||
Reference in New Issue
Block a user