From 620801781f40b2010779cfdd0e850ae0455c092f Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 15 Apr 2025 13:14:43 +0200 Subject: [PATCH 01/11] Added test cases for `firebase` both client and server sides. --- .../Security/CWE-079/DomBasedXss/Xss.expected | 40 ++++++++++++++++ .../XssWithAdditionalSources.expected | 29 ++++++++++++ .../CWE-079/DomBasedXss/firebase-client.js | 46 +++++++++++++++++++ .../CodeInjection/CodeInjection.expected | 12 +++++ .../HeuristicSourceCodeInjection.expected | 6 +++ .../CWE-094/CodeInjection/firebase-server.js | 19 ++++++++ 6 files changed, 152 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/firebase-client.js create mode 100644 javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server.js 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..9b14d8058f9f 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,17 @@ | 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 | '

' | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '

' | Cross-site scripting vulnerability due to $@. | event-handler-receiver.js:2:49:2:61 | location.href | user-provided value | | express.js:6:15:6:33 | req.param("wobble") | express.js:6:15:6:33 | req.param("wobble") | express.js:6:15:6:33 | req.param("wobble") | Cross-site scripting vulnerability due to $@. | express.js:6:15:6:33 | req.param("wobble") | user-provided value | +| firebase-client.js:7:59:7:65 | x.val() | firebase-client.js:7:59:7:65 | x.val() | firebase-client.js:7:59:7:65 | x.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:7:59:7:65 | x.val() | user-provided value | +| firebase-client.js:8:59:8:79 | x.expor ... message | firebase-client.js:8:59:8:71 | x.exportVal() | firebase-client.js:8:59:8:79 | x.expor ... message | Cross-site scripting vulnerability due to $@. | firebase-client.js:8:59:8:71 | x.exportVal() | user-provided value | +| firebase-client.js:10:63:10:82 | parentSnapshot.val() | firebase-client.js:10:63:10:82 | parentSnapshot.val() | firebase-client.js:10:63:10:82 | parentSnapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:10:63:10:82 | parentSnapshot.val() | user-provided value | +| firebase-client.js:14:54:14:70 | bioSnapshot.val() | firebase-client.js:14:54:14:70 | bioSnapshot.val() | firebase-client.js:14:54:14:70 | bioSnapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:14:54:14:70 | bioSnapshot.val() | user-provided value | +| firebase-client.js:19:56:19:84 | `
$ ...
` | firebase-client.js:18:20:18:38 | childSnapshot.val() | firebase-client.js:19:56:19:84 | `
$ ...
` | Cross-site scripting vulnerability due to $@. | firebase-client.js:18:20:18:38 | childSnapshot.val() | user-provided value | +| firebase-client.js:25:59:25:65 | x.val() | firebase-client.js:25:59:25:65 | x.val() | firebase-client.js:25:59:25:65 | x.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:25:59:25:65 | x.val() | user-provided value | +| firebase-client.js:26:59:26:79 | x.expor ... message | firebase-client.js:26:59:26:71 | x.exportVal() | firebase-client.js:26:59:26:79 | x.expor ... message | Cross-site scripting vulnerability due to $@. | firebase-client.js:26:59:26:71 | x.exportVal() | user-provided value | +| firebase-client.js:28:63:28:82 | parentSnapshot.val() | firebase-client.js:28:63:28:82 | parentSnapshot.val() | firebase-client.js:28:63:28:82 | parentSnapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:28:63:28:82 | parentSnapshot.val() | user-provided value | +| firebase-client.js:33:52:33:65 | snapshot.val() | firebase-client.js:33:52:33:65 | snapshot.val() | firebase-client.js:33:52:33:65 | snapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:33:52:33:65 | snapshot.val() | user-provided value | +| firebase-client.js:38:56:38:67 | userData.bio | firebase-client.js:37:22:37:35 | snapshot.val() | firebase-client.js:38:56:38:67 | userData.bio | Cross-site scripting vulnerability due to $@. | firebase-client.js:37:22:37:35 | snapshot.val() | user-provided value | +| firebase-client.js:44:55:44:74 | parentSnapshot.val() | firebase-client.js:44:55:44:74 | parentSnapshot.val() | firebase-client.js:44:55:44:74 | parentSnapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:44:55:44:74 | parentSnapshot.val() | user-provided value | | jquery.js:7:5:7:34 | "
" | jquery.js:2:17:2:40 | documen ... .search | jquery.js:7:5:7:34 | "
" | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:40 | documen ... .search | user-provided value | | jquery.js:8:18:8:34 | "XSS: " + tainted | jquery.js:2:17:2:40 | documen ... .search | jquery.js:8:18:8:34 | "XSS: " + tainted | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:40 | documen ... .search | user-provided value | | jquery.js:10:5:10:40 | "" + ... "" | jquery.js:10:13:10:20 | location | jquery.js:10:5:10:40 | "" + ... "" | Cross-site scripting vulnerability due to $@. | jquery.js:10:13:10:20 | location | user-provided value | @@ -352,6 +363,15 @@ 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 | | | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '

' | provenance | Config | +| firebase-client.js:8:59:8:71 | x.exportVal() | firebase-client.js:8:59:8:79 | x.expor ... message | provenance | | +| firebase-client.js:18:13:18:38 | data | firebase-client.js:19:64:19:67 | data | provenance | | +| firebase-client.js:18:20:18:38 | childSnapshot.val() | firebase-client.js:18:13:18:38 | data | provenance | | +| firebase-client.js:19:64:19:67 | data | firebase-client.js:19:64:19:76 | data.username | provenance | | +| firebase-client.js:19:64:19:76 | data.username | firebase-client.js:19:56:19:84 | `
$ ...
` | provenance | | +| firebase-client.js:26:59:26:71 | x.exportVal() | firebase-client.js:26:59:26:79 | x.expor ... message | provenance | | +| firebase-client.js:37:11:37:35 | userData | firebase-client.js:38:56:38:63 | userData | provenance | | +| firebase-client.js:37:22:37:35 | snapshot.val() | firebase-client.js:37:11:37:35 | userData | provenance | | +| firebase-client.js:38:56:38:63 | userData | firebase-client.js:38:56:38:67 | userData.bio | 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 +974,26 @@ nodes | event-handler-receiver.js:2:31:2:83 | '

' | semmle.label | '

' | | event-handler-receiver.js:2:49:2:61 | location.href | semmle.label | location.href | | express.js:6:15:6:33 | req.param("wobble") | semmle.label | req.param("wobble") | +| firebase-client.js:7:59:7:65 | x.val() | semmle.label | x.val() | +| firebase-client.js:8:59:8:71 | x.exportVal() | semmle.label | x.exportVal() | +| firebase-client.js:8:59:8:79 | x.expor ... message | semmle.label | x.expor ... message | +| firebase-client.js:10:63:10:82 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | +| firebase-client.js:14:54:14:70 | bioSnapshot.val() | semmle.label | bioSnapshot.val() | +| firebase-client.js:18:13:18:38 | data | semmle.label | data | +| firebase-client.js:18:20:18:38 | childSnapshot.val() | semmle.label | childSnapshot.val() | +| firebase-client.js:19:56:19:84 | `
$ ...
` | semmle.label | `
$ ...
` | +| firebase-client.js:19:64:19:67 | data | semmle.label | data | +| firebase-client.js:19:64:19:76 | data.username | semmle.label | data.username | +| firebase-client.js:25:59:25:65 | x.val() | semmle.label | x.val() | +| firebase-client.js:26:59:26:71 | x.exportVal() | semmle.label | x.exportVal() | +| firebase-client.js:26:59:26:79 | x.expor ... message | semmle.label | x.expor ... message | +| firebase-client.js:28:63:28:82 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | +| firebase-client.js:33:52:33:65 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-client.js:37:11:37:35 | userData | semmle.label | userData | +| firebase-client.js:37:22:37:35 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-client.js:38:56:38:63 | userData | semmle.label | userData | +| firebase-client.js:38:56:38:67 | userData.bio | semmle.label | userData.bio | +| firebase-client.js:44:55:44:74 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | | 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..d24dbcb747ce 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,26 @@ nodes | event-handler-receiver.js:2:31:2:83 | '

' | semmle.label | '

' | | event-handler-receiver.js:2:49:2:61 | location.href | semmle.label | location.href | | express.js:6:15:6:33 | req.param("wobble") | semmle.label | req.param("wobble") | +| firebase-client.js:7:59:7:65 | x.val() | semmle.label | x.val() | +| firebase-client.js:8:59:8:71 | x.exportVal() | semmle.label | x.exportVal() | +| firebase-client.js:8:59:8:79 | x.expor ... message | semmle.label | x.expor ... message | +| firebase-client.js:10:63:10:82 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | +| firebase-client.js:14:54:14:70 | bioSnapshot.val() | semmle.label | bioSnapshot.val() | +| firebase-client.js:18:13:18:38 | data | semmle.label | data | +| firebase-client.js:18:20:18:38 | childSnapshot.val() | semmle.label | childSnapshot.val() | +| firebase-client.js:19:56:19:84 | `
$ ...
` | semmle.label | `
$ ...
` | +| firebase-client.js:19:64:19:67 | data | semmle.label | data | +| firebase-client.js:19:64:19:76 | data.username | semmle.label | data.username | +| firebase-client.js:25:59:25:65 | x.val() | semmle.label | x.val() | +| firebase-client.js:26:59:26:71 | x.exportVal() | semmle.label | x.exportVal() | +| firebase-client.js:26:59:26:79 | x.expor ... message | semmle.label | x.expor ... message | +| firebase-client.js:28:63:28:82 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | +| firebase-client.js:33:52:33:65 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-client.js:37:11:37:35 | userData | semmle.label | userData | +| firebase-client.js:37:22:37:35 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-client.js:38:56:38:63 | userData | semmle.label | userData | +| firebase-client.js:38:56:38:67 | userData.bio | semmle.label | userData.bio | +| firebase-client.js:44:55:44:74 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | | 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 +840,15 @@ 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 | | | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '

' | provenance | Config | +| firebase-client.js:8:59:8:71 | x.exportVal() | firebase-client.js:8:59:8:79 | x.expor ... message | provenance | | +| firebase-client.js:18:13:18:38 | data | firebase-client.js:19:64:19:67 | data | provenance | | +| firebase-client.js:18:20:18:38 | childSnapshot.val() | firebase-client.js:18:13:18:38 | data | provenance | | +| firebase-client.js:19:64:19:67 | data | firebase-client.js:19:64:19:76 | data.username | provenance | | +| firebase-client.js:19:64:19:76 | data.username | firebase-client.js:19:56:19:84 | `
$ ...
` | provenance | | +| firebase-client.js:26:59:26:71 | x.exportVal() | firebase-client.js:26:59:26:79 | x.expor ... message | provenance | | +| firebase-client.js:37:11:37:35 | userData | firebase-client.js:38:56:38:63 | userData | provenance | | +| firebase-client.js:37:22:37:35 | snapshot.val() | firebase-client.js:37:11:37:35 | userData | provenance | | +| firebase-client.js:38:56:38:63 | userData | firebase-client.js:38:56:38:67 | userData.bio | 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..1d3caa1fa040 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/firebase-client.js @@ -0,0 +1,46 @@ +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 += `
${data.username}
`; // $ Alert + }); + }) + .catch(); + +firebase.database().ref('/users').on('value', (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 + }); +}); + +firebase.database().refFromURL("https://example.com").once("value", (snapshot) => { + document.getElementById("content").innerHTML = snapshot.val(); // $ Alert +}); + +firebase.database().ref("users").child("12345").once("value", (snapshot) => { + const userData = snapshot.val(); // $ Source + document.getElementById("userProfile").innerHTML = userData.bio; // $ Alert +}); + +firebase.database().ref("users/12345/profile").once("value", (snapshot) => { + const rootref = snapshot.ref.root; + rootref.once("value", (parentSnapshot) => { + document.getElementById("userData").innerHTML = parentSnapshot.val(); // $ 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 a81b9dbcce0f..2265cb20e5bf 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 @@ -27,6 +27,12 @@ | express.js:20:34:20:38 | taint | express.js:19:17:19:35 | req.param("wobble") | express.js:20:34:20:38 | taint | This code execution depends on a $@. | express.js:19:17:19:35 | req.param("wobble") | user-provided value | | express.js:36:15:36:19 | taint | express.js:27:17:27:35 | req.param("wobble") | express.js:36:15:36:19 | taint | This code execution depends on a $@. | express.js:27:17:27:35 | req.param("wobble") | user-provided value | | express.js:43:10:43:12 | msg | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | This code execution depends on a $@. | express.js:42:30:42:32 | msg | 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 | | 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 | @@ -144,6 +150,12 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| 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() | | 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 ba973943e124..e985466af8fd 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 @@ -82,6 +82,12 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| 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() | | 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..137442d3c5db --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server.js @@ -0,0 +1,19 @@ +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] + }); +}); From fdddcb11e2d286a1d6dec1e98fbf70a62e7e1d83 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 15 Apr 2025 18:25:21 +0200 Subject: [PATCH 02/11] Added `firebase` as `MaD` --- javascript/ql/lib/ext/firebase.model.yml | 20 +++++++++++++ .../semmle/javascript/frameworks/Firebase.qll | 30 +++++++++---------- 2 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 javascript/ql/lib/ext/firebase.model.yml diff --git a/javascript/ql/lib/ext/firebase.model.yml b/javascript/ql/lib/ext/firebase.model.yml new file mode 100644 index 000000000000..7f98ee363a76 --- /dev/null +++ b/javascript/ql/lib/ext/firebase.model.yml @@ -0,0 +1,20 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["FirebaseDBRef", "firebase/app", "Member[database].ReturnValue.Member[ref,refFromURL].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].Argument[1].Parameter[0]"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].ReturnValue.Member[then].Argument[0].Parameter[0]"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,root,parent,before,after]"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[child].ReturnValue"] + + - ["FirebaseDBRef", "firebase-functions", "Member[database].Member[ref,refFromURL].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[onCreate,onUpdate].Argument[0].Parameter[0]"] + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ["FirebaseDBRef", "Member[val,exportVal].ReturnValue", 'remote'] + - ["FirebaseDBRef", "Member[forEach].Argument[0].Parameter[0].Member[val,exportVal].ReturnValue", 'remote'] + \ No newline at end of file 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" } + // } } From 0ed3758a1e53bd7dc04d9598292a5ecda0f70539 Mon Sep 17 00:00:00 2001 From: Napalys Date: Wed, 16 Apr 2025 12:15:21 +0200 Subject: [PATCH 03/11] Added additional test cases for `firebase-admin` --- .../CWE-079/DomBasedXss/firebase-client.js | 9 +++++ .../CWE-094/CodeInjection/firebase-server.js | 37 +++++++++++++++++++ 2 files changed, 46 insertions(+) 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 index 1d3caa1fa040..34d1fe0ee969 100644 --- 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 @@ -44,3 +44,12 @@ firebase.database().ref("users/12345/profile").once("value", (snapshot) => { document.getElementById("userData").innerHTML = parentSnapshot.val(); // $ Alert }); }); + +function fun2(category){ + dbPath = 'users/' + firebase.auth().currentUser.uid; + dbRef = firebase.database().ref(dbPath); + dbRef.set({'test': randomString}).then(function() {return dbRef.once('value');}).then(function(snapshot) { + document.getElementById("userData").innerHTML = snapshot.val(); // $ MISSING: Alert + return dbRef.remove(); + }); +} 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 index 137442d3c5db..2cd068141f67 100644 --- 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 @@ -17,3 +17,40 @@ functions.database.ref('x').onUpdate(x => { eval(grandParentSnapshot.val()); // $ Alert[js/code-injection] }); }); +functions.database.ref('/messages/{messageId}').onWrite((change, context) => { + eval(change.after.val()); // $ MISSING: Alert[js/code-injection] + eval(change.before.val()); // $ MISSING: Alert[js/code-injection] +}); + +functions.database.ref('/messages/{messageId}').onDelete((change, context) => { + eval(change.val()); // $ MISSING: Alert[js/code-injection] + eval(change.val()); // $ MISSING: 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()); // $ MISSING: 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()); // $ MISSING: 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()); // $ MISSING: Alert[js/code-injection] +} From 269bb3fd0d39dd7dea4c48ffb1180bdd3ba0b027 Mon Sep 17 00:00:00 2001 From: Napalys Date: Wed, 16 Apr 2025 12:18:46 +0200 Subject: [PATCH 04/11] Added modeling of `firebase-admin` and some other functions. --- javascript/ql/lib/ext/firebase.model.yml | 10 ++++++---- .../Security/CWE-079/DomBasedXss/Xss.expected | 2 ++ .../DomBasedXss/XssWithAdditionalSources.expected | 1 + .../CWE-079/DomBasedXss/firebase-client.js | 2 +- .../CWE-094/CodeInjection/CodeInjection.expected | 14 ++++++++++++++ .../HeuristicSourceCodeInjection.expected | 7 +++++++ .../CWE-094/CodeInjection/firebase-server.js | 14 +++++++------- 7 files changed, 38 insertions(+), 12 deletions(-) diff --git a/javascript/ql/lib/ext/firebase.model.yml b/javascript/ql/lib/ext/firebase.model.yml index 7f98ee363a76..b2673db236b3 100644 --- a/javascript/ql/lib/ext/firebase.model.yml +++ b/javascript/ql/lib/ext/firebase.model.yml @@ -7,14 +7,16 @@ extensions: - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].Argument[1].Parameter[0]"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].ReturnValue.Member[then].Argument[0].Parameter[0]"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,root,parent,before,after]"] - - ["FirebaseDBRef", "FirebaseDBRef", "Member[child].ReturnValue"] - + - ["FirebaseDBRef", "FirebaseDBRef", "Member[child,once,on,orderByChild,equalTo,endAt,startAt,limitToLast,orderByKey].ReturnValue"] - ["FirebaseDBRef", "firebase-functions", "Member[database].Member[ref,refFromURL].ReturnValue"] - - ["FirebaseDBRef", "FirebaseDBRef", "Member[onCreate,onUpdate].Argument[0].Parameter[0]"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[onCreate,onUpdate,onWrite,onDelete,transaction].Argument[0].Parameter[0]"] + - ["FirebaseDBRef", "firebase-admin", "Member[database].ReturnValue.Member[ref,refFromURL].ReturnValue"] + - ["FirebaseApp", "firebase-admin", "Member[initializeApp,app].ReturnValue"] + - ["FirebaseDBRef", "FirebaseApp", "Member[database].ReturnValue.Member[ref,refFromURL].ReturnValue"] + - addsTo: pack: codeql/javascript-all extensible: sourceModel data: - ["FirebaseDBRef", "Member[val,exportVal].ReturnValue", 'remote'] - ["FirebaseDBRef", "Member[forEach].Argument[0].Parameter[0].Member[val,exportVal].ReturnValue", 'remote'] - \ No newline at end of file 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 9b14d8058f9f..ff3c18524a45 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 @@ -73,6 +73,7 @@ | firebase-client.js:33:52:33:65 | snapshot.val() | firebase-client.js:33:52:33:65 | snapshot.val() | firebase-client.js:33:52:33:65 | snapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:33:52:33:65 | snapshot.val() | user-provided value | | firebase-client.js:38:56:38:67 | userData.bio | firebase-client.js:37:22:37:35 | snapshot.val() | firebase-client.js:38:56:38:67 | userData.bio | Cross-site scripting vulnerability due to $@. | firebase-client.js:37:22:37:35 | snapshot.val() | user-provided value | | firebase-client.js:44:55:44:74 | parentSnapshot.val() | firebase-client.js:44:55:44:74 | parentSnapshot.val() | firebase-client.js:44:55:44:74 | parentSnapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:44:55:44:74 | parentSnapshot.val() | user-provided value | +| firebase-client.js:52:57:52:70 | snapshot.val() | firebase-client.js:52:57:52:70 | snapshot.val() | firebase-client.js:52:57:52:70 | snapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:52:57:52:70 | snapshot.val() | user-provided value | | jquery.js:7:5:7:34 | "
" | jquery.js:2:17:2:40 | documen ... .search | jquery.js:7:5:7:34 | "
" | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:40 | documen ... .search | user-provided value | | jquery.js:8:18:8:34 | "XSS: " + tainted | jquery.js:2:17:2:40 | documen ... .search | jquery.js:8:18:8:34 | "XSS: " + tainted | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:40 | documen ... .search | user-provided value | | jquery.js:10:5:10:40 | "" + ... "" | jquery.js:10:13:10:20 | location | jquery.js:10:5:10:40 | "" + ... "" | Cross-site scripting vulnerability due to $@. | jquery.js:10:13:10:20 | location | user-provided value | @@ -994,6 +995,7 @@ nodes | firebase-client.js:38:56:38:63 | userData | semmle.label | userData | | firebase-client.js:38:56:38:67 | userData.bio | semmle.label | userData.bio | | firebase-client.js:44:55:44:74 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | +| firebase-client.js:52:57:52:70 | snapshot.val() | semmle.label | snapshot.val() | | 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 d24dbcb747ce..c5989c506e01 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 @@ -174,6 +174,7 @@ nodes | firebase-client.js:38:56:38:63 | userData | semmle.label | userData | | firebase-client.js:38:56:38:67 | userData.bio | semmle.label | userData.bio | | firebase-client.js:44:55:44:74 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | +| firebase-client.js:52:57:52:70 | snapshot.val() | semmle.label | snapshot.val() | | 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 | 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 index 34d1fe0ee969..327893d2b127 100644 --- 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 @@ -49,7 +49,7 @@ function fun2(category){ dbPath = 'users/' + firebase.auth().currentUser.uid; dbRef = firebase.database().ref(dbPath); dbRef.set({'test': randomString}).then(function() {return dbRef.once('value');}).then(function(snapshot) { - document.getElementById("userData").innerHTML = snapshot.val(); // $ MISSING: Alert + document.getElementById("userData").innerHTML = snapshot.val(); // $ Alert return dbRef.remove(); }); } 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 2265cb20e5bf..bcf5b233d6e7 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 @@ -33,6 +33,13 @@ | 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 | | 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 | @@ -156,6 +163,13 @@ nodes | 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() | | 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 e985466af8fd..95c20ee333e2 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 @@ -88,6 +88,13 @@ nodes | 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() | | 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 index 2cd068141f67..675f83fc5f0e 100644 --- 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 @@ -18,19 +18,19 @@ functions.database.ref('x').onUpdate(x => { }); }); functions.database.ref('/messages/{messageId}').onWrite((change, context) => { - eval(change.after.val()); // $ MISSING: Alert[js/code-injection] - eval(change.before.val()); // $ MISSING: Alert[js/code-injection] + 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()); // $ MISSING: Alert[js/code-injection] - eval(change.val()); // $ MISSING: Alert[js/code-injection] + 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()); // $ MISSING: Alert[js/code-injection] + const status = eval(statusSnapshot.val()); // $ Alert[js/code-injection] return null; }); @@ -41,7 +41,7 @@ function fun(category){ let messages = []; snapshot.forEach((childSnapshot) => { messages.push({key: childSnapshot.key, message: childSnapshot.val().message}); - eval(childSnapshot.val()); // $ MISSING: Alert[js/code-injection] + eval(childSnapshot.val()); // $ Alert[js/code-injection] }); } @@ -52,5 +52,5 @@ async function fun3(uid, postId, size) { app = admin.initializeApp(config, uid); const imageUrlRef = app.database().ref(`/posts`); const snap = await imageUrlRef.once('value'); - eval(snap.val()); // $ MISSING: Alert[js/code-injection] + eval(snap.val()); // $ Alert[js/code-injection] } From 009ff29c6fa0c4cda7c6d767e13751436af63a0d Mon Sep 17 00:00:00 2001 From: Napalys Date: Wed, 16 Apr 2025 12:29:36 +0200 Subject: [PATCH 05/11] Cleaned up `firebase.model.yml` to remove duplicates and enhance readability --- javascript/ql/lib/ext/firebase.model.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/javascript/ql/lib/ext/firebase.model.yml b/javascript/ql/lib/ext/firebase.model.yml index b2673db236b3..2aaa4f919e7a 100644 --- a/javascript/ql/lib/ext/firebase.model.yml +++ b/javascript/ql/lib/ext/firebase.model.yml @@ -3,13 +3,14 @@ extensions: pack: codeql/javascript-all extensible: typeModel data: - - ["FirebaseDBRef", "firebase/app", "Member[database].ReturnValue.Member[ref,refFromURL].ReturnValue"] - - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].Argument[1].Parameter[0]"] - - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].ReturnValue.Member[then].Argument[0].Parameter[0]"] + - ["FirebaseDBRef", "firebase/app", "Member[database].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,refFromURL].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[child,once,on].ReturnValue"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,root,parent,before,after]"] - - ["FirebaseDBRef", "FirebaseDBRef", "Member[child,once,on,orderByChild,equalTo,endAt,startAt,limitToLast,orderByKey].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[endAt,startAt,orderByChild,orderByKey,equalTo,limitToLast].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[onCreate,onUpdate,onWrite,onDelete,transaction,then,forEach].Argument[0].Parameter[0]"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].Argument[1].Parameter[0]"] - ["FirebaseDBRef", "firebase-functions", "Member[database].Member[ref,refFromURL].ReturnValue"] - - ["FirebaseDBRef", "FirebaseDBRef", "Member[onCreate,onUpdate,onWrite,onDelete,transaction].Argument[0].Parameter[0]"] - ["FirebaseDBRef", "firebase-admin", "Member[database].ReturnValue.Member[ref,refFromURL].ReturnValue"] - ["FirebaseApp", "firebase-admin", "Member[initializeApp,app].ReturnValue"] - ["FirebaseDBRef", "FirebaseApp", "Member[database].ReturnValue.Member[ref,refFromURL].ReturnValue"] @@ -19,4 +20,3 @@ extensions: extensible: sourceModel data: - ["FirebaseDBRef", "Member[val,exportVal].ReturnValue", 'remote'] - - ["FirebaseDBRef", "Member[forEach].Argument[0].Parameter[0].Member[val,exportVal].ReturnValue", 'remote'] From 34dfa3d9aa10deb45153afdf02361afc0b403308 Mon Sep 17 00:00:00 2001 From: Napalys Date: Wed, 16 Apr 2025 15:35:11 +0200 Subject: [PATCH 06/11] Enhance Firebase model with `push`, `order*` and `limitToFirst` members. --- javascript/ql/lib/ext/firebase.model.yml | 10 +++++----- .../Security/CWE-079/DomBasedXss/Xss.expected | 8 ++++++++ .../DomBasedXss/XssWithAdditionalSources.expected | 7 +++++++ .../Security/CWE-079/DomBasedXss/firebase-client.js | 13 +++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/javascript/ql/lib/ext/firebase.model.yml b/javascript/ql/lib/ext/firebase.model.yml index 2aaa4f919e7a..2588c8bf536f 100644 --- a/javascript/ql/lib/ext/firebase.model.yml +++ b/javascript/ql/lib/ext/firebase.model.yml @@ -5,15 +5,15 @@ extensions: data: - ["FirebaseDBRef", "firebase/app", "Member[database].ReturnValue"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,refFromURL].ReturnValue"] - - ["FirebaseDBRef", "FirebaseDBRef", "Member[child,once,on].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,equalTo,limitToLast].ReturnValue"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[endAt,startAt,orderByChild,orderByKey,orderByValue,orderByPriority,equalTo,limitToLast,limitToFirst].ReturnValue"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[onCreate,onUpdate,onWrite,onDelete,transaction,then,forEach].Argument[0].Parameter[0]"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].Argument[1].Parameter[0]"] - - ["FirebaseDBRef", "firebase-functions", "Member[database].Member[ref,refFromURL].ReturnValue"] - - ["FirebaseDBRef", "firebase-admin", "Member[database].ReturnValue.Member[ref,refFromURL].ReturnValue"] + - ["FirebaseDBRef", "firebase-functions", "Member[database]"] + - ["FirebaseDBRef", "firebase-admin", "Member[database].ReturnValue"] - ["FirebaseApp", "firebase-admin", "Member[initializeApp,app].ReturnValue"] - - ["FirebaseDBRef", "FirebaseApp", "Member[database].ReturnValue.Member[ref,refFromURL].ReturnValue"] + - ["FirebaseDBRef", "FirebaseApp", "Member[database].ReturnValue"] - addsTo: pack: codeql/javascript-all 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 ff3c18524a45..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 @@ -74,6 +74,7 @@ | firebase-client.js:38:56:38:67 | userData.bio | firebase-client.js:37:22:37:35 | snapshot.val() | firebase-client.js:38:56:38:67 | userData.bio | Cross-site scripting vulnerability due to $@. | firebase-client.js:37:22:37:35 | snapshot.val() | user-provided value | | firebase-client.js:44:55:44:74 | parentSnapshot.val() | firebase-client.js:44:55:44:74 | parentSnapshot.val() | firebase-client.js:44:55:44:74 | parentSnapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:44:55:44:74 | parentSnapshot.val() | user-provided value | | firebase-client.js:52:57:52:70 | snapshot.val() | firebase-client.js:52:57:52:70 | snapshot.val() | firebase-client.js:52:57:52:70 | snapshot.val() | Cross-site scripting vulnerability due to $@. | firebase-client.js:52:57:52:70 | snapshot.val() | user-provided value | +| firebase-client.js:66:34:66:57 | "

" + ... "

" | 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 | "
" | jquery.js:2:17:2:40 | documen ... .search | jquery.js:7:5:7:34 | "
" | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:40 | documen ... .search | user-provided value | | jquery.js:8:18:8:34 | "XSS: " + tainted | jquery.js:2:17:2:40 | documen ... .search | jquery.js:8:18:8:34 | "XSS: " + tainted | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:40 | documen ... .search | user-provided value | | jquery.js:10:5:10:40 | "" + ... "" | jquery.js:10:13:10:20 | location | jquery.js:10:5:10:40 | "" + ... "" | Cross-site scripting vulnerability due to $@. | jquery.js:10:13:10:20 | location | user-provided value | @@ -373,6 +374,9 @@ edges | firebase-client.js:37:11:37:35 | userData | firebase-client.js:38:56:38:63 | userData | provenance | | | firebase-client.js:37:22:37:35 | snapshot.val() | firebase-client.js:37:11:37:35 | userData | provenance | | | firebase-client.js:38:56:38:63 | userData | firebase-client.js:38:56:38:67 | userData.bio | provenance | | +| firebase-client.js:65:13:65:44 | message | firebase-client.js:66:42:66:48 | message | provenance | | +| firebase-client.js:65:23:65:36 | snapshot.val() | firebase-client.js:65:13:65:44 | message | provenance | | +| firebase-client.js:66:42:66:48 | message | firebase-client.js:66:34:66:57 | "

" + ... "

" | 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 | | @@ -996,6 +1000,10 @@ nodes | firebase-client.js:38:56:38:67 | userData.bio | semmle.label | userData.bio | | firebase-client.js:44:55:44:74 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | | firebase-client.js:52:57:52:70 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-client.js:65:13:65:44 | message | semmle.label | message | +| firebase-client.js:65:23:65:36 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-client.js:66:34:66:57 | "

" + ... "

" | 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 c5989c506e01..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 @@ -175,6 +175,10 @@ nodes | firebase-client.js:38:56:38:67 | userData.bio | semmle.label | userData.bio | | firebase-client.js:44:55:44:74 | parentSnapshot.val() | semmle.label | parentSnapshot.val() | | firebase-client.js:52:57:52:70 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-client.js:65:13:65:44 | message | semmle.label | message | +| firebase-client.js:65:23:65:36 | snapshot.val() | semmle.label | snapshot.val() | +| firebase-client.js:66:34:66:57 | "

" + ... "

" | 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 | @@ -850,6 +854,9 @@ edges | firebase-client.js:37:11:37:35 | userData | firebase-client.js:38:56:38:63 | userData | provenance | | | firebase-client.js:37:22:37:35 | snapshot.val() | firebase-client.js:37:11:37:35 | userData | provenance | | | firebase-client.js:38:56:38:63 | userData | firebase-client.js:38:56:38:67 | userData.bio | provenance | | +| firebase-client.js:65:13:65:44 | message | firebase-client.js:66:42:66:48 | message | provenance | | +| firebase-client.js:65:23:65:36 | snapshot.val() | firebase-client.js:65:13:65:44 | message | provenance | | +| firebase-client.js:66:42:66:48 | message | firebase-client.js:66:34:66:57 | "

" + ... "

" | 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 index 327893d2b127..f11b549f7799 100644 --- 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 @@ -53,3 +53,16 @@ function fun2(category){ return dbRef.remove(); }); } + +function fun3(){ + const messagesRef = firebase.database().ref("messages"); + const userInput = ""; + const newMessageRef = messagesRef.push({ + message: userInput + }); + + newMessageRef.once("value", (snapshot) => { + const message = snapshot.val().message; // $ Source + document.body.innerHTML += "

" + message + "

"; // $ Alert + }); +} From 9842d1d31bd2370547e34f9324979132e50ff7b7 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 22 Apr 2025 12:17:58 +0200 Subject: [PATCH 07/11] Added test cases for global usage of firebase and with `Promise.all` --- .../CWE-094/CodeInjection/CodeInjection.expected | 2 ++ .../CWE-094/CodeInjection/firebase-server.js | 16 ++++++++++++++++ .../CWE-094/CodeInjection/firebase-server2.js | 11 +++++++++++ 3 files changed, 29 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server2.js 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 bcf5b233d6e7..adb693c1fb27 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 @@ -40,6 +40,7 @@ | 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: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 | @@ -170,6 +171,7 @@ nodes | 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: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 index 675f83fc5f0e..26d628a3bf5d 100644 --- 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 @@ -54,3 +54,19 @@ async function fun3(uid, postId, size) { 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()); // $ MISSING: 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..fee66112bba0 --- /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()); // $ MISSING: Alert[js/code-injection] + var followUserRef = firebase.database().ref('followers/' + uid + '/' + this.currentUid); + + followUserRef.on('value', function(followSnapshot) { + eval(followSnapshot.val()); // $ MISSING: Alert[js/code-injection] + }); + }); +}; From 42604eb3eaf122be1036d71a72d610a6027645b9 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 22 Apr 2025 12:20:44 +0200 Subject: [PATCH 08/11] Added MaD for global firebase variable. --- javascript/ql/lib/ext/firebase.model.yml | 1 + .../Security/CWE-094/CodeInjection/CodeInjection.expected | 4 ++++ .../CodeInjection/HeuristicSourceCodeInjection.expected | 3 +++ .../Security/CWE-094/CodeInjection/firebase-server2.js | 4 ++-- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/ext/firebase.model.yml b/javascript/ql/lib/ext/firebase.model.yml index 2588c8bf536f..c1bd5a8bda52 100644 --- a/javascript/ql/lib/ext/firebase.model.yml +++ b/javascript/ql/lib/ext/firebase.model.yml @@ -4,6 +4,7 @@ extensions: extensible: typeModel data: - ["FirebaseDBRef", "firebase/app", "Member[database].ReturnValue"] + - ["firebase/app", "global", "Member[firebase]"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,refFromURL].ReturnValue"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[child,once,on,push,set,then].ReturnValue"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,root,parent,before,after]"] 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 adb693c1fb27..21c8885aae70 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 @@ -27,6 +27,8 @@ | express.js:20:34:20:38 | taint | express.js:19:17:19:35 | req.param("wobble") | express.js:20:34:20:38 | taint | This code execution depends on a $@. | express.js:19:17:19:35 | req.param("wobble") | user-provided value | | express.js:36:15:36:19 | taint | express.js:27:17:27:35 | req.param("wobble") | express.js:36:15:36:19 | taint | This code execution depends on a $@. | express.js:27:17:27:35 | req.param("wobble") | user-provided value | | express.js:43:10:43:12 | msg | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | This code execution depends on a $@. | express.js:42:30:42:32 | msg | 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 | @@ -158,6 +160,8 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| 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() | 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 95c20ee333e2..0dee48921e32 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 @@ -82,6 +82,8 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| 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() | @@ -95,6 +97,7 @@ nodes | 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: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-server2.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/firebase-server2.js index fee66112bba0..adfd8d83ec48 100644 --- 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 @@ -1,11 +1,11 @@ function globalFirebaseUsage() { var usersRef = firebase.database().ref('users'); usersRef.on('child_added', function(snapshot) { - eval(snapshot.val()); // $ MISSING: Alert[js/code-injection] + eval(snapshot.val()); // $ Alert[js/code-injection] var followUserRef = firebase.database().ref('followers/' + uid + '/' + this.currentUid); followUserRef.on('value', function(followSnapshot) { - eval(followSnapshot.val()); // $ MISSING: Alert[js/code-injection] + eval(followSnapshot.val()); // $ Alert[js/code-injection] }); }); }; From b9026c8bab99fd92ac42e785246c746354c372fa Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 22 Apr 2025 12:40:57 +0200 Subject: [PATCH 09/11] Refactored `firebase` test cases to use inline expectations --- .../frameworks/Firebase/src/import_assign.ts | 2 +- .../frameworks/Firebase/src/import_named.ts | 2 +- .../frameworks/Firebase/src/import_star.ts | 2 +- .../frameworks/Firebase/tests.expected | 5 +- .../frameworks/Firebase/tests.qlref | 2 + .../library-tests/frameworks/Firebase/tst.js | 48 +++++++++---------- 6 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 javascript/ql/test/library-tests/frameworks/Firebase/tests.qlref 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..c28e765ade70 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected @@ -1,3 +1,4 @@ +WARNING: type 'FirebaseVal' has been deprecated and may be removed in future (tests.ql:10,29-50) firebaseRef | src/import_assign.ts:4:3:4:17 | db.ref("hello") | | src/import_named.ts:4:3:4:17 | db.ref("hello") | @@ -20,9 +21,9 @@ firebaseRef | 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 | 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..cd68468a4b8c 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 +}); // $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 +}); // $firebaseSnapshot -functions.database.ref('x').onCreate(x => { - x.val(); - x.ref.parent; +functions.database.ref('x').onCreate(x => {// $firebaseSnapshot + 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 + x.before.val(); // $firebaseSnapshot $firebaseVal + x.after.val(); // $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 FirebaseWrapper(firebase.initializeApp()).getRef('/news'); // $firebaseRef +new FirebaseWrapper2().getRef('/news'); // $firebaseRef +new FirebaseWrapper2().getNewsItem('x'); // $firebaseSnapshot +new FirebaseWrapper2().adjustValue(x => x + 1); // $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 From 7b572a034481ce48cf226958565d0e0b18cc0a6b Mon Sep 17 00:00:00 2001 From: Napalys Date: Wed, 23 Apr 2025 09:11:38 +0200 Subject: [PATCH 10/11] Updated old test cases to work with `MaD` and adjusted `MaD` with more types. --- javascript/ql/lib/ext/firebase.model.yml | 27 ++++++++++++++----- .../frameworks/Firebase/tests.expected | 18 +++++++++---- .../frameworks/Firebase/tests.ql | 12 ++++++--- .../library-tests/frameworks/Firebase/tst.js | 20 +++++++------- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/javascript/ql/lib/ext/firebase.model.yml b/javascript/ql/lib/ext/firebase.model.yml index c1bd5a8bda52..5a0aa3c05a93 100644 --- a/javascript/ql/lib/ext/firebase.model.yml +++ b/javascript/ql/lib/ext/firebase.model.yml @@ -3,21 +3,34 @@ extensions: pack: codeql/javascript-all extensible: typeModel data: - - ["FirebaseDBRef", "firebase/app", "Member[database].ReturnValue"] - ["firebase/app", "global", "Member[firebase]"] - - ["FirebaseDBRef", "FirebaseDBRef", "Member[ref,refFromURL].ReturnValue"] + + - ["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,transaction,then,forEach].Argument[0].Parameter[0]"] + - ["FirebaseDBRef", "FirebaseDBRef", "Member[onCreate,onUpdate,onWrite,onDelete,then,forEach].Argument[0].Parameter[0]"] - ["FirebaseDBRef", "FirebaseDBRef", "Member[once,on].Argument[1].Parameter[0]"] - - ["FirebaseDBRef", "firebase-functions", "Member[database]"] - - ["FirebaseDBRef", "firebase-admin", "Member[database].ReturnValue"] - - ["FirebaseApp", "firebase-admin", "Member[initializeApp,app].ReturnValue"] - - ["FirebaseDBRef", "FirebaseApp", "Member[database].ReturnValue"] + + - ["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]"] - addsTo: pack: codeql/javascript-all extensible: sourceModel data: - ["FirebaseDBRef", "Member[val,exportVal].ReturnValue", 'remote'] + - ["FirebaseDBRef", "Member[transaction].Argument[0].Parameter[0]", 'remote'] + - ["Snapshot", "Member[val,exportVal].ReturnValue", 'remote'] diff --git a/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected b/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected index c28e765ade70..4b517168ae0d 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/Firebase/tests.expected @@ -1,25 +1,32 @@ -WARNING: type 'FirebaseVal' has been deprecated and may be removed in future (tests.ql:10,29-50) firebaseRef | src/import_assign.ts:4:3:4:17 | db.ref("hello") | | 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 ... eRef\\n}) | | tst.js:5:38:5:38 | x | @@ -29,8 +36,9 @@ firebaseSnapshot | 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/tst.js b/javascript/ql/test/library-tests/frameworks/Firebase/tst.js index cd68468a4b8c..fdcc185384ba 100644 --- a/javascript/ql/test/library-tests/frameworks/Firebase/tst.js +++ b/javascript/ql/test/library-tests/frameworks/Firebase/tst.js @@ -5,21 +5,21 @@ import * as functions from 'firebase-functions'; fb.database().ref('x').once('value', x => { // $firebaseSnapshot $firebaseRef x.val(); // $firebaseVal x.ref.parent; // $firebaseRef -}); // $firebaseSnapshot +}); // $firebaseRef $firebaseSnapshot admin.database().ref('x').once('value', x => { // $firebaseSnapshot $firebaseRef x.val(); // $firebaseVal x.ref.parent; // $firebaseRef -}); // $firebaseSnapshot +}); // $firebaseRef $firebaseSnapshot -functions.database.ref('x').onCreate(x => {// $firebaseSnapshot +functions.database.ref('x').onCreate(x => {// $firebaseSnapshot $firebaseRef x.val(); // $firebaseVal x.ref.parent; // $firebaseRef }); -functions.database.ref('x').onUpdate(x => { // $firebaseSnapshot - x.before.val(); // $firebaseSnapshot $firebaseVal - x.after.val(); // $firebaseSnapshot $firebaseVal +functions.database.ref('x').onUpdate(x => { // $firebaseSnapshot $firebaseRef + x.before.val(); // $firebaseRef $firebaseSnapshot $firebaseVal + x.after.val(); // $firebaseRef $firebaseSnapshot $firebaseVal x.ref.parent; // $firebaseRef }); @@ -55,10 +55,10 @@ class FirebaseWrapper2 { } } -new FirebaseWrapper(firebase.initializeApp()).getRef('/news'); // $firebaseRef -new FirebaseWrapper2().getRef('/news'); // $firebaseRef -new FirebaseWrapper2().getNewsItem('x'); // $firebaseSnapshot -new FirebaseWrapper2().adjustValue(x => x + 1); // $firebaseVal +new FirebaseWrapper(firebase.initializeApp()).getRef('/news'); +new FirebaseWrapper2().getRef('/news'); +new FirebaseWrapper2().getNewsItem('x'); +new FirebaseWrapper2().adjustValue(x => x + 1); // $firebaseSnapshot $firebaseVal class Box { constructor(x) { From bfc523c3b9fb344724c703676bd6c98ec18b74a9 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 23 Apr 2025 10:46:30 +0200 Subject: [PATCH 11/11] Addded `Snapshot` to `Snapshot` awaited step. --- javascript/ql/lib/ext/firebase.model.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/ext/firebase.model.yml b/javascript/ql/lib/ext/firebase.model.yml index 5a0aa3c05a93..523ae0701ca7 100644 --- a/javascript/ql/lib/ext/firebase.model.yml +++ b/javascript/ql/lib/ext/firebase.model.yml @@ -27,10 +27,11 @@ extensions: - ["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[val,exportVal].ReturnValue", 'remote'] - ["FirebaseDBRef", "Member[transaction].Argument[0].Parameter[0]", 'remote'] - ["Snapshot", "Member[val,exportVal].ReturnValue", 'remote']