From 67aac7abfab8af3731ba7c06a1eb16d7f6bb6206 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 16 Jun 2025 16:23:40 +0200 Subject: [PATCH 1/5] JS: add test cases for middleware property assignment tracking --- .../Security/CWE-918/RequestForgery.expected | 14 ++++++++++ .../Security/CWE-918/serverSide2.js | 27 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js diff --git a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected index dde72095df16..b562fcd8992e 100644 --- a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected +++ b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -6,6 +6,8 @@ | apollo.serverSide.ts:8:39:8:64 | get(fil ... => {}) | apollo.serverSide.ts:7:36:7:44 | { files } | apollo.serverSide.ts:8:43:8:50 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:8:43:8:50 | file.url | URL | apollo.serverSide.ts:7:36:7:44 | { files } | user-provided value | | apollo.serverSide.ts:18:37:18:62 | get(fil ... => {}) | apollo.serverSide.ts:17:34:17:42 | { files } | apollo.serverSide.ts:18:41:18:48 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:18:41:18:48 | file.url | URL | apollo.serverSide.ts:17:34:17:42 | { files } | user-provided value | | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | The $@ of this request depends on a $@. | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | endpoint | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | user-provided value | +| serverSide2.js:23:29:23:49 | axios.g ... etUrl2) | serverSide2.js:22:24:22:30 | req.url | serverSide2.js:23:39:23:48 | targetUrl2 | The $@ of this request depends on a $@. | serverSide2.js:23:39:23:48 | targetUrl2 | URL | serverSide2.js:22:24:22:30 | req.url | user-provided value | +| serverSide2.js:26:29:26:49 | axios.g ... etUrl3) | serverSide2.js:11:24:11:30 | req.url | serverSide2.js:26:39:26:48 | targetUrl3 | The $@ of this request depends on a $@. | serverSide2.js:26:39:26:48 | targetUrl3 | URL | serverSide2.js:11:24:11:30 | req.url | user-provided value | | serverSide.js:18:5:18:20 | request(tainted) | serverSide.js:14:29:14:35 | req.url | serverSide.js:18:13:18:19 | tainted | The $@ of this request depends on a $@. | serverSide.js:18:13:18:19 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value | | serverSide.js:20:5:20:24 | request.get(tainted) | serverSide.js:14:29:14:35 | req.url | serverSide.js:20:17:20:23 | tainted | The $@ of this request depends on a $@. | serverSide.js:20:17:20:23 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value | | serverSide.js:24:5:24:20 | request(options) | serverSide.js:14:29:14:35 | req.url | serverSide.js:23:19:23:25 | tainted | The $@ of this request depends on a $@. | serverSide.js:23:19:23:25 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value | @@ -61,6 +63,11 @@ edges | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:19:11:19:17 | { url } | provenance | | | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | provenance | | | axiosInterceptors.serverSide.js:20:23:20:25 | url | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | provenance | | +| serverSide2.js:11:24:11:30 | req.url | serverSide2.js:25:24:25:41 | req.SomeObject.url | provenance | | +| serverSide2.js:22:11:22:36 | targetUrl2 | serverSide2.js:23:39:23:48 | targetUrl2 | provenance | | +| serverSide2.js:22:24:22:30 | req.url | serverSide2.js:22:11:22:36 | targetUrl2 | provenance | | +| serverSide2.js:25:11:25:47 | targetUrl3 | serverSide2.js:26:39:26:48 | targetUrl3 | provenance | | +| serverSide2.js:25:24:25:41 | req.SomeObject.url | serverSide2.js:25:11:25:47 | targetUrl3 | provenance | | | serverSide.js:14:9:14:52 | tainted | serverSide.js:18:13:18:19 | tainted | provenance | | | serverSide.js:14:9:14:52 | tainted | serverSide.js:20:17:20:23 | tainted | provenance | | | serverSide.js:14:9:14:52 | tainted | serverSide.js:23:19:23:25 | tainted | provenance | | @@ -153,6 +160,13 @@ nodes | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | semmle.label | req.body | | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | semmle.label | userProvidedUrl | | axiosInterceptors.serverSide.js:20:23:20:25 | url | semmle.label | url | +| serverSide2.js:11:24:11:30 | req.url | semmle.label | req.url | +| serverSide2.js:22:11:22:36 | targetUrl2 | semmle.label | targetUrl2 | +| serverSide2.js:22:24:22:30 | req.url | semmle.label | req.url | +| serverSide2.js:23:39:23:48 | targetUrl2 | semmle.label | targetUrl2 | +| serverSide2.js:25:11:25:47 | targetUrl3 | semmle.label | targetUrl3 | +| serverSide2.js:25:24:25:41 | req.SomeObject.url | semmle.label | req.SomeObject.url | +| serverSide2.js:26:39:26:48 | targetUrl3 | semmle.label | targetUrl3 | | serverSide.js:14:9:14:52 | tainted | semmle.label | tainted | | serverSide.js:14:19:14:42 | url.par ... , true) | semmle.label | url.par ... , true) | | serverSide.js:14:29:14:35 | req.url | semmle.label | req.url | diff --git a/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js b/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js new file mode 100644 index 000000000000..9f813c5b2c1b --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js @@ -0,0 +1,27 @@ +const express = require('express'); +const axios = require('axios'); +const qs = require('qs'); + +const app = express(); +const PORT = 3000; + +app.use((req, res, next) => { + req.parsedQueryFromParsedUrl = qs.parse(req._parsedUrl.query); // $MISSING:Source[js/request-forgery] + req.parsedQuery.url = req.url || {}; // $MISSING:Source[js/request-forgery] + req.SomeObject.url = req.url; // $Source[js/request-forgery] + next(); +}); + +app.get('/proxy', async (req, res) => { + const targetUrl = req.parsedQuery.url; + const response = await axios.get(targetUrl); // $MISSING:Alert[js/request-forgery] + + const targetUrl1 = req.parsedQueryFromParsedUrl.url; + const response1 = await axios.get(targetUrl1); // $MISSING:Alert[js/request-forgery] + + const targetUrl2 = req.url || {}; // $Source[js/request-forgery] + const response2 = await axios.get(targetUrl2); // $Alert[js/request-forgery] + + const targetUrl3 = req.SomeObject.url || {}; + const response3 = await axios.get(targetUrl3); // $Alert[js/request-forgery] +}); From da21a064ac27c8a34b8426de367561c5ce29249b Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 16 Jun 2025 16:28:30 +0200 Subject: [PATCH 2/5] JS: add `_parsedUrl` as remote input source --- .../ql/lib/semmle/javascript/frameworks/Express.qll | 2 +- .../Security/CWE-918/RequestForgery.expected | 10 ++++++++++ .../test/query-tests/Security/CWE-918/serverSide2.js | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Express.qll b/javascript/ql/lib/semmle/javascript/frameworks/Express.qll index ffeee7df73ee..dac9fe0f118b 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Express.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Express.qll @@ -620,7 +620,7 @@ module Express { or // `req.path` kind = "url" and - this = ref.getAPropertyRead("path") + this = ref.getAPropertyRead(["path", "_parsedUrl"]) ) } diff --git a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected index b562fcd8992e..bd1638abe440 100644 --- a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected +++ b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -6,6 +6,7 @@ | apollo.serverSide.ts:8:39:8:64 | get(fil ... => {}) | apollo.serverSide.ts:7:36:7:44 | { files } | apollo.serverSide.ts:8:43:8:50 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:8:43:8:50 | file.url | URL | apollo.serverSide.ts:7:36:7:44 | { files } | user-provided value | | apollo.serverSide.ts:18:37:18:62 | get(fil ... => {}) | apollo.serverSide.ts:17:34:17:42 | { files } | apollo.serverSide.ts:18:41:18:48 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:18:41:18:48 | file.url | URL | apollo.serverSide.ts:17:34:17:42 | { files } | user-provided value | | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | The $@ of this request depends on a $@. | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | endpoint | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | user-provided value | +| serverSide2.js:20:29:20:49 | axios.g ... etUrl1) | serverSide2.js:9:43:9:56 | req._parsedUrl | serverSide2.js:20:39:20:48 | targetUrl1 | The $@ of this request depends on a $@. | serverSide2.js:20:39:20:48 | targetUrl1 | URL | serverSide2.js:9:43:9:56 | req._parsedUrl | user-provided value | | serverSide2.js:23:29:23:49 | axios.g ... etUrl2) | serverSide2.js:22:24:22:30 | req.url | serverSide2.js:23:39:23:48 | targetUrl2 | The $@ of this request depends on a $@. | serverSide2.js:23:39:23:48 | targetUrl2 | URL | serverSide2.js:22:24:22:30 | req.url | user-provided value | | serverSide2.js:26:29:26:49 | axios.g ... etUrl3) | serverSide2.js:11:24:11:30 | req.url | serverSide2.js:26:39:26:48 | targetUrl3 | The $@ of this request depends on a $@. | serverSide2.js:26:39:26:48 | targetUrl3 | URL | serverSide2.js:11:24:11:30 | req.url | user-provided value | | serverSide.js:18:5:18:20 | request(tainted) | serverSide.js:14:29:14:35 | req.url | serverSide.js:18:13:18:19 | tainted | The $@ of this request depends on a $@. | serverSide.js:18:13:18:19 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value | @@ -63,7 +64,11 @@ edges | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:19:11:19:17 | { url } | provenance | | | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | provenance | | | axiosInterceptors.serverSide.js:20:23:20:25 | url | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | provenance | | +| serverSide2.js:9:34:9:63 | qs.pars ... .query) | serverSide2.js:19:24:19:51 | req.par ... rsedUrl | provenance | | +| serverSide2.js:9:43:9:56 | req._parsedUrl | serverSide2.js:9:34:9:63 | qs.pars ... .query) | provenance | | | serverSide2.js:11:24:11:30 | req.url | serverSide2.js:25:24:25:41 | req.SomeObject.url | provenance | | +| serverSide2.js:19:11:19:55 | targetUrl1 | serverSide2.js:20:39:20:48 | targetUrl1 | provenance | | +| serverSide2.js:19:24:19:51 | req.par ... rsedUrl | serverSide2.js:19:11:19:55 | targetUrl1 | provenance | | | serverSide2.js:22:11:22:36 | targetUrl2 | serverSide2.js:23:39:23:48 | targetUrl2 | provenance | | | serverSide2.js:22:24:22:30 | req.url | serverSide2.js:22:11:22:36 | targetUrl2 | provenance | | | serverSide2.js:25:11:25:47 | targetUrl3 | serverSide2.js:26:39:26:48 | targetUrl3 | provenance | | @@ -160,7 +165,12 @@ nodes | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | semmle.label | req.body | | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | semmle.label | userProvidedUrl | | axiosInterceptors.serverSide.js:20:23:20:25 | url | semmle.label | url | +| serverSide2.js:9:34:9:63 | qs.pars ... .query) | semmle.label | qs.pars ... .query) | +| serverSide2.js:9:43:9:56 | req._parsedUrl | semmle.label | req._parsedUrl | | serverSide2.js:11:24:11:30 | req.url | semmle.label | req.url | +| serverSide2.js:19:11:19:55 | targetUrl1 | semmle.label | targetUrl1 | +| serverSide2.js:19:24:19:51 | req.par ... rsedUrl | semmle.label | req.par ... rsedUrl | +| serverSide2.js:20:39:20:48 | targetUrl1 | semmle.label | targetUrl1 | | serverSide2.js:22:11:22:36 | targetUrl2 | semmle.label | targetUrl2 | | serverSide2.js:22:24:22:30 | req.url | semmle.label | req.url | | serverSide2.js:23:39:23:48 | targetUrl2 | semmle.label | targetUrl2 | diff --git a/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js b/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js index 9f813c5b2c1b..8c96f895f49f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js +++ b/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js @@ -6,7 +6,7 @@ const app = express(); const PORT = 3000; app.use((req, res, next) => { - req.parsedQueryFromParsedUrl = qs.parse(req._parsedUrl.query); // $MISSING:Source[js/request-forgery] + req.parsedQueryFromParsedUrl = qs.parse(req._parsedUrl.query); // $Source[js/request-forgery] req.parsedQuery.url = req.url || {}; // $MISSING:Source[js/request-forgery] req.SomeObject.url = req.url; // $Source[js/request-forgery] next(); @@ -17,7 +17,7 @@ app.get('/proxy', async (req, res) => { const response = await axios.get(targetUrl); // $MISSING:Alert[js/request-forgery] const targetUrl1 = req.parsedQueryFromParsedUrl.url; - const response1 = await axios.get(targetUrl1); // $MISSING:Alert[js/request-forgery] + const response1 = await axios.get(targetUrl1); // $Alert[js/request-forgery] const targetUrl2 = req.url || {}; // $Source[js/request-forgery] const response2 = await axios.get(targetUrl2); // $Alert[js/request-forgery] From 060b98d36cecc0ad9eea1423452753bdc19e1ce3 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 16 Jun 2025 16:31:17 +0200 Subject: [PATCH 3/5] JS: enchance middleware taint tracking via local source --- javascript/ql/lib/semmle/javascript/Routing.qll | 2 +- .../query-tests/Security/CWE-918/RequestForgery.expected | 8 ++++++++ .../ql/test/query-tests/Security/CWE-918/serverSide2.js | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/Routing.qll b/javascript/ql/lib/semmle/javascript/Routing.qll index 530322a2d2c4..8fe9f79d8623 100644 --- a/javascript/ql/lib/semmle/javascript/Routing.qll +++ b/javascript/ql/lib/semmle/javascript/Routing.qll @@ -925,7 +925,7 @@ module Routing { private DataFlow::Node getAnAccessPathRhs(Node base, int n, string path) { // Assigned in the body of a route handler function, which is a middleware exists(RouteHandler handler | base = handler | - result = AccessPath::getAnAssignmentTo(handler.getParameter(n).ref(), path) and + result = AccessPath::getAnAssignmentTo(handler.getParameter(n).ref(), path).getALocalSource() and ( exists(handler.getAContinuationInvocation()) or diff --git a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected index bd1638abe440..8f340596bab2 100644 --- a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected +++ b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -6,6 +6,7 @@ | apollo.serverSide.ts:8:39:8:64 | get(fil ... => {}) | apollo.serverSide.ts:7:36:7:44 | { files } | apollo.serverSide.ts:8:43:8:50 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:8:43:8:50 | file.url | URL | apollo.serverSide.ts:7:36:7:44 | { files } | user-provided value | | apollo.serverSide.ts:18:37:18:62 | get(fil ... => {}) | apollo.serverSide.ts:17:34:17:42 | { files } | apollo.serverSide.ts:18:41:18:48 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:18:41:18:48 | file.url | URL | apollo.serverSide.ts:17:34:17:42 | { files } | user-provided value | | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | The $@ of this request depends on a $@. | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | endpoint | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | user-provided value | +| serverSide2.js:17:28:17:47 | axios.get(targetUrl) | serverSide2.js:10:25:10:31 | req.url | serverSide2.js:17:38:17:46 | targetUrl | The $@ of this request depends on a $@. | serverSide2.js:17:38:17:46 | targetUrl | URL | serverSide2.js:10:25:10:31 | req.url | user-provided value | | serverSide2.js:20:29:20:49 | axios.g ... etUrl1) | serverSide2.js:9:43:9:56 | req._parsedUrl | serverSide2.js:20:39:20:48 | targetUrl1 | The $@ of this request depends on a $@. | serverSide2.js:20:39:20:48 | targetUrl1 | URL | serverSide2.js:9:43:9:56 | req._parsedUrl | user-provided value | | serverSide2.js:23:29:23:49 | axios.g ... etUrl2) | serverSide2.js:22:24:22:30 | req.url | serverSide2.js:23:39:23:48 | targetUrl2 | The $@ of this request depends on a $@. | serverSide2.js:23:39:23:48 | targetUrl2 | URL | serverSide2.js:22:24:22:30 | req.url | user-provided value | | serverSide2.js:26:29:26:49 | axios.g ... etUrl3) | serverSide2.js:11:24:11:30 | req.url | serverSide2.js:26:39:26:48 | targetUrl3 | The $@ of this request depends on a $@. | serverSide2.js:26:39:26:48 | targetUrl3 | URL | serverSide2.js:11:24:11:30 | req.url | user-provided value | @@ -66,7 +67,10 @@ edges | axiosInterceptors.serverSide.js:20:23:20:25 | url | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | provenance | | | serverSide2.js:9:34:9:63 | qs.pars ... .query) | serverSide2.js:19:24:19:51 | req.par ... rsedUrl | provenance | | | serverSide2.js:9:43:9:56 | req._parsedUrl | serverSide2.js:9:34:9:63 | qs.pars ... .query) | provenance | | +| serverSide2.js:10:25:10:31 | req.url | serverSide2.js:16:23:16:41 | req.parsedQuery.url | provenance | | | serverSide2.js:11:24:11:30 | req.url | serverSide2.js:25:24:25:41 | req.SomeObject.url | provenance | | +| serverSide2.js:16:11:16:41 | targetUrl | serverSide2.js:17:38:17:46 | targetUrl | provenance | | +| serverSide2.js:16:23:16:41 | req.parsedQuery.url | serverSide2.js:16:11:16:41 | targetUrl | provenance | | | serverSide2.js:19:11:19:55 | targetUrl1 | serverSide2.js:20:39:20:48 | targetUrl1 | provenance | | | serverSide2.js:19:24:19:51 | req.par ... rsedUrl | serverSide2.js:19:11:19:55 | targetUrl1 | provenance | | | serverSide2.js:22:11:22:36 | targetUrl2 | serverSide2.js:23:39:23:48 | targetUrl2 | provenance | | @@ -167,7 +171,11 @@ nodes | axiosInterceptors.serverSide.js:20:23:20:25 | url | semmle.label | url | | serverSide2.js:9:34:9:63 | qs.pars ... .query) | semmle.label | qs.pars ... .query) | | serverSide2.js:9:43:9:56 | req._parsedUrl | semmle.label | req._parsedUrl | +| serverSide2.js:10:25:10:31 | req.url | semmle.label | req.url | | serverSide2.js:11:24:11:30 | req.url | semmle.label | req.url | +| serverSide2.js:16:11:16:41 | targetUrl | semmle.label | targetUrl | +| serverSide2.js:16:23:16:41 | req.parsedQuery.url | semmle.label | req.parsedQuery.url | +| serverSide2.js:17:38:17:46 | targetUrl | semmle.label | targetUrl | | serverSide2.js:19:11:19:55 | targetUrl1 | semmle.label | targetUrl1 | | serverSide2.js:19:24:19:51 | req.par ... rsedUrl | semmle.label | req.par ... rsedUrl | | serverSide2.js:20:39:20:48 | targetUrl1 | semmle.label | targetUrl1 | diff --git a/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js b/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js index 8c96f895f49f..7743d0eec38d 100644 --- a/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js +++ b/javascript/ql/test/query-tests/Security/CWE-918/serverSide2.js @@ -7,14 +7,14 @@ const PORT = 3000; app.use((req, res, next) => { req.parsedQueryFromParsedUrl = qs.parse(req._parsedUrl.query); // $Source[js/request-forgery] - req.parsedQuery.url = req.url || {}; // $MISSING:Source[js/request-forgery] + req.parsedQuery.url = req.url || {}; // $Source[js/request-forgery] req.SomeObject.url = req.url; // $Source[js/request-forgery] next(); }); app.get('/proxy', async (req, res) => { const targetUrl = req.parsedQuery.url; - const response = await axios.get(targetUrl); // $MISSING:Alert[js/request-forgery] + const response = await axios.get(targetUrl); // $Alert[js/request-forgery] const targetUrl1 = req.parsedQueryFromParsedUrl.url; const response1 = await axios.get(targetUrl1); // $Alert[js/request-forgery] From 72528749f2e291e360e678bdcf41e9f0812783ba Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 16 Jun 2025 16:35:39 +0200 Subject: [PATCH 4/5] JS: add change note --- .../ql/lib/change-notes/2025-06-16-middleware-express.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2025-06-16-middleware-express.md diff --git a/javascript/ql/lib/change-notes/2025-06-16-middleware-express.md b/javascript/ql/lib/change-notes/2025-06-16-middleware-express.md new file mode 100644 index 000000000000..600aad8bafcf --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-06-16-middleware-express.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* Improved data flow tracking through middleware to handle default value and similar patterns. +* Added `req._parsedUrl` as a remote input source. From c1b2fd86b2b6ff9164dffda47e328b0f81bb05c3 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Fri, 20 Jun 2025 14:29:51 +0200 Subject: [PATCH 5/5] Update javascript/ql/lib/semmle/javascript/frameworks/Express.qll Co-authored-by: Taus --- javascript/ql/lib/semmle/javascript/frameworks/Express.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Express.qll b/javascript/ql/lib/semmle/javascript/frameworks/Express.qll index dac9fe0f118b..bcfcffb82a0f 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Express.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Express.qll @@ -618,7 +618,7 @@ module Express { kind = "body" and this = ref.getAPropertyRead("body") or - // `req.path` + // `req.path` and `req._parsedUrl` kind = "url" and this = ref.getAPropertyRead(["path", "_parsedUrl"]) )