diff --git a/javascript/ql/lib/ext/firebase.model.yml b/javascript/ql/lib/ext/firebase.model.yml new file mode 100644 index 000000000000..523ae0701ca7 --- /dev/null +++ b/javascript/ql/lib/ext/firebase.model.yml @@ -0,0 +1,37 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["firebase/app", "global", "Member[firebase]"] + + - ["FirebaseDB", "firebase/app", "Member[database].ReturnValue"] + - ["FirebaseDB", "firebase-functions", "Member[database]"] + - ["FirebaseDB", "firebase-admin", "Member[database].ReturnValue"] + - ["FirebaseDB", "FirebaseDBApp", "Member[database].ReturnValue"] + - ["FirebaseDB", "firebase.database.Database", ""] + + - ["FirebaseDBApp", "firebase-admin", "Member[initializeApp,app].ReturnValue"] + - ["FirebaseDBApp", "firebase/app", "Member[initializeApp,app].ReturnValue"] + - ["FirebaseDBApp", "firebase/app", "Member[initializeApp].ReturnValue"] + + - ["FirebaseDBRef", "FirebaseDB", "Member[ref,refFromURL].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[child,once,on,push,set,then].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,root,parent,before,after]"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[endAt,startAt,orderByChild,orderByKey,orderByValue,orderByPriority,equalTo,limitToLast,limitToFirst].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[onCreate,onUpdate,onWrite,onDelete,then,forEach].Argument[0].Parameter[0]"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].Argument[1].Parameter[0]"] + + - ["Snapshot", "FirebaseDBRef", "Member[child,once,on,push,set,then].ReturnValue"] + - ["Snapshot", "FirebaseDBRef", "Member[before,after]"] + - ["Snapshot", "FirebaseDBRef", "Member[once,on].Argument[1].Parameter[0]"] + - ["Snapshot", "FirebaseDBRef", "Member[onCreate,onUpdate,onWrite,onDelete,transaction,then,forEach].Argument[0].Parameter[0]"] + + - ["Snapshot", "Snapshot", "Awaited"] + + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ["FirebaseDBRef", "Member[transaction].Argument[0].Parameter[0]", 'remote'] + - ["Snapshot", "Member[val,exportVal].ReturnValue", 'remote'] diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Firebase.qll b/javascript/ql/lib/semmle/javascript/frameworks/Firebase.qll index 51e465ef908b..32a2b364dcaa 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Firebase.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Firebase.qll @@ -282,20 +282,18 @@ module Firebase { * `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" } - } + // /** + // * A reference to a value obtained from a Firebase database. + // */ + // deprecated 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" } + // } } diff --git a/javascript/ql/test/library-tests/frameworks/Firebase/src/import_assign.ts b/javascript/ql/test/library-tests/frameworks/Firebase/src/import_assign.ts index c201fc9dd4f6..62d89289fff5 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/src/import_assign.ts +++ b/javascript/ql/test/library-tests/frameworks/Firebase/src/import_assign.ts @@ -1,5 +1,5 @@ import firebase = require("firebase"); function test(db: firebase.database.Database) { - db.ref("hello"); + db.ref("hello"); // $firebaseRef } diff --git a/javascript/ql/test/library-tests/frameworks/Firebase/src/import_named.ts b/javascript/ql/test/library-tests/frameworks/Firebase/src/import_named.ts index 0144f0f24b0d..cd0eabd970d1 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/src/import_named.ts +++ b/javascript/ql/test/library-tests/frameworks/Firebase/src/import_named.ts @@ -1,5 +1,5 @@ import { database } from "firebase"; function test(db: database.Database) { - db.ref("hello"); + db.ref("hello"); // $firebaseRef } diff --git a/javascript/ql/test/library-tests/frameworks/Firebase/src/import_star.ts b/javascript/ql/test/library-tests/frameworks/Firebase/src/import_star.ts index d52d84cdfcf5..fbe3dda15cb0 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/src/import_star.ts +++ b/javascript/ql/test/library-tests/frameworks/Firebase/src/import_star.ts @@ -1,5 +1,5 @@ import * as firebase from "firebase"; function test(db: firebase.database.Database) { - db.ref("hello"); + db.ref("hello"); // $firebaseRef } diff --git a/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected b/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected index e53b029fa3a9..4b517168ae0d 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected @@ -3,33 +3,42 @@ firebaseRef | src/import_named.ts:4:3:4:17 | db.ref("hello") | | src/import_star.ts:4:3:4:17 | db.ref("hello") | | tst.js:5:1:5:22 | fb.data ... ef('x') | +| tst.js:5:1:8:2 | fb.data ... eRef\\n}) | +| tst.js:5:38:5:38 | 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:10:1:13:2 | admin.d ... eRef\\n}) | +| tst.js:10:41:10:41 | x | | tst.js:12:3:12:7 | x.ref | | tst.js:12:3:12:14 | x.ref.parent | +| tst.js:15:1:15:27 | functio ... ef('x') | +| tst.js:15:38:15:38 | x | | tst.js:17:3:17:7 | x.ref | | tst.js:17:3:17:14 | x.ref.parent | +| tst.js:20:1:20:27 | functio ... ef('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: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:50:12:50:48 | this.ge ... value') | | 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') | firebaseSnapshot -| tst.js:5:1:8:2 | fb.data ... ent;\\n}) | +| tst.js:5:1:8:2 | fb.data ... eRef\\n}) | | tst.js:5:38:5:38 | x | -| tst.js:10:1:13:2 | admin.d ... ent;\\n}) | +| tst.js:10:1:13:2 | admin.d ... eRef\\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:34 | this.ge ... hild(x) | | tst.js:50:12:50:48 | this.ge ... value') | -| tst.js:60:1:60:39 | new Fir ... em('x') | +| tst.js:61:36:61:36 | x | firebaseVal | tst.js:6:3:6:9 | x.val() | | tst.js:11:3:11:9 | x.val() | diff --git a/javascript/ql/test/library-tests/frameworks/Firebase/tests.ql b/javascript/ql/test/library-tests/frameworks/Firebase/tests.ql index 13f4a0971021..f3806d103597 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/tests.ql +++ b/javascript/ql/test/library-tests/frameworks/Firebase/tests.ql @@ -1,10 +1,16 @@ import javascript -query predicate firebaseRef(DataFlow::SourceNode ref) { ref = Firebase::Database::ref() } +query predicate firebaseRef(DataFlow::SourceNode ref) { + ref = ModelOutput::getATypeNode("FirebaseDBRef").asSource() +} -query predicate firebaseSnapshot(DataFlow::SourceNode snap) { snap = Firebase::snapshot() } +query predicate firebaseSnapshot(DataFlow::SourceNode snap) { + snap = ModelOutput::getATypeNode("Snapshot").asSource() +} -query predicate firebaseVal(Firebase::FirebaseVal val) { any() } +query predicate firebaseVal(DataFlow::SourceNode val) { + val = ModelOutput::getASourceNode("remote").asSource() +} query predicate requestInputAccess(Http::RequestInputAccess acc) { any() } diff --git a/javascript/ql/test/library-tests/frameworks/Firebase/tests.qlref b/javascript/ql/test/library-tests/frameworks/Firebase/tests.qlref new file mode 100644 index 000000000000..8581a3f8b748 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Firebase/tests.qlref @@ -0,0 +1,2 @@ +query: tests.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/library-tests/frameworks/Firebase/tst.js b/javascript/ql/test/library-tests/frameworks/Firebase/tst.js index fe2ee3d6a993..fdcc185384ba 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/tst.js +++ b/javascript/ql/test/library-tests/frameworks/Firebase/tst.js @@ -2,25 +2,25 @@ 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; -}); +fb.database().ref('x').once('value', x => { // $firebaseSnapshot $firebaseRef + x.val(); // $firebaseVal + x.ref.parent; // $firebaseRef +}); // $firebaseRef $firebaseSnapshot -admin.database().ref('x').once('value', x => { - x.val(); - x.ref.parent; -}); +admin.database().ref('x').once('value', x => { // $firebaseSnapshot $firebaseRef + x.val(); // $firebaseVal + x.ref.parent; // $firebaseRef +}); // $firebaseRef $firebaseSnapshot -functions.database.ref('x').onCreate(x => { - x.val(); - x.ref.parent; +functions.database.ref('x').onCreate(x => {// $firebaseSnapshot $firebaseRef + x.val(); // $firebaseVal + x.ref.parent; // $firebaseRef }); -functions.database.ref('x').onUpdate(x => { - x.before.val(); - x.after.val(); - x.ref.parent; +functions.database.ref('x').onUpdate(x => { // $firebaseSnapshot $firebaseRef + x.before.val(); // $firebaseRef $firebaseSnapshot $firebaseVal + x.after.val(); // $firebaseRef $firebaseSnapshot $firebaseVal + x.ref.parent; // $firebaseRef }); class FirebaseWrapper { @@ -29,7 +29,7 @@ class FirebaseWrapper { } getRef(x) { - return this.firebase.database().ref(x); + return this.firebase.database().ref(x); // $firebaseRef } } @@ -43,22 +43,22 @@ class FirebaseWrapper2 { } getRef(x) { - return this.firebase.database().ref(x); + return this.firebase.database().ref(x); // $firebaseRef } getNewsItem(x) { - return this.getRef(x).child(x).once('value'); + return this.getRef(x).child(x).once('value'); // $firebaseRef $firebaseSnapshot } adjustValue(fn) { - this.firebase.database().ref('x').transaction(fn); + this.firebase.database().ref('x').transaction(fn); // $firebaseRef } } new FirebaseWrapper(firebase.initializeApp()).getRef('/news'); new FirebaseWrapper2().getRef('/news'); new FirebaseWrapper2().getNewsItem('x'); -new FirebaseWrapper2().adjustValue(x => x + 1); +new FirebaseWrapper2().adjustValue(x => x + 1); // $firebaseSnapshot $firebaseVal class Box { constructor(x) { @@ -69,4 +69,4 @@ let box1 = new Box(fb.database()); let box2 = new Box(whatever()); box2.x.ref(); // not a firebase ref -functions.https.onRequest((req, res) => { res.send(req.params.foo); }); +functions.https.onRequest((req, res) => { res.send(req.params.foo); }); // $routeHandler $requestInputAccess $responseSendArgument diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected index 6ba8ab703bff..162dc5de805b 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected @@ -62,6 +62,19 @@ | dragAndDrop.ts:73:29:73:39 | droppedHtml | dragAndDrop.ts:71:27:71:61 | e.dataT ... /html') | dragAndDrop.ts:73:29:73:39 | droppedHtml | Cross-site scripting vulnerability due to $@. | dragAndDrop.ts:71:27:71:61 | e.dataT ... /html') | user-provided value | | event-handler-receiver.js:2:31:2:83 | '
" + ... "
" | firebase-client.js:65:23:65:36 | snapshot.val() | firebase-client.js:66:34:66:57 | "" + ... "
" | Cross-site scripting vulnerability due to $@. | firebase-client.js:65:23:65:36 | snapshot.val() | user-provided value | | jquery.js:7:5:7:34 | "" + ... "
" | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:4:5:4:11 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:5:13:5:19 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:6:11:6:17 | tainted | provenance | | @@ -954,6 +979,31 @@ nodes | event-handler-receiver.js:2:31:2:83 | '" + ... "
" | semmle.label | "" + ... "
" | +| firebase-client.js:66:42:66:48 | message | semmle.label | message | | jquery.js:2:7:2:40 | tainted | semmle.label | tainted | | jquery.js:2:17:2:40 | documen ... .search | semmle.label | documen ... .search | | jquery.js:4:5:4:11 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected index 0ed15b8d92ab..9c3140f36e73 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected @@ -154,6 +154,31 @@ nodes | event-handler-receiver.js:2:31:2:83 | '" + ... "
" | semmle.label | "" + ... "
" | +| firebase-client.js:66:42:66:48 | message | semmle.label | message | | hana.js:11:37:11:40 | rows | semmle.label | rows | | hana.js:11:37:11:51 | rows[0].comment | semmle.label | rows[0].comment | | hana.js:16:37:16:40 | rows | semmle.label | rows | @@ -820,6 +845,18 @@ edges | dragAndDrop.ts:71:27:71:61 | e.dataT ... /html') | dragAndDrop.ts:71:13:71:61 | droppedHtml | provenance | | | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '" + ... "
" | provenance | | | hana.js:11:37:11:40 | rows | hana.js:11:37:11:51 | rows[0].comment | provenance | | | hana.js:16:37:16:40 | rows | hana.js:16:37:16:51 | rows[0].comment | provenance | | | hana.js:19:37:19:40 | rows | hana.js:19:37:19:51 | rows[0].comment | provenance | | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/firebase-client.js b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/firebase-client.js new file mode 100644 index 000000000000..f11b549f7799 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/firebase-client.js @@ -0,0 +1,68 @@ +import firebase from 'firebase/app'; +import 'firebase/database'; + + +firebase.database().ref("/userMessages/message1").once("value") + .then((x) => { + document.getElementById("messageDisplay").innerHTML = x.val(); // $ Alert + document.getElementById("messageDisplay").innerHTML = x.exportVal().message; // $ Alert + x.ref.parent.parent.once('value', parentSnapshot => { + document.getElementById("messageDisplay").innerHTML = parentSnapshot.val(); // $ Alert + }); + + x.ref.parent.child('bio').once('value', (bioSnapshot) => { + document.getElementById('userBio').innerHTML = bioSnapshot.val(); // $ Alert + }); + + x.forEach((childSnapshot) => { + const data = childSnapshot.val(); // $ Source + document.getElementById("userList").innerHTML += `" + message + "
"; // $ Alert + }); +} diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected index 9ad04af3a2c5..e7b2cf2e3382 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected @@ -57,6 +57,23 @@ | fastify.js:84:30:84:43 | reply.userCode | fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:79:20:79:42 | request ... plyCode | user-provided value | | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:41 | request.query | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:41 | request.query | user-provided value | | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:51 | request ... plyCode | user-provided value | +| firebase-server2.js:4:10:4:23 | snapshot.val() | firebase-server2.js:4:10:4:23 | snapshot.val() | firebase-server2.js:4:10:4:23 | snapshot.val() | This code execution depends on a $@. | firebase-server2.js:4:10:4:23 | snapshot.val() | user-provided value | +| firebase-server2.js:8:14:8:33 | followSnapshot.val() | firebase-server2.js:8:14:8:33 | followSnapshot.val() | firebase-server2.js:8:14:8:33 | followSnapshot.val() | This code execution depends on a $@. | firebase-server2.js:8:14:8:33 | followSnapshot.val() | user-provided value | +| firebase-server.js:7:10:7:16 | x.val() | firebase-server.js:7:10:7:16 | x.val() | firebase-server.js:7:10:7:16 | x.val() | This code execution depends on a $@. | firebase-server.js:7:10:7:16 | x.val() | user-provided value | +| firebase-server.js:8:10:8:22 | x.exportVal() | firebase-server.js:8:10:8:22 | x.exportVal() | firebase-server.js:8:10:8:22 | x.exportVal() | This code execution depends on a $@. | firebase-server.js:8:10:8:22 | x.exportVal() | user-provided value | +| firebase-server.js:10:14:10:33 | parentSnapshot.val() | firebase-server.js:10:14:10:33 | parentSnapshot.val() | firebase-server.js:10:14:10:33 | parentSnapshot.val() | This code execution depends on a $@. | firebase-server.js:10:14:10:33 | parentSnapshot.val() | user-provided value | +| firebase-server.js:14:10:14:23 | x.before.val() | firebase-server.js:14:10:14:23 | x.before.val() | firebase-server.js:14:10:14:23 | x.before.val() | This code execution depends on a $@. | firebase-server.js:14:10:14:23 | x.before.val() | user-provided value | +| firebase-server.js:15:10:15:22 | x.after.val() | firebase-server.js:15:10:15:22 | x.after.val() | firebase-server.js:15:10:15:22 | x.after.val() | This code execution depends on a $@. | firebase-server.js:15:10:15:22 | x.after.val() | user-provided value | +| firebase-server.js:17:14:17:38 | grandPa ... t.val() | firebase-server.js:17:14:17:38 | grandPa ... t.val() | firebase-server.js:17:14:17:38 | grandPa ... t.val() | This code execution depends on a $@. | firebase-server.js:17:14:17:38 | grandPa ... t.val() | user-provided value | +| firebase-server.js:21:10:21:27 | change.after.val() | firebase-server.js:21:10:21:27 | change.after.val() | firebase-server.js:21:10:21:27 | change.after.val() | This code execution depends on a $@. | firebase-server.js:21:10:21:27 | change.after.val() | user-provided value | +| firebase-server.js:22:10:22:28 | change.before.val() | firebase-server.js:22:10:22:28 | change.before.val() | firebase-server.js:22:10:22:28 | change.before.val() | This code execution depends on a $@. | firebase-server.js:22:10:22:28 | change.before.val() | user-provided value | +| firebase-server.js:26:10:26:21 | change.val() | firebase-server.js:26:10:26:21 | change.val() | firebase-server.js:26:10:26:21 | change.val() | This code execution depends on a $@. | firebase-server.js:26:10:26:21 | change.val() | user-provided value | +| firebase-server.js:27:10:27:21 | change.val() | firebase-server.js:27:10:27:21 | change.val() | firebase-server.js:27:10:27:21 | change.val() | This code execution depends on a $@. | firebase-server.js:27:10:27:21 | change.val() | user-provided value | +| firebase-server.js:33:25:33:44 | statusSnapshot.val() | firebase-server.js:33:25:33:44 | statusSnapshot.val() | firebase-server.js:33:25:33:44 | statusSnapshot.val() | This code execution depends on a $@. | firebase-server.js:33:25:33:44 | statusSnapshot.val() | user-provided value | +| firebase-server.js:44:12:44:30 | childSnapshot.val() | firebase-server.js:44:12:44:30 | childSnapshot.val() | firebase-server.js:44:12:44:30 | childSnapshot.val() | This code execution depends on a $@. | firebase-server.js:44:12:44:30 | childSnapshot.val() | user-provided value | +| firebase-server.js:55:10:55:19 | snap.val() | firebase-server.js:55:10:55:19 | snap.val() | firebase-server.js:55:10:55:19 | snap.val() | This code execution depends on a $@. | firebase-server.js:55:10:55:19 | snap.val() | user-provided value | +| firebase-server.js:68:12:68:31 | tokensSnapshot.val() | firebase-server.js:68:12:68:31 | tokensSnapshot.val() | firebase-server.js:68:12:68:31 | tokensSnapshot.val() | This code execution depends on a $@. | firebase-server.js:68:12:68:31 | tokensSnapshot.val() | user-provided value | +| firebase-server.js:70:12:70:21 | snap.val() | firebase-server.js:70:12:70:21 | snap.val() | firebase-server.js:70:12:70:21 | snap.val() | This code execution depends on a $@. | firebase-server.js:70:12:70:21 | snap.val() | user-provided value | | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | This code execution depends on a $@. | module.js:9:16:9:29 | req.query.code | user-provided value | | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | This code execution depends on a $@. | module.js:11:17:11:30 | req.query.code | user-provided value | | react-native.js:8:32:8:38 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:32:8:38 | tainted | This code execution depends on a $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value | @@ -268,6 +285,23 @@ nodes | fastify.js:94:29:94:41 | request.query | semmle.label | request.query | | fastify.js:94:29:94:51 | request ... plyCode | semmle.label | request ... plyCode | | fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | +| firebase-server2.js:4:10:4:23 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-server2.js:8:14:8:33 | followSnapshot.val() | semmle.label | followSnapshot.val() | +| firebase-server.js:7:10:7:16 | x.val() | semmle.label | x.val() | +| firebase-server.js:8:10:8:22 | x.exportVal() | semmle.label | x.exportVal() | +| firebase-server.js:10:14:10:33 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | +| firebase-server.js:14:10:14:23 | x.before.val() | semmle.label | x.before.val() | +| firebase-server.js:15:10:15:22 | x.after.val() | semmle.label | x.after.val() | +| firebase-server.js:17:14:17:38 | grandPa ... t.val() | semmle.label | grandPa ... t.val() | +| firebase-server.js:21:10:21:27 | change.after.val() | semmle.label | change.after.val() | +| firebase-server.js:22:10:22:28 | change.before.val() | semmle.label | change.before.val() | +| firebase-server.js:26:10:26:21 | change.val() | semmle.label | change.val() | +| firebase-server.js:27:10:27:21 | change.val() | semmle.label | change.val() | +| firebase-server.js:33:25:33:44 | statusSnapshot.val() | semmle.label | statusSnapshot.val() | +| firebase-server.js:44:12:44:30 | childSnapshot.val() | semmle.label | childSnapshot.val() | +| firebase-server.js:55:10:55:19 | snap.val() | semmle.label | snap.val() | +| firebase-server.js:68:12:68:31 | tokensSnapshot.val() | semmle.label | tokensSnapshot.val() | +| firebase-server.js:70:12:70:21 | snap.val() | semmle.label | snap.val() | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected index aa23d7a6d5a1..ceffde2c3c56 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected @@ -176,6 +176,23 @@ nodes | fastify.js:94:29:94:41 | request.query | semmle.label | request.query | | fastify.js:94:29:94:51 | request ... plyCode | semmle.label | request ... plyCode | | fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | +| firebase-server2.js:4:10:4:23 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-server2.js:8:14:8:33 | followSnapshot.val() | semmle.label | followSnapshot.val() | +| firebase-server.js:7:10:7:16 | x.val() | semmle.label | x.val() | +| firebase-server.js:8:10:8:22 | x.exportVal() | semmle.label | x.exportVal() | +| firebase-server.js:10:14:10:33 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | +| firebase-server.js:14:10:14:23 | x.before.val() | semmle.label | x.before.val() | +| firebase-server.js:15:10:15:22 | x.after.val() | semmle.label | x.after.val() | +| firebase-server.js:17:14:17:38 | grandPa ... t.val() | semmle.label | grandPa ... t.val() | +| firebase-server.js:21:10:21:27 | change.after.val() | semmle.label | change.after.val() | +| firebase-server.js:22:10:22:28 | change.before.val() | semmle.label | change.before.val() | +| firebase-server.js:26:10:26:21 | change.val() | semmle.label | change.val() | +| firebase-server.js:27:10:27:21 | change.val() | semmle.label | change.val() | +| firebase-server.js:33:25:33:44 | statusSnapshot.val() | semmle.label | statusSnapshot.val() | +| firebase-server.js:44:12:44:30 | childSnapshot.val() | semmle.label | childSnapshot.val() | +| firebase-server.js:55:10:55:19 | snap.val() | semmle.label | snap.val() | +| firebase-server.js:68:12:68:31 | tokensSnapshot.val() | semmle.label | tokensSnapshot.val() | +| firebase-server.js:70:12:70:21 | snap.val() | semmle.label | snap.val() | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server.js new file mode 100644 index 000000000000..e71b963df8ae --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server.js @@ -0,0 +1,72 @@ +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); + +admin.initializeApp(); + +functions.database.ref('x').onCreate(x => { + eval(x.val()); // $ Alert[js/code-injection] + eval(x.exportVal()); // $ Alert[js/code-injection] + x.ref.parent.once('value', parentSnapshot => { + eval(parentSnapshot.val()); // $ Alert[js/code-injection] + }); +}); +functions.database.ref('x').onUpdate(x => { + eval(x.before.val()); // $ Alert[js/code-injection] + eval(x.after.val()); // $ Alert[js/code-injection] + x.ref.parent.parent.once('value', grandParentSnapshot => { + eval(grandParentSnapshot.val()); // $ Alert[js/code-injection] + }); +}); +functions.database.ref('/messages/{messageId}').onWrite((change, context) => { + eval(change.after.val()); // $ Alert[js/code-injection] + eval(change.before.val()); // $ Alert[js/code-injection] +}); + +functions.database.ref('/messages/{messageId}').onDelete((change, context) => { + eval(change.val()); // $ Alert[js/code-injection] + eval(change.val()); // $ Alert[js/code-injection] +}); + +functions.database.ref('/status/{uid}').onUpdate(async (change, context) => { + const eventStatus = change.after.val(); + const statusSnapshot = await change.after.ref.once('value'); + const status = eval(statusSnapshot.val()); // $ Alert[js/code-injection] + return null; +}); + +function fun(category){ + let query = admin.database().ref(`/users/messages`); + query = query.orderByChild('category').equalTo(category); + const snapshot = query.once('value'); + let messages = []; + snapshot.forEach((childSnapshot) => { + messages.push({key: childSnapshot.key, message: childSnapshot.val().message}); + eval(childSnapshot.val()); // $ Alert[js/code-injection] + }); +} + +async function fun3(uid, postId, size) { + let app; + const config = JSON.parse(process.env.FIREBASE_CONFIG); + config.databaseAuthVariableOverride = {uid: uid}; + app = admin.initializeApp(config, uid); + const imageUrlRef = app.database().ref(`/posts`); + const snap = await imageUrlRef.once('value'); + eval(snap.val()); // $ Alert[js/code-injection] +} + +exports.sendFollowerNotification = functions.database.ref('/followers/{followedUid}/{followerUid}').onWrite(async (change, context) => { + const followerUid = context.params.followerUid; + const followedUid = context.params.followedUid; + const getDeviceTokensPromise = admin.database().ref(`/users/${followedUid}/notificationTokens`).once('value'); + + const getFollowerProfilePromise = admin.auth().getUser(followerUid); + + const results = await Promise.all([getDeviceTokensPromise, getFollowerProfilePromise]); + let tokensSnapshot = results[0]; + const follower = results[1]; + eval(tokensSnapshot.val()); // $ Alert[js/code-injection] + let snap = await getDeviceTokensPromise; + eval(snap.val()); // $ Alert[js/code-injection] + return follower; +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server2.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server2.js new file mode 100644 index 000000000000..adfd8d83ec48 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server2.js @@ -0,0 +1,11 @@ +function globalFirebaseUsage() { + var usersRef = firebase.database().ref('users'); + usersRef.on('child_added', function(snapshot) { + eval(snapshot.val()); // $ Alert[js/code-injection] + var followUserRef = firebase.database().ref('followers/' + uid + '/' + this.currentUid); + + followUserRef.on('value', function(followSnapshot) { + eval(followSnapshot.val()); // $ Alert[js/code-injection] + }); + }); +};