Merge pull request #1035 from asger-semmle/firebase

Approved by xiemaisi
This commit is contained in:
semmle-qlci
2019-03-29 13:44:02 +00:00
committed by GitHub
10 changed files with 411 additions and 1 deletions

View File

@@ -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

View File

@@ -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

View 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"
}
}
}

View File

@@ -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') |

View File

@@ -0,0 +1,3 @@
import javascript
select Firebase::Database::ref()

View File

@@ -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') |

View File

@@ -0,0 +1,3 @@
import javascript
select Firebase::snapshot()

View File

@@ -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 |

View File

@@ -0,0 +1,4 @@
import javascript
from Firebase::FirebaseVal val
select val

View 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