From 5a52340f7b98ce6f1c5a30cf627ce5ba849fef70 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Mon, 12 Feb 2024 22:48:40 -0800 Subject: [PATCH 01/86] Add draft of multi-service, message-passing log poc --- .../test/queries/loginjection/sample-app/README.md | 5 +++++ .../queries/loginjection/sample-app/db/schema.cds | 11 +++++++++++ .../queries/loginjection/sample-app/package.json | 14 ++++++++++++++ .../test/queries/loginjection/sample-app/server.js | 5 +++++ .../loginjection/sample-app/srv/service1.cds | 9 +++++++++ .../loginjection/sample-app/srv/service1.js | 8 ++++++++ .../loginjection/sample-app/srv/service2.cds | 1 + .../loginjection/sample-app/srv/service2.js | 10 ++++++++++ 8 files changed, 63 insertions(+) create mode 100644 javascript/frameworks/cap/test/queries/loginjection/sample-app/README.md create mode 100644 javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/README.md b/javascript/frameworks/cap/test/queries/loginjection/sample-app/README.md new file mode 100644 index 000000000..0ae0ca457 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/README.md @@ -0,0 +1,5 @@ +# Log Injection PoC + +This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. + +The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds b/javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds new file mode 100644 index 000000000..58682e397 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds @@ -0,0 +1,11 @@ +namespace advanced-security.log-injection.sample-entities; + +entity Entity1 { + Attribute1 : String(100) + Attribute2 : String(100) +} + +entity Entity2 { + Attribute3 : String(100) + Attribute4 : String(100) +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json b/javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json new file mode 100644 index 000000000..1d3f853ef --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json @@ -0,0 +1,14 @@ +{ + "name": "@advanced-security/log-injection", + "version": "1.0.0", + "devDependencies": { + }, + "dependencies": { + "@sap/cds": "^7", + "express": "^4.17.1" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js new file mode 100644 index 000000000..06424930e --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js @@ -0,0 +1,5 @@ +const cds = require('@sap/cds'); + +cds.once('bootstrap', (app) => { + app.serve("/log-injection").from("@advanced-security/log-injection"); +}); diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds new file mode 100644 index 000000000..cef84e199 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds @@ -0,0 +1,9 @@ +using { advanced-security.log-injection.sample-entities as db-schema } from '../db/schema'; + +service Service1 { + entity Service1Entity as projection on db-schema.Entity1 excluding { Attribute2 } + + event Received: { + messageToPass : String; + } +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js new file mode 100644 index 000000000..94b7ab1ab --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js @@ -0,0 +1,8 @@ +const cds = require("@sap/cds"); + +module.exports = class Service1 extends cds.ApplicationService { + this.on("READ", "Entity1/Attribute1", (req) => { + const { messageToPass } = req.data; + await this.emit("Received", { messageToPass }); + }); +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds new file mode 100644 index 000000000..7dc8c9211 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds @@ -0,0 +1 @@ +service Service2 {} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js new file mode 100644 index 000000000..0fc86d706 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js @@ -0,0 +1,10 @@ +const cds = require("@sap/cds"); +const LOG = cds.log("logger"); + +module.exports = cds.service.impl(() => { + const Service1 = cds.connect.to("Service1"); + Service1.on("Received", async (msg) => { + const { messageToPass } = msg.data; + LOG.info("Recevied: ", messageToPass); + }); +}) From bb8fe7358468eb0640005b871f768cf1ff6849f1 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 14:27:43 -0800 Subject: [PATCH 02/86] Finalize draft on log injection app --- .../cap/test/queries/loginjection/sample-app/server.js | 8 +++++++- .../queries/loginjection/sample-app/srv/service1.cds | 8 +++++--- .../test/queries/loginjection/sample-app/srv/service1.js | 3 ++- .../queries/loginjection/sample-app/srv/service2.cds | 7 ++++++- .../test/queries/loginjection/sample-app/srv/service2.js | 9 +++++---- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js index 06424930e..d0ba1ce87 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js @@ -1,5 +1,11 @@ const cds = require('@sap/cds'); +const Service1 = cds.connect.to("Service1"); cds.once('bootstrap', (app) => { - app.serve("/log-injection").from("@advanced-security/log-injection"); + app.serve("/log-injection").from("@advanced-security/log-injection"); +}); + +Service1.on("Received1", async (msg) => { + const { messageToPass } = msg.data; + Service2.send("Received2", { messageToPass }); }); diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds index cef84e199..41d6ce4c9 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds @@ -1,9 +1,11 @@ using { advanced-security.log-injection.sample-entities as db-schema } from '../db/schema'; service Service1 { - entity Service1Entity as projection on db-schema.Entity1 excluding { Attribute2 } + /* Entity to send READ about. */ + entity Service1Entity as projection on db-schema.Entity1 excluding { Attribute2 } - event Received: { + /* Async API for Service1 to speak through. */ + event Received1: { messageToPass : String; } -} \ No newline at end of file +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js index 94b7ab1ab..35ddd6532 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js @@ -1,8 +1,9 @@ const cds = require("@sap/cds"); +/* Emit a "Received1" event upon receiving a READ request on its entity. */ module.exports = class Service1 extends cds.ApplicationService { this.on("READ", "Entity1/Attribute1", (req) => { const { messageToPass } = req.data; - await this.emit("Received", { messageToPass }); + await this.emit("Received1", { messageToPass }); }); } diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds index 7dc8c9211..3cfae1f0f 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds @@ -1 +1,6 @@ -service Service2 {} \ No newline at end of file +service Service2 { + /* Async API to talk to Service2. */ + event Received2: { + messageToPass: String; + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js index 0fc86d706..bdc33f1d0 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js @@ -2,9 +2,10 @@ const cds = require("@sap/cds"); const LOG = cds.log("logger"); module.exports = cds.service.impl(() => { - const Service1 = cds.connect.to("Service1"); - Service1.on("Received", async (msg) => { + /* Log upon receiving an "Received2" event. */ + this.on("Received2", async (msg) => { const { messageToPass } = msg.data; - LOG.info("Recevied: ", messageToPass); - }); + /* A log injection sink. */ + LOG.info("Received: ", messageToPass); + }); }) From 1e69a239d701f31234fb510b1b70b4819f87ad4c Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 14:31:50 -0800 Subject: [PATCH 03/86] Remove unnecessary devDependencies in package.json --- .../cap/test/queries/loginjection/sample-app/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json b/javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json index 1d3f853ef..feaa60a89 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json @@ -1,8 +1,6 @@ { "name": "@advanced-security/log-injection", "version": "1.0.0", - "devDependencies": { - }, "dependencies": { "@sap/cds": "^7", "express": "^4.17.1" From c71ef16023721eafa6ad7ff0d569071494e9c8fb Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 14:44:46 -0800 Subject: [PATCH 04/86] Wrong entity name --- .../cap/test/queries/loginjection/sample-app/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js index d0ba1ce87..e386a6cde 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js @@ -7,5 +7,5 @@ cds.once('bootstrap', (app) => { Service1.on("Received1", async (msg) => { const { messageToPass } = msg.data; - Service2.send("Received2", { messageToPass }); + await Service2.send("Received2", { messageToPass }); }); From 810942e5cf36d715654b6337e290495a4b11629d Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 14:50:29 -0800 Subject: [PATCH 05/86] Add draft of SQL injection PoC --- .../test/queries/sqlinjection/sample-app/README.md | 3 +++ .../queries/sqlinjection/sample-app/package.json | 13 +++++++++++++ .../test/queries/sqlinjection/sample-app/server.js | 12 ++++++++++++ .../sqlinjection/sample-app/srv/Service1.cds | 11 +++++++++++ .../queries/sqlinjection/sample-app/srv/Service1.js | 9 +++++++++ .../sqlinjection/sample-app/srv/Service2.cds | 11 +++++++++++ .../queries/sqlinjection/sample-app/srv/Service2.js | 11 +++++++++++ 7 files changed, 70 insertions(+) create mode 100644 javascript/frameworks/cap/test/queries/sqlinjection/sample-app/README.md create mode 100644 javascript/frameworks/cap/test/queries/sqlinjection/sample-app/package.json create mode 100644 javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js create mode 100644 javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.cds create mode 100644 javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js create mode 100644 javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.cds create mode 100644 javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.js diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/README.md b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/README.md new file mode 100644 index 000000000..567f3b7df --- /dev/null +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/README.md @@ -0,0 +1,3 @@ +# SQL Injection PoC + +This application demonstrates the possibility of an SQL injection through communications between several `ApplicationService`s and a `DatabaseService`, eventually sinking to a SQLite database. diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/package.json b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/package.json new file mode 100644 index 000000000..0bf35981c --- /dev/null +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/package.json @@ -0,0 +1,13 @@ +{ + "name": "@advanced-security/sql-injection", + "version": "1.0.0", + "dependencies": { + "@cap-js/sqlite": "*", + "@sap/cds": "^7", + "express": "^4.17.1" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + } +} diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js new file mode 100644 index 000000000..efaf14bcc --- /dev/null +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js @@ -0,0 +1,12 @@ +const cds = require("@sap/cds"); +const Service1 = cds.connect.to("Service1"); + +cds.once("bootstrap", (app) => { + app.serve("/sql-injection").from("@advanced-security/log-injection"); +}); + +/* */ +Service1.on("Received1", async (msg) => { + const { messageToPass } = msg.data; + await Service2.run(SELECT.from `Service2Entity`.where({Attribute3: messageToPass})); +}) diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.cds b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.cds new file mode 100644 index 000000000..41d6ce4c9 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.cds @@ -0,0 +1,11 @@ +using { advanced-security.log-injection.sample-entities as db-schema } from '../db/schema'; + +service Service1 { + /* Entity to send READ about. */ + entity Service1Entity as projection on db-schema.Entity1 excluding { Attribute2 } + + /* Async API for Service1 to speak through. */ + event Received1: { + messageToPass : String; + } +} diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js new file mode 100644 index 000000000..45e0a4a22 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js @@ -0,0 +1,9 @@ +const cds = require("@sap/cds"); + +/* Emit a "Received1" event upon receiving a READ request on its entity. */ +module.exports = class Service1 extends cds.ApplicationService { + this.on("READ", "Service1Entity/Attribute1", (req) => { + const { messageToPass } = req.data; + await this.emit("Received1", { messageToPass }); + }); +} diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.cds b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.cds new file mode 100644 index 000000000..7d9d1ce5d --- /dev/null +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.cds @@ -0,0 +1,11 @@ +using { advanced-security.log-injection.sample-entities as db-schema } from '../db/schema'; + +service Service2 { + /* Entity to SELECT from. */ + entity Service2Entity as projection on db-schema.Entity2 excluding { Attribute4 } + + /* Async API to talk to Service2. */ + event Received2: { + messageToPass : String; + } +} diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.js new file mode 100644 index 000000000..bdc33f1d0 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.js @@ -0,0 +1,11 @@ +const cds = require("@sap/cds"); +const LOG = cds.log("logger"); + +module.exports = cds.service.impl(() => { + /* Log upon receiving an "Received2" event. */ + this.on("Received2", async (msg) => { + const { messageToPass } = msg.data; + /* A log injection sink. */ + LOG.info("Received: ", messageToPass); + }); +}) From 140cee065b608282add65a33aaa3938c5b21af26 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 14:58:57 -0800 Subject: [PATCH 06/86] Fix CQL expression to be vulnerable to SQL injection --- .../cap/test/queries/sqlinjection/sample-app/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js index efaf14bcc..d966ef271 100644 --- a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js @@ -8,5 +8,5 @@ cds.once("bootstrap", (app) => { /* */ Service1.on("Received1", async (msg) => { const { messageToPass } = msg.data; - await Service2.run(SELECT.from `Service2Entity`.where({Attribute3: messageToPass})); + await Service2.run(SELECT.from `Service2Entity`.where(`Attribute3=${messageToPass}`)); }) From f354b9d15df06b9f8493b97b813e3e8feb670cb3 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 17:02:15 -0800 Subject: [PATCH 07/86] Add missing `init()` and add some comments --- .../queries/loginjection/sample-app/srv/service1.js | 10 ++++++---- .../test/queries/sqlinjection/sample-app/server.js | 2 +- .../queries/sqlinjection/sample-app/srv/Service1.js | 11 +++++++---- .../queries/sqlinjection/sample-app/srv/Service2.js | 9 +-------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js index 35ddd6532..dc94467f2 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js @@ -2,8 +2,10 @@ const cds = require("@sap/cds"); /* Emit a "Received1" event upon receiving a READ request on its entity. */ module.exports = class Service1 extends cds.ApplicationService { - this.on("READ", "Entity1/Attribute1", (req) => { - const { messageToPass } = req.data; - await this.emit("Received1", { messageToPass }); - }); + init() { + this.on("READ", "Entity1/Attribute1", (req) => { + const { messageToPass } = req.data; + await this.emit("Received1", { messageToPass }); + }); + } } diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js index d966ef271..417239c21 100644 --- a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js @@ -5,7 +5,7 @@ cds.once("bootstrap", (app) => { app.serve("/sql-injection").from("@advanced-security/log-injection"); }); -/* */ +/* Upon receiving "Received1" event emitted by Service1, make Service2 run a CQL query. */ Service1.on("Received1", async (msg) => { const { messageToPass } = msg.data; await Service2.run(SELECT.from `Service2Entity`.where(`Attribute3=${messageToPass}`)); diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js index 45e0a4a22..4657c5198 100644 --- a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js @@ -2,8 +2,11 @@ const cds = require("@sap/cds"); /* Emit a "Received1" event upon receiving a READ request on its entity. */ module.exports = class Service1 extends cds.ApplicationService { - this.on("READ", "Service1Entity/Attribute1", (req) => { - const { messageToPass } = req.data; - await this.emit("Received1", { messageToPass }); - }); + init() { + this.on("READ", "Service1Entity/Attribute1", (req) => { + const { messageToPass } = req.data; + await this.emit("Received1", { messageToPass }); + }); + } + } diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.js index bdc33f1d0..577834b44 100644 --- a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.js +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service2.js @@ -1,11 +1,4 @@ const cds = require("@sap/cds"); const LOG = cds.log("logger"); -module.exports = cds.service.impl(() => { - /* Log upon receiving an "Received2" event. */ - this.on("Received2", async (msg) => { - const { messageToPass } = msg.data; - /* A log injection sink. */ - LOG.info("Received: ", messageToPass); - }); -}) +module.exports = cds.service.impl(() => { }); From 3f50833e274b0726100fa6b27b956add735b8cfa Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 17:30:02 -0800 Subject: [PATCH 08/86] Normalize import path and remove redundant module declaration - Normalize import path to `advanced_security.javascript.frameworks.cap`. - Remove wrapping CDS / CQL inner module declaration. --- .../javascript/frameworks/cap/CDS.qll | 275 +++++------ .../javascript/frameworks/cap/CQL.qll | 434 +++++++++--------- .../cap/src/loginjection/LogInjection.ql | 4 +- .../cap/src/sqlinjection/SqlInjection.ql | 8 +- 4 files changed, 365 insertions(+), 356 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 14e0bdccf..e2291a30e 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -1,155 +1,166 @@ import javascript import DataFlow -module CDS { - // TODO: should this base type be more specific? - abstract class ServiceInstance extends DataFlow::Node { } - - /** - * Call to`cds.serve` - */ - class CdsServeCall extends ServiceInstance { - CdsServeCall() { this = any(CdsFacade cds).getMember("serve").getACall() } - } +// TODO: should this base type be more specific? +abstract class ServiceInstance extends DataFlow::Node { } + +/** + * Call to`cds.serve` + */ +class CdsServeCall extends ServiceInstance { + CdsServeCall() { this = any(CdsFacade cds).getMember("serve").getACall() } +} - /** - * call to: - * `new cds.ApplicationService` or `new cds.Service` - */ - class ServiceConstructor extends ServiceInstance { - ServiceConstructor() { this = any(ApplicationService cds).getAnInstantiation() } - } +/** + * call to: + * `new cds.ApplicationService` or `new cds.Service` + */ +class ServiceConstructor extends ServiceInstance { + ServiceConstructor() { this = any(ApplicationService cds).getAnInstantiation() } +} - /** - * return value of `cds.connect.to` - */ - class ConnectTo extends ServiceInstance { - ConnectTo() { this = any(CdsFacade cds).getMember("connect").getMember("to").getACall() } - } +/** + * return value of `cds.connect.to` + */ +class ConnectTo extends ServiceInstance { + ConnectTo() { this = any(CdsFacade cds).getMember("connect").getMember("to").getACall() } +} - /** Last argument to the service methods `srv.before`, `srv.on`, and `srv.after` */ - private class RequestHandler extends FunctionNode { } - - private class ErrorHandler extends RequestHandler { } - - /** - * Subclassing ApplicationService via `extends`: - * ```js - * class SomeService extends cds.ApplicationService - * ``` - */ - class UserDefinedApplicationService extends ClassNode { - UserDefinedApplicationService() { - exists(ApplicationService cdsApplicationService | - this.getASuperClassNode() = cdsApplicationService.asSource() - ) - } +/** Last argument to the service methods `srv.before`, `srv.on`, and `srv.after` */ +private class RequestHandler extends FunctionNode { } + +private class ErrorHandler extends RequestHandler { } + +/** + * Subclassing ApplicationService via `extends`: + * ```js + * class SomeService extends cds.ApplicationService + * ``` + */ +class UserDefinedApplicationService extends ClassNode { + UserDefinedApplicationService() { + exists(ApplicationService cdsApplicationService | + this.getASuperClassNode() = cdsApplicationService.asSource() + ) } +} - /** - * Subclassing ApplicationService via `cds.service.impl`: - * ```js - * const cds = require('@sap/cds') - * module.exports = cds.service.impl (function() { ... }) - * ``` - */ - class OldStyleUserDefinedApplicationService extends MethodCallNode { - OldStyleUserDefinedApplicationService() { - exists(CdsFacade cds | this = cds.getMember("service").getMember("impl").getACall()) - } +/** + * Subclassing ApplicationService via `cds.service.impl`: + * ```js + * const cds = require('@sap/cds') + * module.exports = cds.service.impl (function() { ... }) + * ``` + */ +class OldStyleUserDefinedApplicationService extends MethodCallNode { + OldStyleUserDefinedApplicationService() { + exists(CdsFacade cds | this = cds.getMember("service").getMember("impl").getACall()) } +} - /** - * Parameter of a `srv.with` method call: - * ```js - * cds.serve('./srv/cat-service') .with ((srv) => { - * srv.on ('READ','Books', (req) => req.reply([...])) - * }) - * ``` - * - * TODO expand this to capture request handlers registered inside the function - */ - class WithCallParameter extends RequestHandler { - WithCallParameter() { - exists(MethodCallNode withCall, ServiceInstance svc | - withCall.getArgument(0) = this and - withCall.getMethodName() = "with" and - withCall.getReceiver() = svc - ) - } +/** + * Parameter of a `srv.with` method call: + * ```js + * cds.serve('./srv/cat-service') .with ((srv) => { + * srv.on ('READ','Books', (req) => req.reply([...])) + * }) + * ``` + * + * TODO expand this to capture request handlers registered inside the function + */ +class WithCallParameter extends RequestHandler { + WithCallParameter() { + exists(MethodCallNode withCall, ServiceInstance svc | + withCall.getArgument(0) = this and + withCall.getMethodName() = "with" and + withCall.getReceiver() = svc + ) } +} - /** - * Parameter of request handler of `_.on`: - * ```js - * _.on ('READ','Books', (req) => req.reply([...])) - * ``` - */ - class OnNodeParam extends ValueNode, ParameterNode { - MethodCallNode on; - - OnNodeParam() { - exists(FunctionNode handler | - on.getMethodName() = "on" and - on.getLastArgument() = handler and - handler.getLastParameter() = this - ) - } - - MethodCallNode getOnNode() { result = on } +/** + * Parameter of request handler of `_.on`: + * ```js + * _.on ('READ','Books', (req) => req.reply([...])) + * ``` + */ +class OnNodeParam extends ValueNode, ParameterNode { + MethodCallNode on; + + OnNodeParam() { + exists(FunctionNode handler | + on.getMethodName() = "on" and + on.getLastArgument() = handler and + handler.getLastParameter() = this + ) } - /** - * Parameter of request handler of `srv.on`: - * ```js - * this.on ('READ','Books', (req) => req.reply([...])) - * ``` - * not sure how else to know which service is registering the handler - */ - class RequestSource extends OnNodeParam { - RequestSource() { - // TODO : consider - do we need to actually ever know which service the handler is associated to? - exists(UserDefinedApplicationService svc, FunctionNode init | - svc.getAnInstanceMember() = init and - init.getName() = "init" and - this.getOnNode().getEnclosingFunction() = init.getAstNode() - ) - or - exists(WithCallParameter pa | this.getOnNode().getEnclosingFunction() = pa.getFunction()) - } - } + MethodCallNode getOnNode() { result = on } +} - class ApplicationService extends API::Node { - ApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } +/** + * Parameter of request handler of `srv.on`: + * ```js + * this.on ('READ','Books', (req) => req.reply([...])) + * ``` + * not sure how else to know which service is registering the handler + */ +class RequestSource extends OnNodeParam { + RequestSource() { + // TODO : consider - do we need to actually ever know which service the handler is associated to? + exists(UserDefinedApplicationService svc, FunctionNode init | + svc.getAnInstanceMember() = init and + init.getName() = "init" and + this.getOnNode().getEnclosingFunction() = init.getAstNode() + ) + or + exists(WithCallParameter pa | this.getOnNode().getEnclosingFunction() = pa.getFunction()) } +} - /** - * ```js - * const cds = require('@sap/cds') - * ``` - */ - class CdsFacade extends API::Node { - CdsFacade() { this = API::moduleImport("@sap/cds") } - } +class ApplicationService extends API::Node { + ApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } +} - /** - * Call to`cds.log` - */ - class CdsLogCall extends API::Node { - CdsLogCall() { this = any(CdsFacade cds).getMember("log") } - } +/** + * ```js + * const cds = require('@sap/cds') + * ``` + */ +class CdsFacade extends API::Node { + CdsFacade() { this = API::moduleImport("@sap/cds") } +} + +/** + * Call to`cds.log` + */ +class CdsLogCall extends API::Node { + CdsLogCall() { this = any(CdsFacade cds).getMember("log") } +} - /** - * Arguments of calls to `cds.log.{trace, debug, info, log, warn, error}` - */ - class CdsLogSink extends DataFlow::Node { - CdsLogSink() { this = any(CdsLogCall cdsLog).getACall().getAChainedMethodCall(["trace", "debug", "info", "log", "warn", "error"]).getAnArgument() } +/** + * Arguments of calls to `cds.log.{trace, debug, info, log, warn, error}` + */ +class CdsLogSink extends DataFlow::Node { + CdsLogSink() { + this = + any(CdsLogCall cdsLog) + .getACall() + .getAChainedMethodCall(["trace", "debug", "info", "log", "warn", "error"]) + .getAnArgument() } +} - /** - * Methods that parse source strings into a CQL expression - */ - class ParseSink extends DataFlow::Node { - ParseSink() { this = any(CdsFacade cds).getMember("parse").getMember(["expr", "ref", "xpr"]).getACall().getAnArgument() } +/** + * Methods that parse source strings into a CQL expression + */ +class ParseSink extends DataFlow::Node { + ParseSink() { + this = + any(CdsFacade cds) + .getMember("parse") + .getMember(["expr", "ref", "xpr"]) + .getACall() + .getAnArgument() } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CQL.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CQL.qll index 1dde91599..0724f7fa7 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CQL.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CQL.qll @@ -1,261 +1,259 @@ import javascript import DataFlow -import CDS::CDS - -module CQL { - /** - * Objects from the SQL-like fluent API - * this is the set of clauses that acts as the base of a statement - */ - class CqlQueryBase extends VarRef { - CqlQueryBase() { - exists(string name | - this.getName() = name and - name in ["SELECT", "INSERT", "DELETE", "UPDATE", "UPSERT"] and - // Made available as a global variable - exists(GlobalVariable queryBase | this = queryBase.getAReference()) - or - // Imported from `cds.ql` */ - exists(CdsFacade cds | - cds.getMember("ql").getMember(name).getAValueReachableFromSource().asExpr() = this - ) +import advanced_security.javascript.frameworks.cap.CDS + +/** + * Objects from the SQL-like fluent API + * this is the set of clauses that acts as the base of a statement + */ +class CqlQueryBase extends VarRef { + CqlQueryBase() { + exists(string name | + this.getName() = name and + name in ["SELECT", "INSERT", "DELETE", "UPDATE", "UPSERT"] and + // Made available as a global variable + exists(GlobalVariable queryBase | this = queryBase.getAReference()) + or + // Imported from `cds.ql` */ + exists(CdsFacade cds | + cds.getMember("ql").getMember(name).getAValueReachableFromSource().asExpr() = this ) - } + ) } +} - class CqlSelectBase extends CqlQueryBase { - CqlSelectBase() { this.getName() = "SELECT" } - } +class CqlSelectBase extends CqlQueryBase { + CqlSelectBase() { this.getName() = "SELECT" } +} - class CqlInsertBase extends CqlQueryBase { - CqlInsertBase() { this.getName() = "INSERT" } - } +class CqlInsertBase extends CqlQueryBase { + CqlInsertBase() { this.getName() = "INSERT" } +} + +class CqlDeleteBase extends CqlQueryBase { + CqlDeleteBase() { this.getName() = "DELETE" } +} + +class CqlUpdateBase extends CqlQueryBase { + CqlUpdateBase() { this.getName() = "UPDATE" } +} + +class CqlUpsertBase extends CqlQueryBase { + CqlUpsertBase() { this.getName() = "UPSERT" } +} + +/** + * The cds-ql docs do not mention DELETE being a function acting as a shortcut to any underlying clause + */ +abstract class CqlQueryBaseCall extends CallExpr { + // TODO: Express "It's a global function or a local function imported from cds.ql" +} - class CqlDeleteBase extends CqlQueryBase { - CqlDeleteBase() { this.getName() = "DELETE" } +class CqlSelectBaseCall extends CqlQueryBaseCall { + CqlSelectBaseCall() { this.getCalleeName() = "SELECT" } +} + +class CqlInsertBaseCall extends CqlQueryBaseCall { + CqlInsertBaseCall() { this.getCalleeName() = "INSERT" } +} + +class CqlUpdateBaseCall extends CqlQueryBaseCall { + CqlUpdateBaseCall() { this.getCalleeName() = "UPDATE" } +} + +class CqlUpsertBaseCall extends CqlQueryBaseCall { + CqlUpsertBaseCall() { this.getCalleeName() = "UPSERT" } +} + +/** + * Obtains the receiver across a variety of types of accesses + */ +Expr getRootReceiver(Expr e) { + result = e and + ( + e instanceof VarRef + or + e instanceof CallExpr and not exists(e.(CallExpr).getReceiver()) + ) + or + result = getRootReceiver(e.(DotExpr).getBase()) + or + result = getRootReceiver(e.(MethodCallExpr).getReceiver()) + or + result = getRootReceiver(e.(PropAccess).getBase()) + or + result = getRootReceiver(e.(TaggedTemplateExpr).getTag()) +} + +/** + * An aggregation type for the two ways to access the fluent API + * provided by the module cds.ql + */ +newtype TCqlClause = + MethodCall(MethodCallExpr callExpr) { + exists(CqlQueryBase base | base = getRootReceiver(callExpr)) or + exists(CqlQueryBaseCall call | call = getRootReceiver(callExpr)) + } or + ShortcutCall(CqlQueryBaseCall callExpr) + +class CqlClause extends TCqlClause { + Expr asExpr() { + result = this.asMethodCall() + or + result = this.asShortcutCall() } - class CqlUpdateBase extends CqlQueryBase { - CqlUpdateBase() { this.getName() = "UPDATE" } + Expr getArgument() { + result = this.asMethodCall().getAnArgument() + or + result = this.asShortcutCall().getAnArgument() } - class CqlUpsertBase extends CqlQueryBase { - CqlUpsertBase() { this.getName() = "UPSERT" } + string getClauseName() { + result = this.asMethodCall().getMethodName() + or + this.asShortcutCall().getCalleeName() = "SELECT" and + result = "columns" + or + this.asShortcutCall().getCalleeName() in ["INSERT", "UPSERT"] and + result = "entries" + or + this.asShortcutCall().getCalleeName() = "UPDATE" and + result = "entity" } + MethodCallExpr asMethodCall() { this = MethodCall(result) } + + CallExpr asShortcutCall() { this = ShortcutCall(result) } + /** - * The cds-ql docs do not mention DELETE being a function acting as a shortcut to any underlying clause + * Convert this `CqlClause` into a `DotExpr`, i.e. + * `Get SELECT.from'Table' when given SELECT.from'Table'.wherecond`, */ - abstract class CqlQueryBaseCall extends CallExpr { - // TODO: Express "It's a global function or a local function imported from cds.ql" - } + DotExpr asDotExpr() { result = this.asMethodCall().getCallee().(DotExpr) } - class CqlSelectBaseCall extends CqlQueryBaseCall { - CqlSelectBaseCall() { this.getCalleeName() = "SELECT" } + string toString() { + result = this.asMethodCall().toString() or + result = this.asShortcutCall().toString() } - class CqlInsertBaseCall extends CqlQueryBaseCall { - CqlInsertBaseCall() { this.getCalleeName() = "INSERT" } + Location getLocation() { + result = this.asMethodCall().getLocation() or + result = this.asShortcutCall().getLocation() } - class CqlUpdateBaseCall extends CqlQueryBaseCall { - CqlUpdateBaseCall() { this.getCalleeName() = "UPDATE" } + CqlQueryBase getCqlBase() { result = getRootReceiver(this.asMethodCall()) } + + CqlQueryBaseCall getCqlBaseCall() { + result = getRootReceiver(this.asMethodCall()).(CqlQueryBaseCall) } - class CqlUpsertBaseCall extends CqlQueryBaseCall { - CqlUpsertBaseCall() { this.getCalleeName() = "UPSERT" } + /** Describes a parent expression relation */ + Expr getParentExpr() { + result = this.asMethodCall().getParentExpr() or + result = this.asShortcutCall().getParentExpr() } /** - * Obtains the receiver across a variety of types of accesses + * Possible cases for constructing a chain of clauses: + * + * (looking at the terminal clause and its possible parent types as tuples: (this, parent)) + * 1) MethodCall.MethodCall + * - example `(SELECT.from(Table), SELECT.from(Table).where("col1='*'"))` + * 2) ShortcutCall.MethodCall + * - example `(SELECT("col1, col2"), SELECT("col1, col2").from("Table"))` + * + * ShortcutCalls cannot be added to any clause chain other than the first position + * example - `SELECT("col1, col2").INSERT(col2)` is not valid */ - Expr getRootReceiver(Expr e) { - result = e and - ( - e instanceof VarRef - or - e instanceof CallExpr and not exists(e.(CallExpr).getReceiver()) - ) - or - result = getRootReceiver(e.(DotExpr).getBase()) + CqlClause getCqlParentExpr() { + result.asMethodCall() = this.asMethodCall().getParentExpr().getParentExpr() or - result = getRootReceiver(e.(MethodCallExpr).getReceiver()) - or - result = getRootReceiver(e.(PropAccess).getBase()) - or - result = getRootReceiver(e.(TaggedTemplateExpr).getTag()) + result.asMethodCall() = this.asShortcutCall().getParentExpr().getParentExpr() } - /** - * An aggregation type for the two ways to access the fluent API - * provided by the module cds.ql - */ - newtype TCqlClause = - MethodCall(MethodCallExpr callExpr) { - exists(CqlQueryBase base | base = getRootReceiver(callExpr)) or - exists(CqlQueryBaseCall call | call = getRootReceiver(callExpr)) - } or - ShortcutCall(CqlQueryBaseCall callExpr) - - class CqlClause extends TCqlClause { - Expr asExpr() { - result = this.asMethodCall() - or - result = this.asShortcutCall() - } + Expr getAnAncestorExpr() { + result = this.asMethodCall().getParentExpr+() or + result = this.asShortcutCall().getParentExpr+() + } - Expr getArgument() { - result = this.asMethodCall().getAnArgument() - or - result = this.asShortcutCall().getAnArgument() - } + CqlClause getAnAncestorCqlClause() { + result.asMethodCall() = this.getAnAncestorExpr() or + result.asShortcutCall() = this.getAnAncestorExpr() + } - string getClauseName() { - result = this.asMethodCall().getMethodName() - or - this.asShortcutCall().getCalleeName() = "SELECT" and - result = "columns" - or - this.asShortcutCall().getCalleeName() in ["INSERT", "UPSERT"] and - result = "entries" - or - this.asShortcutCall().getCalleeName() = "UPDATE" and - result = "entity" - } - - MethodCallExpr asMethodCall() { this = MethodCall(result) } - - CallExpr asShortcutCall() { this = ShortcutCall(result) } - - /** - * Convert this `CqlClause` into a `DotExpr`, i.e. - * `Get SELECT.from'Table' when given SELECT.from'Table'.wherecond`, - */ - DotExpr asDotExpr() { result = this.asMethodCall().getCallee().(DotExpr) } - - string toString() { - result = this.asMethodCall().toString() or - result = this.asShortcutCall().toString() - } - - Location getLocation() { - result = this.asMethodCall().getLocation() or - result = this.asShortcutCall().getLocation() - } - - CqlQueryBase getCqlBase() { result = getRootReceiver(this.asMethodCall()) } - - CqlQueryBaseCall getCqlBaseCall() { - result = getRootReceiver(this.asMethodCall()).(CqlQueryBaseCall) - } - - /** Describes a parent expression relation */ - Expr getParentExpr() { - result = this.asMethodCall().getParentExpr() or - result = this.asShortcutCall().getParentExpr() - } - - /** - * Possible cases for constructing a chain of clauses: - * - * (looking at the terminal clause and its possible parent types as tuples: (this, parent)) - * 1) MethodCall.MethodCall - * - example `(SELECT.from(Table), SELECT.from(Table).where("col1='*'"))` - * 2) ShortcutCall.MethodCall - * - example `(SELECT("col1, col2"), SELECT("col1, col2").from("Table"))` - * - * ShortcutCalls cannot be added to any clause chain other than the first position - * example - `SELECT("col1, col2").INSERT(col2)` is not valid - */ - CqlClause getCqlParentExpr() { - result.asMethodCall() = this.asMethodCall().getParentExpr().getParentExpr() - or - result.asMethodCall() = this.asShortcutCall().getParentExpr().getParentExpr() - } - - Expr getAnAncestorExpr() { - result = this.asMethodCall().getParentExpr+() or - result = this.asShortcutCall().getParentExpr+() - } - - CqlClause getAnAncestorCqlClause() { - result.asMethodCall() = this.getAnAncestorExpr() or - result.asShortcutCall() = this.getAnAncestorExpr() - } - - /** Describes a child expression relation */ - Expr getAChildExpr() { - result = this.asMethodCall().getAChildExpr() or - result = this.asShortcutCall().getAChildExpr() - } - - /** - * the same chain order logic as `getCqlParentExpr` but reversed - */ - CqlClause getAChildCqlClause() { - result.asMethodCall() = this.asMethodCall().getAChildExpr().getAChildExpr() or - result.asShortcutCall() = this.asMethodCall().getAChildExpr().getAChildExpr() - } - - Expr getADescendantExpr() { - result = this.asMethodCall().getAChildExpr+() or - result = this.asShortcutCall().getAChildExpr+() - } - - CqlClause getADescendantCqlClause() { - result.asMethodCall() = this.getADescendantExpr() or - result.asShortcutCall() = this.getADescendantExpr() - } - - /** - * Matches the given `CqlClause` to its method/property name, nested at arbitrary depth. - */ - string getAnAPIName() { - result = this.asDotExpr().getPropertyName() or - result = this.getADescendantCqlClause().getAnAPIName() - } + /** Describes a child expression relation */ + Expr getAChildExpr() { + result = this.asMethodCall().getAChildExpr() or + result = this.asShortcutCall().getAChildExpr() } /** - * A possibly tainted clause - * any clause with a string concatenation in it - * regardless of where that operand came from + * the same chain order logic as `getCqlParentExpr` but reversed */ - class TaintedClause extends CqlClause { - TaintedClause() { exists(StringConcatenation::getAnOperand(this.getArgument().flow())) } + CqlClause getAChildCqlClause() { + result.asMethodCall() = this.asMethodCall().getAChildExpr().getAChildExpr() or + result.asShortcutCall() = this.asMethodCall().getAChildExpr().getAChildExpr() } - /** - * Call to`cds.db.run` - * or - * an await surrounding a sql statement - */ - class CQLSink extends DataFlow::Node { - CQLSink() { - this = any(CdsFacade cds).getMember("db").getMember("run").getACall().getAnArgument() - or - exists(AwaitExpr a, CQL::CqlClause clause | - a.getAChildExpr() = clause.asExpr() and this.asExpr() = clause.asExpr() - ) - } + Expr getADescendantExpr() { + result = this.asMethodCall().getAChildExpr+() or + result = this.asShortcutCall().getAChildExpr+() + } + + CqlClause getADescendantCqlClause() { + result.asMethodCall() = this.getADescendantExpr() or + result.asShortcutCall() = this.getADescendantExpr() } /** - * a more heurisitic based taint step - * captures one of the alternative ways to construct query strings: - * `cds.parse.cql(`string`+userInput)` - * and considers them tainted if they've been concatenated against - * in any manner + * Matches the given `CqlClause` to its method/property name, nested at arbitrary depth. */ - class ParseCQLTaintedClause extends CallNode { - ParseCQLTaintedClause() { - this = any(CdsFacade cds).getMember("parse").getMember("cql").getACall() and - exists(DataFlow::Node n | - n = StringConcatenation::getAnOperand(this.getAnArgument()) and - //omit the fact that the arg of cds.parse.cql (`SELECT * from Foo`) - //is technically a string concat - not n.asExpr() instanceof TemplateElement - ) - } + string getAnAPIName() { + result = this.asDotExpr().getPropertyName() or + result = this.getADescendantCqlClause().getAnAPIName() + } +} + +/** + * A possibly tainted clause + * any clause with a string concatenation in it + * regardless of where that operand came from + */ +class TaintedClause extends CqlClause { + TaintedClause() { exists(StringConcatenation::getAnOperand(this.getArgument().flow())) } +} + +/** + * Call to`cds.db.run` + * or + * an await surrounding a sql statement + */ +class CQLSink extends DataFlow::Node { + CQLSink() { + this = any(CdsFacade cds).getMember("db").getMember("run").getACall().getAnArgument() + or + exists(AwaitExpr a, CqlClause clause | + a.getAChildExpr() = clause.asExpr() and this.asExpr() = clause.asExpr() + ) + } +} + +/** + * a more heurisitic based taint step + * captures one of the alternative ways to construct query strings: + * `cds.parse.cql(`string`+userInput)` + * and considers them tainted if they've been concatenated against + * in any manner + */ +class ParseCQLTaintedClause extends CallNode { + ParseCQLTaintedClause() { + this = any(CdsFacade cds).getMember("parse").getMember("cql").getACall() and + exists(DataFlow::Node n | + n = StringConcatenation::getAnOperand(this.getAnArgument()) and + //omit the fact that the arg of cds.parse.cql (`SELECT * from Foo`) + //is technically a string concat + not n.asExpr() instanceof TemplateElement + ) } } diff --git a/javascript/frameworks/cap/src/loginjection/LogInjection.ql b/javascript/frameworks/cap/src/loginjection/LogInjection.ql index 7e8b3f53a..fe4f2a593 100644 --- a/javascript/frameworks/cap/src/loginjection/LogInjection.ql +++ b/javascript/frameworks/cap/src/loginjection/LogInjection.ql @@ -18,12 +18,12 @@ import advanced_security.javascript.frameworks.cap.CDS /** * A source of remote user controlled input. */ -class CapRemoteSource extends Source, CDS::RequestSource { } +class CapRemoteSource extends Source, RequestSource { } /** * An argument to a logging mechanism. */ -class CapLoggingSink extends Sink, CDS::CdsLogSink { } +class CapLoggingSink extends Sink, CdsLogSink { } from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) diff --git a/javascript/frameworks/cap/src/sqlinjection/SqlInjection.ql b/javascript/frameworks/cap/src/sqlinjection/SqlInjection.ql index ed3e6a0b5..75fbbf95a 100644 --- a/javascript/frameworks/cap/src/sqlinjection/SqlInjection.ql +++ b/javascript/frameworks/cap/src/sqlinjection/SqlInjection.ql @@ -20,22 +20,22 @@ class Configuration extends TaintTracking::Configuration { Configuration() { this = "CapSqlInjection" } override predicate isSource(DataFlow::Node source) { - source instanceof Source or source instanceof CDS::RequestSource + source instanceof Source or source instanceof RequestSource } override predicate isSink(DataFlow::Node sink) { - sink instanceof Sink or sink instanceof CQL::CQLSink + sink instanceof Sink or sink instanceof CQLSink } override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { //string concatenation in a clause arg taints the clause - exists(CQL::TaintedClause clause | + exists(TaintedClause clause | clause.getArgument() = pred.asExpr() and clause.asExpr() = succ.asExpr() ) or //less precise, any concat in the alternative sql stmt construction techniques - exists(CQL::ParseCQLTaintedClause parse | + exists(ParseCQLTaintedClause parse | parse.getAnArgument() = pred and parse = succ ) From 7d2a95fd8c52d5a662d6d9b96510f9ba39fbb9dd Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 18:04:36 -0800 Subject: [PATCH 09/86] Introduce variant `ImplMethodCall` --- .../javascript/frameworks/cap/CDS.qll | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index e2291a30e..fa8d2bbe7 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -31,17 +31,33 @@ private class RequestHandler extends FunctionNode { } private class ErrorHandler extends RequestHandler { } +newtype TUserDefinedApplicationService = + TClassDefinition(ClassNode classNode) { + exists(ApplicationService cdsApplicationService | + classNode.getASuperClassNode() = cdsApplicationService.asSource() + ) + } or + TImplMethodCall(MethodCallNode cdsServiceImplCall) { + exists(CdsFacade cds | + cdsServiceImplCall.getReceiver() = cds.getMember("service").asSource() and + cdsServiceImplCall.getMethodName() = "impl" + ) + } + /** * Subclassing ApplicationService via `extends`: * ```js * class SomeService extends cds.ApplicationService * ``` */ -class UserDefinedApplicationService extends ClassNode { - UserDefinedApplicationService() { - exists(ApplicationService cdsApplicationService | - this.getASuperClassNode() = cdsApplicationService.asSource() - ) +class UserDefinedApplicationService extends TUserDefinedApplicationService { + ClassNode asClassDefinition() { this = TClassDefinition(result) } + + MethodCallNode asImplMethodCall() { this = TImplMethodCall(result) } + + string toString() { + result = this.asClassDefinition().toString() or + result = this.asImplMethodCall().toString() } } @@ -109,7 +125,7 @@ class RequestSource extends OnNodeParam { RequestSource() { // TODO : consider - do we need to actually ever know which service the handler is associated to? exists(UserDefinedApplicationService svc, FunctionNode init | - svc.getAnInstanceMember() = init and + svc.asClassDefinition().getAnInstanceMember() = init and init.getName() = "init" and this.getOnNode().getEnclosingFunction() = init.getAstNode() ) From f8588fb3b9c50ba938fffe1c04dbefff25ae201f Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 13 Feb 2024 20:28:43 -0800 Subject: [PATCH 10/86] Add missing await on call to `cds.connect.to` --- .../cap/test/queries/loginjection/sample-app/server.js | 2 +- .../cap/test/queries/sqlinjection/sample-app/server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js index e386a6cde..2f0beac60 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js @@ -1,5 +1,5 @@ const cds = require('@sap/cds'); -const Service1 = cds.connect.to("Service1"); +const Service1 = await cds.connect.to("Service1"); cds.once('bootstrap', (app) => { app.serve("/log-injection").from("@advanced-security/log-injection"); diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js index 417239c21..c1966545f 100644 --- a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/server.js @@ -1,5 +1,5 @@ const cds = require("@sap/cds"); -const Service1 = cds.connect.to("Service1"); +const Service1 = await cds.connect.to("Service1"); cds.once("bootstrap", (app) => { app.serve("/sql-injection").from("@advanced-security/log-injection"); From a097e044799cdb3875b62bf79eb24f5edf771ed4 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 14 Feb 2024 10:33:03 -0800 Subject: [PATCH 11/86] Add classes predicates --- .../javascript/frameworks/cap/CDS.qll | 264 ++++++++++++++---- 1 file changed, 206 insertions(+), 58 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index fa8d2bbe7..47f7a506b 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -1,42 +1,199 @@ import javascript import DataFlow -// TODO: should this base type be more specific? -abstract class ServiceInstance extends DataFlow::Node { } +/** + * ```js + * const cds = require('@sap/cds') + * ``` + */ +class CdsFacade extends API::Node { + CdsFacade() { this = API::moduleImport("@sap/cds") } +} /** - * Call to`cds.serve` + * A call to `serve` on a CDS facade. */ -class CdsServeCall extends ServiceInstance { - CdsServeCall() { this = any(CdsFacade cds).getMember("serve").getACall() } +class CdsServeCall extends MethodCallNode { + CdsServeCall() { exists(CdsFacade cds | this = cds.getMember("serve").getACall()) } } /** - * call to: - * `new cds.ApplicationService` or `new cds.Service` + * A service instance that are obtained by the service's name, either via: + * - Serving defined services via cds.serve and awaiting its promise, or + * - Connecting to a service already being served and awaiting its promise, or + * - Simply calling a constructor with a `new` keyword. + * e.g. + * ```javascript + * // Obtained through `cds.serve` + * const { Service1, Service2 } = await cds.serve("all"); + * const Service1 = await cds.serve("service-1"); + * + * // Obtained through `cds.connect.to` + * const Service1 = await cds.connect.to("service-1"); + * + * // A constructor call + * const srv = new cds.ApplicationService(...); + * const srv = new cds.Service(...); + * ``` */ -class ServiceConstructor extends ServiceInstance { - ServiceConstructor() { this = any(ApplicationService cds).getAnInstantiation() } +class ServiceInstance extends DataFlow::Node { + ServiceInstance() { + exists(AwaitExpr await, CdsServeCall cdsServe | + /* 1. Obtained using `cds.Serve` */ + ( + /* + * 1-1. Destructuring definition, e.g. + * ``` + * const { Service1, Service2 } = await cds.serve("all"); + * ``` + */ + + this.asExpr().getFirstControlFlowNode().(VarDef).getDestructuringSource() = await + or + /* + * 1-2. Direct definition, e.g. + * ``` + * const Service1 = await cds.serve("service-1"); + * ``` + */ + + this.getALocalSource().asExpr() = await + ) and + await.getOperand().flow() = cdsServe + ) + or + /* 2. Obtained using `cds.connect.to` */ + exists(AwaitExpr await, CdsConnectTo cdsConnectTo | + this.getALocalSource().asExpr() = await and + await.getOperand().flow() = cdsConnectTo + ) + or + /* + * 3. A constructor call on the class with a `new` keyword, e.g. + * ``` + * const srv = new cds.ApplicationService(...); + * const srv = new cds.Service(...); + * ``` + */ + + this = any(ApplicationService cds).getAnInstantiation() + } } /** - * return value of `cds.connect.to` + * A Call to `cds.connect.to` that returns a promise containing the service that is asked for by its name. */ -class ConnectTo extends ServiceInstance { - ConnectTo() { this = any(CdsFacade cds).getMember("connect").getMember("to").getACall() } +class CdsConnectTo extends MethodCallNode { + string serviceName; + + CdsConnectTo() { + exists(CdsFacade cds | + this = cds.getMember("connect").getMember("to").getACall() and + serviceName = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + ) + } } -/** Last argument to the service methods `srv.before`, `srv.on`, and `srv.after` */ -private class RequestHandler extends FunctionNode { } +/** + * A call to `before`, `on`, or `after` on an `ApplicationService`. + * It registers an handler to be executed when an event is fired, + * to do something with the incoming request or event. + */ +class HandlerRegistration extends MethodCallNode { + HandlerRegistration() { + exists(ApplicationService srv | + this.getReceiver() = srv.asSource() and + ( + this.getMethodName() = "before" or + this.getMethodName() = "on" or + this.getMethodName() = "after" + ) + ) + } + + /** + * Get the name of the event that the handler is registered for. + */ + string getAnEventName() { + exists(StringLiteral stringLiteral | + stringLiteral = this.getArgument(0).asExpr() and + result = stringLiteral.getValue() + ) + or + exists(ArrayLiteralNode arrayLiteral | + arrayLiteral = this.getArgument(0) and + result = arrayLiteral.getAnElement().asExpr().(StringLiteral).getValue() + ) + } + + /** + * Get the name of the entity that the handler is registered for, if any. + */ + string getEntityName() { result = this.getArgument(1).asExpr().(StringLiteral).getValue() } +} + +/** + * The handler that implements a service's logic to deal with the incoming request or message when a certain event is fired. + * It is the last argument to the method calls that registers the handler: either `srv.before`, `srv.on`, or `srv.after`. + */ +abstract class Handler extends FunctionNode { + UserDefinedApplicationService srv; + string eventName; -private class ErrorHandler extends RequestHandler { } + Handler() { + this.asExpr().getEnclosingFunction() = srv.getInitFunction().asExpr() and + exists(HandlerRegistration handlerRegistration | + handlerRegistration.asExpr() = this.getEnclosingExpr() and + eventName = handlerRegistration.getAnEventName() + ) + } + + /** + * Gets the service registering this handler. + */ + UserDefinedApplicationService getDefiningService() { result = srv } + + /** + * Gets a name of one of the event this handler is registered for. + */ + string getAnEventName() { result = eventName } +} + +/** + * A handler whose parameter is of type `cds.Event`. It handles asynchronous events. + */ +class MessageHandler extends Handler { } + +/** + * A handler whose parameter is of type `cds.Request`, and might contain a second parameter for + * passing the control to the handler registered below it. It handles synchronous requests. + */ +class RequestHandler extends Handler { } + +/** + * A handler that handles errors. (TODO) + */ +class ErrorHandler extends Handler { } newtype TUserDefinedApplicationService = + /** + * Subclassing `ApplicationService` via a ES6 class definition. + * ```js + * class SomeService extends cds.ApplicationService + * ``` + */ TClassDefinition(ClassNode classNode) { exists(ApplicationService cdsApplicationService | classNode.getASuperClassNode() = cdsApplicationService.asSource() ) } or + /** + * Subclassing `ApplicationService` via a call to `cds.service.impl`. + * ```js + * const cds = require('@sap/cds') + * module.exports = cds.service.impl (function() { ... }) + * ``` + */ TImplMethodCall(MethodCallNode cdsServiceImplCall) { exists(CdsFacade cds | cdsServiceImplCall.getReceiver() = cds.getMember("service").asSource() and @@ -45,10 +202,7 @@ newtype TUserDefinedApplicationService = } /** - * Subclassing ApplicationService via `extends`: - * ```js - * class SomeService extends cds.ApplicationService - * ``` + * A custom application service of type `cds.ApplicationService`, where parts of the business logic are implemented. */ class UserDefinedApplicationService extends TUserDefinedApplicationService { ClassNode asClassDefinition() { this = TClassDefinition(result) } @@ -59,18 +213,14 @@ class UserDefinedApplicationService extends TUserDefinedApplicationService { result = this.asClassDefinition().toString() or result = this.asImplMethodCall().toString() } -} -/** - * Subclassing ApplicationService via `cds.service.impl`: - * ```js - * const cds = require('@sap/cds') - * module.exports = cds.service.impl (function() { ... }) - * ``` - */ -class OldStyleUserDefinedApplicationService extends MethodCallNode { - OldStyleUserDefinedApplicationService() { - exists(CdsFacade cds | this = cds.getMember("service").getMember("impl").getACall()) + FunctionNode getInitFunction() { + result = this.asClassDefinition().getInstanceMethod("init") or + result = this.asImplMethodCall().getArgument(0) + } + + HandlerRegistration getAHandlerRegistration() { + result.getEnclosingFunction() = getInitFunction().asExpr() } } @@ -134,24 +284,35 @@ class RequestSource extends OnNodeParam { } } -class ApplicationService extends API::Node { +private class ApplicationService extends API::Node { ApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } } /** - * ```js - * const cds = require('@sap/cds') - * ``` + * Methods that parse source strings into a CQL expression. */ -class CdsFacade extends API::Node { - CdsFacade() { this = API::moduleImport("@sap/cds") } +class ParseSink extends DataFlow::Node { + ParseSink() { + this = + any(CdsFacade cds) + .getMember("parse") + .getMember(["expr", "ref", "xpr"]) + .getACall() + .getAnArgument() + } } /** - * Call to`cds.log` + * A logger obtained by a call to `log` on a CDS facade. Each logger is associated with + * its unique name. */ -class CdsLogCall extends API::Node { - CdsLogCall() { this = any(CdsFacade cds).getMember("log") } +class CdsLogger extends MethodCallNode { + string name; + + CdsLogger() { + this = any(CdsFacade cds).getMember("log").getACall() and + name = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + } } /** @@ -159,24 +320,11 @@ class CdsLogCall extends API::Node { */ class CdsLogSink extends DataFlow::Node { CdsLogSink() { - this = - any(CdsLogCall cdsLog) - .getACall() - .getAChainedMethodCall(["trace", "debug", "info", "log", "warn", "error"]) - .getAnArgument() - } -} - -/** - * Methods that parse source strings into a CQL expression - */ -class ParseSink extends DataFlow::Node { - ParseSink() { - this = - any(CdsFacade cds) - .getMember("parse") - .getMember(["expr", "ref", "xpr"]) - .getACall() - .getAnArgument() + exists(CdsLogger log, MethodCallNode loggingMethod | + this = loggingMethod.getAnArgument() and + not this.asExpr() instanceof Literal and + loggingMethod.getReceiver().getALocalSource() = log and + loggingMethod.getMethodName() = ["trace", "debug", "info", "log", "warn", "error"] + ) } } From 7a97eb06182bea4045189e46625ae3114e01c6f1 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 14 Feb 2024 18:56:29 -0800 Subject: [PATCH 12/86] Add CDL.qll --- .../javascript/frameworks/cap/CDL.qll | 122 ++++++++++++++++++ .../javascript/frameworks/cap/CDS.qll | 2 +- 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll new file mode 100644 index 000000000..3a71648fe --- /dev/null +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll @@ -0,0 +1,122 @@ +/** + * A module to reason about CDL, the language to write specification of models of services, parsed into JSON. + */ + +import javascript + +newtype CdlKind = + Service(string value) { value = "service" } or + Entity(string value) { value = "entity" } or + Event(string value) { value = "event" } or + Action(string value) { value = "action" } + +/** + * Any CDL element, including entities, event, actions, and more. + */ +class CdlDefinition extends JsonObject { + CdlDefinition() { exists(JsonObject root | this = root.getPropValue("definitions")) } + + JsonObject getElement(string elementName) { result = this.getPropValue(elementName) } + + JsonObject getAnElement() { result = this.getElement(_) } +} + +abstract class CdlElement extends JsonObject { + abstract string getName(); + + abstract CdlKind getKind(); +} + +class CdlService extends CdlElement { + string name; + CdlKind kind; + + CdlService() { + exists(CdlDefinition definition | + this = definition.getElement(name) and + kind = Service(this.getPropStringValue("kind")) + ) + } + + override string getName() { result = name } + + override CdlKind getKind() { result = kind } + + CdlEntity getEntity(string entityName) { + entityName = result.getName() and + /* WARNING: Hacky! */ + entityName.splitAt(".", 0) = name + } +} + +class CdlEntity extends CdlElement { + string name; + CdlKind kind; + + CdlEntity() { + exists(CdlDefinition definition | + this = definition.getElement(name) and + kind = Entity(this.getPropStringValue("kind")) + ) + } + + override string getName() { result = name } + + override CdlKind getKind() { result = kind } + + CdlAttribute getAttribute(string attributeName) { + result = this.getPropValue("elements").getPropValue(attributeName) + } +} + +class CdlEvent extends CdlElement { + string name; + CdlKind kind; + + CdlEvent() { + exists(CdlDefinition definition | + this = definition.getElement(name) and + kind = Event(this.getPropStringValue("kind")) + ) + } + + override string getName() { result = name } + + override CdlKind getKind() { result = kind } + + CdlAttribute getAttribute(string attributeName) { + result = this.getPropValue("elements").getPropValue(attributeName) + } +} + +class CdlAction extends CdlElement { + string name; + CdlKind kind; + + CdlAction() { + exists(CdlDefinition definition | + this = definition.getElement(name) and + kind = Action(this.getPropStringValue("kind")) + ) + } + + override string getName() { result = name } + + override CdlKind getKind() { result = kind } + + CdlAttribute getAttribute(string attributeName) { + result = this.getPropValue("elements").getPropValue(attributeName) + } +} + +class CdlAttribute extends JsonObject { + string name; + + CdlAttribute() { + exists(CdlElement entity | this = entity.getPropValue("elements").getPropValue(name)) + } + + string getType() { result = this.getPropStringValue("type") } + + int getLength() { result = this.getPropValue("length").(JsonPrimitiveValue).getIntValue() } +} diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 47f7a506b..968049d9a 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -30,7 +30,7 @@ class CdsServeCall extends MethodCallNode { * * // Obtained through `cds.connect.to` * const Service1 = await cds.connect.to("service-1"); - * + * * // A constructor call * const srv = new cds.ApplicationService(...); * const srv = new cds.Service(...); From db58714698b61004e61025c98d42220fd835cde2 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 14 Feb 2024 19:00:40 -0800 Subject: [PATCH 13/86] Fix parsing errors on the cds files --- .../cap/test/queries/loginjection/sample-app/db/schema.cds | 6 +++--- .../test/queries/loginjection/sample-app/srv/service1.cds | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds b/javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds index 58682e397..1d4202fb1 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds @@ -1,11 +1,11 @@ -namespace advanced-security.log-injection.sample-entities; +namespace advanced_security.log_injection.sample_entities; entity Entity1 { - Attribute1 : String(100) + Attribute1 : String(100); Attribute2 : String(100) } entity Entity2 { - Attribute3 : String(100) + Attribute3 : String(100); Attribute4 : String(100) } \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds index 41d6ce4c9..d8477454b 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds @@ -1,8 +1,8 @@ -using { advanced-security.log-injection.sample-entities as db-schema } from '../db/schema'; +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; service Service1 { /* Entity to send READ about. */ - entity Service1Entity as projection on db-schema.Entity1 excluding { Attribute2 } + entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } /* Async API for Service1 to speak through. */ event Received1: { From 5bc2a7950396bf507751aab91b3635904183b7c2 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 15 Feb 2024 12:36:54 -0800 Subject: [PATCH 14/86] Implement and clean up definitions of `Handler`s --- .../javascript/frameworks/cap/CDS.qll | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 968049d9a..03078d721 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -1,5 +1,6 @@ import javascript import DataFlow +import advanced_security.javascript.frameworks.cap.CDL /** * ```js @@ -136,7 +137,7 @@ class HandlerRegistration extends MethodCallNode { * The handler that implements a service's logic to deal with the incoming request or message when a certain event is fired. * It is the last argument to the method calls that registers the handler: either `srv.before`, `srv.on`, or `srv.after`. */ -abstract class Handler extends FunctionNode { +class Handler extends FunctionNode { UserDefinedApplicationService srv; string eventName; @@ -160,20 +161,19 @@ abstract class Handler extends FunctionNode { } /** - * A handler whose parameter is of type `cds.Event`. It handles asynchronous events. + * A handler whose first parameter is of type `cds.Event` and handles the event in an + * asynchronous manner, or is of type `cds.Request` and handles the event synchronously. */ -class MessageHandler extends Handler { } - -/** - * A handler whose parameter is of type `cds.Request`, and might contain a second parameter for - * passing the control to the handler registered below it. It handles synchronous requests. - */ -class RequestHandler extends Handler { } +class EventHandler extends Handler { + EventHandler() { exists(CdlEvent event | this.getAnEventName() = event.getName()) } +} /** - * A handler that handles errors. (TODO) + * A handler that handles errors. */ -class ErrorHandler extends Handler { } +class ErrorHandler extends Handler { + ErrorHandler() { this.getAnEventName() = "error" } +} newtype TUserDefinedApplicationService = /** From 903537740c17148d6818fe683e39518bbb7aa1af Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 16 Feb 2024 10:58:42 -0800 Subject: [PATCH 15/86] Fix failing unit tests Either by: - Fixing module paths, or - Using newer classes --- javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql | 2 +- .../models/cds/oldstyleuserdefined/oldstyleuserdefined.ql | 5 +++-- .../test/models/cds/userdefinedservice/userdefinedservice.ql | 2 +- javascript/frameworks/cap/test/models/cql/delete/delete.ql | 2 +- javascript/frameworks/cap/test/models/cql/insert/insert.ql | 2 +- javascript/frameworks/cap/test/models/cql/select/select.ql | 2 +- .../cap/test/models/cql/taintedclause/taintedclause.ql | 2 +- javascript/frameworks/cap/test/models/cql/update/update.ql | 2 +- javascript/frameworks/cap/test/models/cql/upsert/upsert.ql | 3 +-- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql b/javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql index 11a59e4dc..b20a1498c 100644 --- a/javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql +++ b/javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql @@ -1,5 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CDS -from CDS::ParseSink sink +from ParseSink sink select sink \ No newline at end of file diff --git a/javascript/frameworks/cap/test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql b/javascript/frameworks/cap/test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql index 9d3229dcb..ad3affced 100644 --- a/javascript/frameworks/cap/test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql +++ b/javascript/frameworks/cap/test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql @@ -1,5 +1,6 @@ import javascript import advanced_security.javascript.frameworks.cap.CDS -from CDS::OldStyleUserDefinedApplicationService svc -select svc \ No newline at end of file +from MethodCallNode cdsServiceImplCall +where exists(TUserDefinedApplicationService svc | svc = TImplMethodCall(cdsServiceImplCall)) +select cdsServiceImplCall diff --git a/javascript/frameworks/cap/test/models/cds/userdefinedservice/userdefinedservice.ql b/javascript/frameworks/cap/test/models/cds/userdefinedservice/userdefinedservice.ql index 0a3adf59d..3b7e50102 100644 --- a/javascript/frameworks/cap/test/models/cds/userdefinedservice/userdefinedservice.ql +++ b/javascript/frameworks/cap/test/models/cds/userdefinedservice/userdefinedservice.ql @@ -1,5 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CDS -from CDS::UserDefinedApplicationService svc +from UserDefinedApplicationService svc select svc \ No newline at end of file diff --git a/javascript/frameworks/cap/test/models/cql/delete/delete.ql b/javascript/frameworks/cap/test/models/cql/delete/delete.ql index 2c7551107..e22ccd2f8 100644 --- a/javascript/frameworks/cap/test/models/cql/delete/delete.ql +++ b/javascript/frameworks/cap/test/models/cql/delete/delete.ql @@ -1,6 +1,6 @@ import javascript import advanced_security.javascript.frameworks.cap.CQL -from CQL::CqlClause s +from CqlClause s select s.getLocation(), s diff --git a/javascript/frameworks/cap/test/models/cql/insert/insert.ql b/javascript/frameworks/cap/test/models/cql/insert/insert.ql index 3f53113e1..7b12dc95e 100644 --- a/javascript/frameworks/cap/test/models/cql/insert/insert.ql +++ b/javascript/frameworks/cap/test/models/cql/insert/insert.ql @@ -1,5 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CQL -from CQL::CqlClause s +from CqlClause s select s.getLocation(), s diff --git a/javascript/frameworks/cap/test/models/cql/select/select.ql b/javascript/frameworks/cap/test/models/cql/select/select.ql index d2961efff..b3ff8981d 100644 --- a/javascript/frameworks/cap/test/models/cql/select/select.ql +++ b/javascript/frameworks/cap/test/models/cql/select/select.ql @@ -1,5 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CQL -from CQL::CqlClause s +from CqlClause s select s.getLocation(), s \ No newline at end of file diff --git a/javascript/frameworks/cap/test/models/cql/taintedclause/taintedclause.ql b/javascript/frameworks/cap/test/models/cql/taintedclause/taintedclause.ql index 1fc1dd1bc..3034a38e4 100644 --- a/javascript/frameworks/cap/test/models/cql/taintedclause/taintedclause.ql +++ b/javascript/frameworks/cap/test/models/cql/taintedclause/taintedclause.ql @@ -1,5 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CQL -from CQL::ParseCQLTaintedClause clause +from ParseCQLTaintedClause clause select clause \ No newline at end of file diff --git a/javascript/frameworks/cap/test/models/cql/update/update.ql b/javascript/frameworks/cap/test/models/cql/update/update.ql index 2c7551107..e22ccd2f8 100644 --- a/javascript/frameworks/cap/test/models/cql/update/update.ql +++ b/javascript/frameworks/cap/test/models/cql/update/update.ql @@ -1,6 +1,6 @@ import javascript import advanced_security.javascript.frameworks.cap.CQL -from CQL::CqlClause s +from CqlClause s select s.getLocation(), s diff --git a/javascript/frameworks/cap/test/models/cql/upsert/upsert.ql b/javascript/frameworks/cap/test/models/cql/upsert/upsert.ql index 2c7551107..7b12dc95e 100644 --- a/javascript/frameworks/cap/test/models/cql/upsert/upsert.ql +++ b/javascript/frameworks/cap/test/models/cql/upsert/upsert.ql @@ -1,6 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CQL -from CQL::CqlClause s +from CqlClause s select s.getLocation(), s - From 7b28623758ef1cd23feec5655f81234e7577b485 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 16 Feb 2024 13:45:49 -0800 Subject: [PATCH 16/86] Add and reorder classes --- .../javascript/frameworks/cap/CDL.qll | 2 + .../javascript/frameworks/cap/CDS.qll | 208 +++++++++--------- .../frameworks/cap/RemoteFlowSources.qll | 19 ++ 3 files changed, 126 insertions(+), 103 deletions(-) create mode 100644 javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll index 3a71648fe..7a94033a4 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll @@ -80,6 +80,8 @@ class CdlEvent extends CdlElement { ) } + string getBasename() { result = name.splitAt(".", count(name.indexOf("."))) } + override string getName() { result = name } override CdlKind getKind() { result = kind } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 03078d721..605e62a77 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -9,6 +9,8 @@ import advanced_security.javascript.frameworks.cap.CDL */ class CdsFacade extends API::Node { CdsFacade() { this = API::moduleImport("@sap/cds") } + + Node getNode() { result = this.asSource() } } /** @@ -19,72 +21,89 @@ class CdsServeCall extends MethodCallNode { } /** - * A service instance that are obtained by the service's name, either via: - * - Serving defined services via cds.serve and awaiting its promise, or - * - Connecting to a service already being served and awaiting its promise, or - * - Simply calling a constructor with a `new` keyword. - * e.g. + * A dataflow node that represents a service. + */ +abstract class ServiceInstance extends DataFlow::Node { } // Use `DataFlow::Node` to be the most general. + +/** + * A service instance obtained by the service's name, via serving + * defined services via `cds.serve` and awaiting its promise. e.g. * ```javascript * // Obtained through `cds.serve` * const { Service1, Service2 } = await cds.serve("all"); * const Service1 = await cds.serve("service-1"); - * - * // Obtained through `cds.connect.to` - * const Service1 = await cds.connect.to("service-1"); - * - * // A constructor call - * const srv = new cds.ApplicationService(...); - * const srv = new cds.Service(...); * ``` */ -class ServiceInstance extends DataFlow::Node { - ServiceInstance() { +class ServiceInstanceFromCdsServe extends ServiceInstance { + ServiceInstanceFromCdsServe() { exists(AwaitExpr await, CdsServeCall cdsServe | /* 1. Obtained using `cds.Serve` */ ( - /* - * 1-1. Destructuring definition, e.g. - * ``` - * const { Service1, Service2 } = await cds.serve("all"); - * ``` - */ - + /* 1-1. Destructuring definition */ this.asExpr().getFirstControlFlowNode().(VarDef).getDestructuringSource() = await or - /* - * 1-2. Direct definition, e.g. - * ``` - * const Service1 = await cds.serve("service-1"); - * ``` - */ - + /* 1-2. Direct definition */ this.getALocalSource().asExpr() = await ) and await.getOperand().flow() = cdsServe ) - or - /* 2. Obtained using `cds.connect.to` */ + } +} + +/** + * A service instance obtained by the service's name, via connecting + * to a service already being served and awaiting its promise. e.g. + * ```javascript + * // Obtained through `cds.connect.to` + * const Service1 = await cds.connect.to("service-1"); + * ``` + */ +class ServiceInstanceFromCdsConnectTo extends ServiceInstance { + ServiceInstanceFromCdsConnectTo() { exists(AwaitExpr await, CdsConnectTo cdsConnectTo | this.getALocalSource().asExpr() = await and await.getOperand().flow() = cdsConnectTo ) - or - /* - * 3. A constructor call on the class with a `new` keyword, e.g. - * ``` - * const srv = new cds.ApplicationService(...); - * const srv = new cds.Service(...); - * ``` - */ - - this = any(ApplicationService cds).getAnInstantiation() + } +} + +/** + * A service instance obtained by directly calling the constructor + * of its class with a `new` keyword. e.g. + * ```javascript + * // A constructor call + * const srv = new cds.ApplicationService(...); + * const srv = new cds.Service(...); + * ``` + */ +class ServiceInstanceFromConstructor extends ServiceInstance { + ServiceInstanceFromConstructor() { this = any(ApplicationService cds).getAnInstantiation() } +} + +/** + * The parameter node representing the service being served, given to a + * callback argument to the `cds.serve(...).with` call. e.g. + * ```js + * cds.serve('./srv/some-service').with ((srv) => { + * srv.on ('READ','SomeEntity', (req) => req.reply([...])) + * }) + * ``` + * This is used to extend the given service's functionality. + */ +class ServiceInstanceFromServeWithParameter extends ParameterNode, ServiceInstance { + ServiceInstanceFromServeWithParameter() { + exists(MethodCallNode withCall, CdsServeCall cdsServe | + withCall.getMethodName() = "with" and + withCall.getReceiver() = cdsServe and + this = withCall.getArgument(0).(FunctionNode).getParameter(0) + ) } } /** * A Call to `cds.connect.to` that returns a promise containing the service that is asked for by its name. */ -class CdsConnectTo extends MethodCallNode { +private class CdsConnectTo extends MethodCallNode { string serviceName; CdsConnectTo() { @@ -98,12 +117,15 @@ class CdsConnectTo extends MethodCallNode { /** * A call to `before`, `on`, or `after` on an `ApplicationService`. * It registers an handler to be executed when an event is fired, - * to do something with the incoming request or event. + * to do something with the incoming request or event as its parameter. */ class HandlerRegistration extends MethodCallNode { HandlerRegistration() { - exists(ApplicationService srv | - this.getReceiver() = srv.asSource() and + exists(UserDefinedApplicationService srv | + ( + this.getReceiver() = srv.asClassDefinition() or + this.getReceiver() = srv.asImplMethodCall() + ) and ( this.getMethodName() = "before" or this.getMethodName() = "on" or @@ -160,12 +182,45 @@ class Handler extends FunctionNode { string getAnEventName() { result = eventName } } +/** + * Built-in event names to use to talk to a service. + * - Event names for [REST-style API](https://cap.cloud.sap/docs/node.js/core-services#rest-style-api) + * - GET + * - PUT + * - POST + * - PATCH + * - DELETE + * - Event names for [CRUD-style API](https://cap.cloud.sap/docs/node.js/core-services#crud-style-api) + * - READ + * - CREATE + * - INSERT + * - UPSERT + * - UPDATE + * - DELETE + */ +class BuiltInEventNames extends string { + BuiltInEventNames() { + /* 1. REST-style API names. */ + this = ["GET", "PUT", "POST", "PATCH", "DELETE"] + or + /* 2. CRUD-style API names. */ + this = ["READ", "CREATE", "INSERT", "UPSERT", "UPDATE", "DELETE"] + } + + predicate isRestStyle() { this = ["GET", "PUT", "POST", "PATCH", "DELETE"] } + + predicate isCrudStyle() { this = ["READ", "CREATE", "INSERT", "UPSERT", "UPDATE", "DELETE"] } +} + /** * A handler whose first parameter is of type `cds.Event` and handles the event in an * asynchronous manner, or is of type `cds.Request` and handles the event synchronously. */ class EventHandler extends Handler { - EventHandler() { exists(CdlEvent event | this.getAnEventName() = event.getName()) } + EventHandler() { + exists(CdlEvent event | this.getAnEventName() = event.getBasename()) or + exists(BuiltInEventNames builtInName | this.getAnEventName() = builtInName) + } } /** @@ -222,65 +277,12 @@ class UserDefinedApplicationService extends TUserDefinedApplicationService { HandlerRegistration getAHandlerRegistration() { result.getEnclosingFunction() = getInitFunction().asExpr() } -} -/** - * Parameter of a `srv.with` method call: - * ```js - * cds.serve('./srv/cat-service') .with ((srv) => { - * srv.on ('READ','Books', (req) => req.reply([...])) - * }) - * ``` - * - * TODO expand this to capture request handlers registered inside the function - */ -class WithCallParameter extends RequestHandler { - WithCallParameter() { - exists(MethodCallNode withCall, ServiceInstance svc | - withCall.getArgument(0) = this and - withCall.getMethodName() = "with" and - withCall.getReceiver() = svc - ) - } -} - -/** - * Parameter of request handler of `_.on`: - * ```js - * _.on ('READ','Books', (req) => req.reply([...])) - * ``` - */ -class OnNodeParam extends ValueNode, ParameterNode { - MethodCallNode on; - - OnNodeParam() { - exists(FunctionNode handler | - on.getMethodName() = "on" and - on.getLastArgument() = handler and - handler.getLastParameter() = this - ) - } - - MethodCallNode getOnNode() { result = on } -} - -/** - * Parameter of request handler of `srv.on`: - * ```js - * this.on ('READ','Books', (req) => req.reply([...])) - * ``` - * not sure how else to know which service is registering the handler - */ -class RequestSource extends OnNodeParam { - RequestSource() { - // TODO : consider - do we need to actually ever know which service the handler is associated to? - exists(UserDefinedApplicationService svc, FunctionNode init | - svc.asClassDefinition().getAnInstanceMember() = init and - init.getName() = "init" and - this.getOnNode().getEnclosingFunction() = init.getAstNode() - ) - or - exists(WithCallParameter pa | this.getOnNode().getEnclosingFunction() = pa.getFunction()) + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.asClassDefinition().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) or + this.asImplMethodCall().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll new file mode 100644 index 000000000..76f1ab6b9 --- /dev/null +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll @@ -0,0 +1,19 @@ +import javascript +import advanced_security.javascript.frameworks.cap.CDS + +/** + * A parameter of a handler registered for a service on an event. e.g. + * ```javascript + * this.on("SomeEvent", "SomeEntity", (req) => { ... }); + * this.before("SomeEvent", "SomeEntity", (req, next) => { ... }); // only `req` is captured + * SomeService.on("SomeEvent", "SomeEntity", (msg) => { ... }); + * SomeService.after("SomeEvent", "SomeEntity", (msg) => { ... }); + * ``` + * All the parameters named `req` and `msg` are captured in the above example. + */ +/* TODO: narrow the definition down to exposed events */ +class HandlerParameter extends ParameterNode, RemoteFlowSource { + HandlerParameter() { exists(EventHandler handler | this = handler.getParameter(0)) } + + override string getSourceType() { result = "Parameter of an event handler" } +} From cb2c7b626c9c50be36975e4ae072e76f9b170555 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 16 Feb 2024 15:44:20 -0800 Subject: [PATCH 17/86] Rename ApplicationService to CdsApplicationService --- .../javascript/frameworks/cap/CDS.qll | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 605e62a77..2ad8397a1 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -77,7 +77,7 @@ class ServiceInstanceFromCdsConnectTo extends ServiceInstance { * ``` */ class ServiceInstanceFromConstructor extends ServiceInstance { - ServiceInstanceFromConstructor() { this = any(ApplicationService cds).getAnInstantiation() } + ServiceInstanceFromConstructor() { this = any(CdsApplicationService cds).getAnInstantiation() } } /** @@ -115,7 +115,7 @@ private class CdsConnectTo extends MethodCallNode { } /** - * A call to `before`, `on`, or `after` on an `ApplicationService`. + * A call to `before`, `on`, or `after` on an `CdsApplicationService`. * It registers an handler to be executed when an event is fired, * to do something with the incoming request or event as its parameter. */ @@ -232,18 +232,18 @@ class ErrorHandler extends Handler { newtype TUserDefinedApplicationService = /** - * Subclassing `ApplicationService` via a ES6 class definition. + * Subclassing `cds.ApplicationService` via a ES6 class definition. * ```js * class SomeService extends cds.ApplicationService * ``` */ TClassDefinition(ClassNode classNode) { - exists(ApplicationService cdsApplicationService | + exists(CdsApplicationService cdsApplicationService | classNode.getASuperClassNode() = cdsApplicationService.asSource() ) } or /** - * Subclassing `ApplicationService` via a call to `cds.service.impl`. + * Subclassing `cds.ApplicationService` via a call to `cds.service.impl`. * ```js * const cds = require('@sap/cds') * module.exports = cds.service.impl (function() { ... }) @@ -286,8 +286,8 @@ class UserDefinedApplicationService extends TUserDefinedApplicationService { } } -private class ApplicationService extends API::Node { - ApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } +private class CdsApplicationService extends API::Node { + CdsApplicationService() { exists(CdsFacade c | this = c.getMember("CdsApplicationService")) } } /** From a95b38d820abffaa8eeab031e4c183a5fb8b3184 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 16 Feb 2024 18:47:45 -0800 Subject: [PATCH 18/86] Fix unit test `requesthandler` --- .../javascript/frameworks/cap/CDS.qll | 38 +++++++++---------- .../frameworks/cap/RemoteFlowSources.qll | 2 +- .../cds/requesthandler/requesthandler.ql | 4 +- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 2ad8397a1..1ddd6cb2c 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -80,6 +80,15 @@ class ServiceInstanceFromConstructor extends ServiceInstance { ServiceInstanceFromConstructor() { this = any(CdsApplicationService cds).getAnInstantiation() } } +/** + * A service instance that represents an w + */ +class ServiceInstanceFromThisNode extends ServiceInstance { + ServiceInstanceFromThisNode() { + exists(ThisNode thisNode | thisNode.flowsTo(this) and this != thisNode) + } +} + /** * The parameter node representing the service being served, given to a * callback argument to the `cds.serve(...).with` call. e.g. @@ -115,17 +124,14 @@ private class CdsConnectTo extends MethodCallNode { } /** - * A call to `before`, `on`, or `after` on an `CdsApplicationService`. + * A call to `before`, `on`, or `after` on an `cds.ApplicationService`. * It registers an handler to be executed when an event is fired, * to do something with the incoming request or event as its parameter. */ class HandlerRegistration extends MethodCallNode { HandlerRegistration() { - exists(UserDefinedApplicationService srv | - ( - this.getReceiver() = srv.asClassDefinition() or - this.getReceiver() = srv.asImplMethodCall() - ) and + exists(ServiceInstance srv | + (srv.(SourceNode).flowsTo(this.getReceiver()) or srv = this.getReceiver()) and ( this.getMethodName() = "before" or this.getMethodName() = "on" or @@ -158,16 +164,17 @@ class HandlerRegistration extends MethodCallNode { /** * The handler that implements a service's logic to deal with the incoming request or message when a certain event is fired. * It is the last argument to the method calls that registers the handler: either `srv.before`, `srv.on`, or `srv.after`. + * Its first parameter is of type `cds.Event` and handles the event in an asynchronous manner, + * or is of type `cds.Request` and handles the event synchronously. */ class Handler extends FunctionNode { UserDefinedApplicationService srv; string eventName; Handler() { - this.asExpr().getEnclosingFunction() = srv.getInitFunction().asExpr() and exists(HandlerRegistration handlerRegistration | - handlerRegistration.asExpr() = this.getEnclosingExpr() and - eventName = handlerRegistration.getAnEventName() + this = handlerRegistration.getAnArgument() and + eventName = handlerRegistration.getArgument(0).asExpr().(StringLiteral).getValue() ) } @@ -212,17 +219,6 @@ class BuiltInEventNames extends string { predicate isCrudStyle() { this = ["READ", "CREATE", "INSERT", "UPSERT", "UPDATE", "DELETE"] } } -/** - * A handler whose first parameter is of type `cds.Event` and handles the event in an - * asynchronous manner, or is of type `cds.Request` and handles the event synchronously. - */ -class EventHandler extends Handler { - EventHandler() { - exists(CdlEvent event | this.getAnEventName() = event.getBasename()) or - exists(BuiltInEventNames builtInName | this.getAnEventName() = builtInName) - } -} - /** * A handler that handles errors. */ @@ -287,7 +283,7 @@ class UserDefinedApplicationService extends TUserDefinedApplicationService { } private class CdsApplicationService extends API::Node { - CdsApplicationService() { exists(CdsFacade c | this = c.getMember("CdsApplicationService")) } + CdsApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } } /** diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll index 76f1ab6b9..dc8ec72b4 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll @@ -13,7 +13,7 @@ import advanced_security.javascript.frameworks.cap.CDS */ /* TODO: narrow the definition down to exposed events */ class HandlerParameter extends ParameterNode, RemoteFlowSource { - HandlerParameter() { exists(EventHandler handler | this = handler.getParameter(0)) } + HandlerParameter() { exists(Handler handler | this = handler.getParameter(0)) } override string getSourceType() { result = "Parameter of an event handler" } } diff --git a/javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.ql b/javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.ql index 2b9e269f9..d57079fbe 100644 --- a/javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.ql +++ b/javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.ql @@ -1,5 +1,5 @@ import javascript -import advanced_security.javascript.frameworks.cap.CDS +import advanced_security.javascript.frameworks.cap.RemoteFlowSources -from CDS::RequestSource src +from HandlerParameter src select src \ No newline at end of file From 4d82d20890ab4e74358074f0f987650b70d5abc8 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 16 Feb 2024 18:48:14 -0800 Subject: [PATCH 19/86] Fix unit test `await` was unexpected in this context. --- .../cap/test/queries/loginjection/sample-app/srv/service1.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js index dc94467f2..ee088f24c 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js +++ b/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js @@ -5,7 +5,7 @@ module.exports = class Service1 extends cds.ApplicationService { init() { this.on("READ", "Entity1/Attribute1", (req) => { const { messageToPass } = req.data; - await this.emit("Received1", { messageToPass }); + this.emit("Received1", { messageToPass }); }); } } From b816eaf3151bfc096c67983ebd71031c58fd3cd5 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 16 Feb 2024 19:11:24 -0800 Subject: [PATCH 20/86] Fix `applicationserviceinstance` unit test --- .../javascript/frameworks/cap/CDS.qll | 39 ++++--------------- .../applicationserviceinstance.ql | 2 +- 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 1ddd6cb2c..0031929f6 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -35,19 +35,7 @@ abstract class ServiceInstance extends DataFlow::Node { } // Use `DataFlow::Node * ``` */ class ServiceInstanceFromCdsServe extends ServiceInstance { - ServiceInstanceFromCdsServe() { - exists(AwaitExpr await, CdsServeCall cdsServe | - /* 1. Obtained using `cds.Serve` */ - ( - /* 1-1. Destructuring definition */ - this.asExpr().getFirstControlFlowNode().(VarDef).getDestructuringSource() = await - or - /* 1-2. Direct definition */ - this.getALocalSource().asExpr() = await - ) and - await.getOperand().flow() = cdsServe - ) - } + ServiceInstanceFromCdsServe() { exists(CdsFacade cds | this = cds.getMember("serve").getACall()) } } /** @@ -56,13 +44,16 @@ class ServiceInstanceFromCdsServe extends ServiceInstance { * ```javascript * // Obtained through `cds.connect.to` * const Service1 = await cds.connect.to("service-1"); + * const Service1 = cds.connect.to("service-2"); * ``` */ -class ServiceInstanceFromCdsConnectTo extends ServiceInstance { +class ServiceInstanceFromCdsConnectTo extends MethodCallNode, ServiceInstance { + string serviceName; + ServiceInstanceFromCdsConnectTo() { - exists(AwaitExpr await, CdsConnectTo cdsConnectTo | - this.getALocalSource().asExpr() = await and - await.getOperand().flow() = cdsConnectTo + exists(CdsFacade cds | + this = cds.getMember("connect").getMember("to").getACall() and + serviceName = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() ) } } @@ -109,20 +100,6 @@ class ServiceInstanceFromServeWithParameter extends ParameterNode, ServiceInstan } } -/** - * A Call to `cds.connect.to` that returns a promise containing the service that is asked for by its name. - */ -private class CdsConnectTo extends MethodCallNode { - string serviceName; - - CdsConnectTo() { - exists(CdsFacade cds | - this = cds.getMember("connect").getMember("to").getACall() and - serviceName = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() - ) - } -} - /** * A call to `before`, `on`, or `after` on an `cds.ApplicationService`. * It registers an handler to be executed when an event is fired, diff --git a/javascript/frameworks/cap/test/models/cds/applicationserviceinstance/applicationserviceinstance.ql b/javascript/frameworks/cap/test/models/cds/applicationserviceinstance/applicationserviceinstance.ql index ae7c7152f..697334742 100644 --- a/javascript/frameworks/cap/test/models/cds/applicationserviceinstance/applicationserviceinstance.ql +++ b/javascript/frameworks/cap/test/models/cds/applicationserviceinstance/applicationserviceinstance.ql @@ -1,5 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CDS -from CDS::ServiceInstance svc +from ServiceInstance svc select svc \ No newline at end of file From 914e8253872c57deedfc6f3b9a161caf064e853e Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Sat, 17 Feb 2024 01:34:59 -0800 Subject: [PATCH 21/86] Create CAPLogInjection class to accommodate related classes --- .../frameworks/cap/CAPLogInjection.qll | 31 +++++++++++++++++++ .../javascript/frameworks/cap/CDS.qll | 27 ---------------- 2 files changed, 31 insertions(+), 27 deletions(-) create mode 100644 javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll new file mode 100644 index 000000000..b581a0479 --- /dev/null +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll @@ -0,0 +1,31 @@ +import javascript +import DataFlow +import advanced_security.javascript.frameworks.cap.CDS + +/** + * A logger obtained by a call to `log` on a CDS facade. Each logger is associated with + * its unique name. + */ +class CdsLogger extends MethodCallNode { + string name; + + CdsLogger() { + this = any(CdsFacade cds).getMember("log").getACall() and + name = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + } +} + +/** + * Arguments of calls to `cds.log.{trace, debug, info, log, warn, error}` + */ +class CdsLogSink extends DataFlow::Node { + CdsLogSink() { + exists(CdsLogger log, MethodCallNode loggingMethod | + this = loggingMethod.getAnArgument() and + not this.asExpr() instanceof Literal and + not this.asExpr() instanceof TemplateLiteral and + loggingMethod.getReceiver().getALocalSource() = log and + loggingMethod.getMethodName() = ["trace", "debug", "info", "log", "warn", "error"] + ) + } +} diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 0031929f6..db3322577 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -276,30 +276,3 @@ class ParseSink extends DataFlow::Node { .getAnArgument() } } - -/** - * A logger obtained by a call to `log` on a CDS facade. Each logger is associated with - * its unique name. - */ -class CdsLogger extends MethodCallNode { - string name; - - CdsLogger() { - this = any(CdsFacade cds).getMember("log").getACall() and - name = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() - } -} - -/** - * Arguments of calls to `cds.log.{trace, debug, info, log, warn, error}` - */ -class CdsLogSink extends DataFlow::Node { - CdsLogSink() { - exists(CdsLogger log, MethodCallNode loggingMethod | - this = loggingMethod.getAnArgument() and - not this.asExpr() instanceof Literal and - loggingMethod.getReceiver().getALocalSource() = log and - loggingMethod.getMethodName() = ["trace", "debug", "info", "log", "warn", "error"] - ) - } -} From a4c0d209f8840a13c87dac2e3472a90756cd0247 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Sat, 17 Feb 2024 01:35:39 -0800 Subject: [PATCH 22/86] Fix `logger` unit test --- .../cap/test/models/cds/logger/logger.expected | 14 +++++++------- .../cap/test/models/cds/logger/logger.js | 17 +++++++++-------- .../cap/test/models/cds/logger/logger.ql | 4 ++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/javascript/frameworks/cap/test/models/cds/logger/logger.expected b/javascript/frameworks/cap/test/models/cds/logger/logger.expected index 4ec314c6b..6f2c1aa2c 100644 --- a/javascript/frameworks/cap/test/models/cds/logger/logger.expected +++ b/javascript/frameworks/cap/test/models/cds/logger/logger.expected @@ -1,7 +1,7 @@ -| logger.js:3:25:3:30 | `test` | -| logger.js:4:25:4:30 | `test` | -| logger.js:5:24:5:29 | `test` | -| logger.js:6:23:6:28 | `test` | -| logger.js:7:24:7:29 | `test` | -| logger.js:8:25:8:30 | `test` | -| logger.js:11:10:11:15 | "test" | +| logger.js:3:25:3:29 | code0 | +| logger.js:4:25:4:29 | code0 | +| logger.js:5:24:5:28 | code0 | +| logger.js:6:23:6:27 | code0 | +| logger.js:7:24:7:28 | code0 | +| logger.js:8:25:8:29 | code0 | +| logger.js:12:10:12:14 | code1 | diff --git a/javascript/frameworks/cap/test/models/cds/logger/logger.js b/javascript/frameworks/cap/test/models/cds/logger/logger.js index 09c968c20..d5c35ebe6 100644 --- a/javascript/frameworks/cap/test/models/cds/logger/logger.js +++ b/javascript/frameworks/cap/test/models/cds/logger/logger.js @@ -1,11 +1,12 @@ import cds from '@sap/cds' -cds.log('nodejs').trace(`test`); -cds.log('nodejs').debug(`test`); -cds.log('nodejs').info(`test`); -cds.log('nodejs').log(`test`); -cds.log('nodejs').warn(`test`); -cds.log('nodejs').error(`test`); +cds.log('nodejs').trace(code0); +cds.log('nodejs').debug(code0); +cds.log('nodejs').info(code0); +cds.log('nodejs').log(code0); +cds.log('nodejs').warn(code0); +cds.log('nodejs').error(code0); -const LOG = cds.log("nodejs"); -LOG.info("test"); \ No newline at end of file +const code0 = "some-name"; +const LOG = cds.log(code0); +LOG.info(code1); \ No newline at end of file diff --git a/javascript/frameworks/cap/test/models/cds/logger/logger.ql b/javascript/frameworks/cap/test/models/cds/logger/logger.ql index b53449469..1da737cc9 100644 --- a/javascript/frameworks/cap/test/models/cds/logger/logger.ql +++ b/javascript/frameworks/cap/test/models/cds/logger/logger.ql @@ -1,5 +1,5 @@ import javascript -import advanced_security.javascript.frameworks.cap.CDS +import advanced_security.javascript.frameworks.cap.CAPLogInjection -from CDS::CdsLogSink sink +from CdsLogSink sink select sink \ No newline at end of file From 624a91d2935a2be239db64d9d272d3883648d984 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 20 Feb 2024 15:08:11 -0800 Subject: [PATCH 23/86] Fix `loginjection`/`sqlinjection` unit tests --- .../cap/src/loginjection/LogInjection.ql | 6 +++++- .../cap/src/sqlinjection/SqlInjection.ql | 3 ++- .../queries/loginjection/loginjection.expected | 18 ++++++++++++++++++ .../sqlinjection/sample-app/srv/Service1.js | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/javascript/frameworks/cap/src/loginjection/LogInjection.ql b/javascript/frameworks/cap/src/loginjection/LogInjection.ql index fe4f2a593..c8761eed0 100644 --- a/javascript/frameworks/cap/src/loginjection/LogInjection.ql +++ b/javascript/frameworks/cap/src/loginjection/LogInjection.ql @@ -13,12 +13,16 @@ import javascript import DataFlow::PathGraph import semmle.javascript.security.dataflow.LogInjectionQuery +import advanced_security.javascript.frameworks.cap.RemoteFlowSources import advanced_security.javascript.frameworks.cap.CDS +import advanced_security.javascript.frameworks.cap.CAPLogInjection /** * A source of remote user controlled input. */ -class CapRemoteSource extends Source, RequestSource { } +class CapRemoteSource extends Source { + CapRemoteSource() { this instanceof RemoteFlowSource } +} /** * An argument to a logging mechanism. diff --git a/javascript/frameworks/cap/src/sqlinjection/SqlInjection.ql b/javascript/frameworks/cap/src/sqlinjection/SqlInjection.ql index 75fbbf95a..da92d7db6 100644 --- a/javascript/frameworks/cap/src/sqlinjection/SqlInjection.ql +++ b/javascript/frameworks/cap/src/sqlinjection/SqlInjection.ql @@ -15,12 +15,13 @@ import DataFlow::PathGraph import semmle.javascript.security.dataflow.SqlInjectionCustomizations::SqlInjection import advanced_security.javascript.frameworks.cap.CDS import advanced_security.javascript.frameworks.cap.CQL +import advanced_security.javascript.frameworks.cap.RemoteFlowSources class Configuration extends TaintTracking::Configuration { Configuration() { this = "CapSqlInjection" } override predicate isSource(DataFlow::Node source) { - source instanceof Source or source instanceof RequestSource + source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { diff --git a/javascript/frameworks/cap/test/queries/loginjection/loginjection.expected b/javascript/frameworks/cap/test/queries/loginjection/loginjection.expected index 85a8e0b18..6922f7f76 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/loginjection.expected +++ b/javascript/frameworks/cap/test/queries/loginjection/loginjection.expected @@ -9,6 +9,15 @@ nodes | loginjection.js:11:14:11:26 | "test" + book | | loginjection.js:11:14:11:26 | "test" + book | | loginjection.js:11:23:11:26 | book | +| sample-app/srv/service2.js:6:33:6:35 | msg | +| sample-app/srv/service2.js:6:33:6:35 | msg | +| sample-app/srv/service2.js:7:15:7:31 | { messageToPass } | +| sample-app/srv/service2.js:7:15:7:42 | messageToPass | +| sample-app/srv/service2.js:7:17:7:29 | messageToPass | +| sample-app/srv/service2.js:7:35:7:37 | msg | +| sample-app/srv/service2.js:7:35:7:42 | msg.data | +| sample-app/srv/service2.js:9:32:9:44 | messageToPass | +| sample-app/srv/service2.js:9:32:9:44 | messageToPass | edges | loginjection.js:7:33:7:35 | req | loginjection.js:8:29:8:31 | req | | loginjection.js:7:33:7:35 | req | loginjection.js:8:29:8:31 | req | @@ -19,5 +28,14 @@ edges | loginjection.js:8:29:8:36 | req.data | loginjection.js:8:11:8:25 | {book,quantity} | | loginjection.js:11:23:11:26 | book | loginjection.js:11:14:11:26 | "test" + book | | loginjection.js:11:23:11:26 | book | loginjection.js:11:14:11:26 | "test" + book | +| sample-app/srv/service2.js:6:33:6:35 | msg | sample-app/srv/service2.js:7:35:7:37 | msg | +| sample-app/srv/service2.js:6:33:6:35 | msg | sample-app/srv/service2.js:7:35:7:37 | msg | +| sample-app/srv/service2.js:7:15:7:31 | { messageToPass } | sample-app/srv/service2.js:7:17:7:29 | messageToPass | +| sample-app/srv/service2.js:7:15:7:42 | messageToPass | sample-app/srv/service2.js:9:32:9:44 | messageToPass | +| sample-app/srv/service2.js:7:15:7:42 | messageToPass | sample-app/srv/service2.js:9:32:9:44 | messageToPass | +| sample-app/srv/service2.js:7:17:7:29 | messageToPass | sample-app/srv/service2.js:7:15:7:42 | messageToPass | +| sample-app/srv/service2.js:7:35:7:37 | msg | sample-app/srv/service2.js:7:35:7:42 | msg.data | +| sample-app/srv/service2.js:7:35:7:42 | msg.data | sample-app/srv/service2.js:7:15:7:31 | { messageToPass } | #select | loginjection.js:11:14:11:26 | "test" + book | loginjection.js:7:33:7:35 | req | loginjection.js:11:14:11:26 | "test" + book | Log entry depends on a $@. | loginjection.js:7:33:7:35 | req | user-provided value | +| sample-app/srv/service2.js:9:32:9:44 | messageToPass | sample-app/srv/service2.js:6:33:6:35 | msg | sample-app/srv/service2.js:9:32:9:44 | messageToPass | Log entry depends on a $@. | sample-app/srv/service2.js:6:33:6:35 | msg | user-provided value | diff --git a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js index 4657c5198..f1d767bcc 100644 --- a/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js +++ b/javascript/frameworks/cap/test/queries/sqlinjection/sample-app/srv/Service1.js @@ -5,7 +5,7 @@ module.exports = class Service1 extends cds.ApplicationService { init() { this.on("READ", "Service1Entity/Attribute1", (req) => { const { messageToPass } = req.data; - await this.emit("Received1", { messageToPass }); + this.emit("Received1", { messageToPass }); }); } From 286980b23d0c4d01f99314027f8d7f27ec5bda80 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 20 Feb 2024 17:32:39 -0800 Subject: [PATCH 24/86] Rename app to something specific --- .../README.md | 0 .../db/schema.cds | 0 .../package.json | 0 .../server.js | 0 .../log-injection-without-protocol-none/show-me-the-cwd.sh | 3 +++ .../srv/service1.cds | 0 .../srv/service1.js | 0 .../srv/service2.cds | 0 .../srv/service2.js | 0 9 files changed, 3 insertions(+) rename javascript/frameworks/cap/test/queries/loginjection/{sample-app => log-injection-without-protocol-none}/README.md (100%) rename javascript/frameworks/cap/test/queries/loginjection/{sample-app => log-injection-without-protocol-none}/db/schema.cds (100%) rename javascript/frameworks/cap/test/queries/loginjection/{sample-app => log-injection-without-protocol-none}/package.json (100%) rename javascript/frameworks/cap/test/queries/loginjection/{sample-app => log-injection-without-protocol-none}/server.js (100%) create mode 100755 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/show-me-the-cwd.sh rename javascript/frameworks/cap/test/queries/loginjection/{sample-app => log-injection-without-protocol-none}/srv/service1.cds (100%) rename javascript/frameworks/cap/test/queries/loginjection/{sample-app => log-injection-without-protocol-none}/srv/service1.js (100%) rename javascript/frameworks/cap/test/queries/loginjection/{sample-app => log-injection-without-protocol-none}/srv/service2.cds (100%) rename javascript/frameworks/cap/test/queries/loginjection/{sample-app => log-injection-without-protocol-none}/srv/service2.js (100%) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/README.md similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/sample-app/README.md rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/README.md diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/db/schema.cds similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/sample-app/db/schema.cds rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/db/schema.cds diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/package.json similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/sample-app/package.json rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/package.json diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/server.js similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/sample-app/server.js rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/server.js diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/show-me-the-cwd.sh b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/show-me-the-cwd.sh new file mode 100755 index 000000000..dcaeba20d --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/show-me-the-cwd.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +(cd $CODEQL_EXTRACTOR_JAVASCRIPT_WIP_DATABASE && pwd && for cds_file in $(find . -type f \( -iname '*.cds' \) -print ); do cds compile $cds_file -2 json -o "$(dirname $cds_file)/$(basename $cds_file .cds).json"; done) diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.cds similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.cds rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.cds diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.js similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service1.js rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.js diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.cds similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.cds rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.cds diff --git a/javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/sample-app/srv/service2.js rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js From f747d22c382d1bcf2b465b651ceea4043b49c67e Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 21 Feb 2024 11:38:41 -0800 Subject: [PATCH 25/86] Move existing loginjection.js to a separate directory --- .../{ => log-injection-single-file}/loginjection.expected | 0 .../loginjection/{ => log-injection-single-file}/loginjection.js | 0 .../{ => log-injection-single-file}/loginjection.qlref | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename javascript/frameworks/cap/test/queries/loginjection/{ => log-injection-single-file}/loginjection.expected (100%) rename javascript/frameworks/cap/test/queries/loginjection/{ => log-injection-single-file}/loginjection.js (100%) rename javascript/frameworks/cap/test/queries/loginjection/{ => log-injection-single-file}/loginjection.qlref (100%) diff --git a/javascript/frameworks/cap/test/queries/loginjection/loginjection.expected b/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/loginjection.expected rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected diff --git a/javascript/frameworks/cap/test/queries/loginjection/loginjection.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.js similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/loginjection.js rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.js diff --git a/javascript/frameworks/cap/test/queries/loginjection/loginjection.qlref b/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.qlref similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/loginjection.qlref rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.qlref From 765608d3a1769893db2d28169d788a081c3564e5 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 21 Feb 2024 15:08:54 -0800 Subject: [PATCH 26/86] Add three more variants of `log-injection-with-partial-protocol-none` --- .../log-injection-not-depending-on-request/README.md | 5 +++++ .../db/schema.cds | 11 +++++++++++ .../package.json | 12 ++++++++++++ .../log-injection-not-depending-on-request/server.js | 11 +++++++++++ .../srv/service1.cds | 11 +++++++++++ .../srv/service1.js | 11 +++++++++++ .../srv/service2.cds | 6 ++++++ .../srv/service2.js | 11 +++++++++++ .../README.md | 5 +++++ .../db/schema.cds | 11 +++++++++++ .../package.json | 12 ++++++++++++ .../server.js | 11 +++++++++++ .../srv/service1.cds | 12 ++++++++++++ .../srv/service1.js | 11 +++++++++++ .../srv/service2.cds | 7 +++++++ .../srv/service2.js | 11 +++++++++++ .../README.md | 5 +++++ .../db/schema.cds | 11 +++++++++++ .../package.json | 12 ++++++++++++ .../server.js | 11 +++++++++++ .../srv/service1.cds | 12 ++++++++++++ .../srv/service1.js | 11 +++++++++++ .../srv/service2.cds | 6 ++++++ .../srv/service2.js | 11 +++++++++++ 24 files changed, 237 insertions(+) create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/README.md create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/db/schema.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/package.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/server.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/README.md create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/db/schema.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/package.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/server.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/db/schema.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/README.md new file mode 100644 index 000000000..0ae0ca457 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/README.md @@ -0,0 +1,5 @@ +# Log Injection PoC + +This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. + +The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/db/schema.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/db/schema.cds new file mode 100644 index 000000000..1d4202fb1 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/db/schema.cds @@ -0,0 +1,11 @@ +namespace advanced_security.log_injection.sample_entities; + +entity Entity1 { + Attribute1 : String(100); + Attribute2 : String(100) +} + +entity Entity2 { + Attribute3 : String(100); + Attribute4 : String(100) +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/package.json new file mode 100644 index 000000000..feaa60a89 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/package.json @@ -0,0 +1,12 @@ +{ + "name": "@advanced-security/log-injection", + "version": "1.0.0", + "dependencies": { + "@sap/cds": "^7", + "express": "^4.17.1" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/server.js new file mode 100644 index 000000000..2f0beac60 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/server.js @@ -0,0 +1,11 @@ +const cds = require('@sap/cds'); +const Service1 = await cds.connect.to("Service1"); + +cds.once('bootstrap', (app) => { + app.serve("/log-injection").from("@advanced-security/log-injection"); +}); + +Service1.on("Received1", async (msg) => { + const { messageToPass } = msg.data; + await Service2.send("Received2", { messageToPass }); +}); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.cds new file mode 100644 index 000000000..d8477454b --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.cds @@ -0,0 +1,11 @@ +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + +service Service1 { + /* Entity to send READ about. */ + entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } + + /* Async API for Service1 to speak through. */ + event Received1: { + messageToPass : String; + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js new file mode 100644 index 000000000..2fe02c9e3 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js @@ -0,0 +1,11 @@ +const cds = require("@sap/cds"); + +/* Emit a "Received1" event upon receiving a READ request on its entity. */ +module.exports = class Service1 extends cds.ApplicationService { + init() { + this.on("READ", "Entity1/Attribute1", (req) => { + let datetime = new Date(); + this.emit("Received1", { datetime.toString() }); + }); + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds new file mode 100644 index 000000000..3cfae1f0f --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds @@ -0,0 +1,6 @@ +service Service2 { + /* Async API to talk to Service2. */ + event Received2: { + messageToPass: String; + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js new file mode 100644 index 000000000..bdc33f1d0 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js @@ -0,0 +1,11 @@ +const cds = require("@sap/cds"); +const LOG = cds.log("logger"); + +module.exports = cds.service.impl(() => { + /* Log upon receiving an "Received2" event. */ + this.on("Received2", async (msg) => { + const { messageToPass } = msg.data; + /* A log injection sink. */ + LOG.info("Received: ", messageToPass); + }); +}) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/README.md new file mode 100644 index 000000000..0ae0ca457 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/README.md @@ -0,0 +1,5 @@ +# Log Injection PoC + +This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. + +The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/db/schema.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/db/schema.cds new file mode 100644 index 000000000..1d4202fb1 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/db/schema.cds @@ -0,0 +1,11 @@ +namespace advanced_security.log_injection.sample_entities; + +entity Entity1 { + Attribute1 : String(100); + Attribute2 : String(100) +} + +entity Entity2 { + Attribute3 : String(100); + Attribute4 : String(100) +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/package.json new file mode 100644 index 000000000..feaa60a89 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/package.json @@ -0,0 +1,12 @@ +{ + "name": "@advanced-security/log-injection", + "version": "1.0.0", + "dependencies": { + "@sap/cds": "^7", + "express": "^4.17.1" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/server.js new file mode 100644 index 000000000..2f0beac60 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/server.js @@ -0,0 +1,11 @@ +const cds = require('@sap/cds'); +const Service1 = await cds.connect.to("Service1"); + +cds.once('bootstrap', (app) => { + app.serve("/log-injection").from("@advanced-security/log-injection"); +}); + +Service1.on("Received1", async (msg) => { + const { messageToPass } = msg.data; + await Service2.send("Received2", { messageToPass }); +}); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.cds new file mode 100644 index 000000000..b236d4735 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.cds @@ -0,0 +1,12 @@ +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + +@protocol: 'none' +service Service1 { + /* Entity to send READ about. */ + entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } + + /* Async API for Service1 to speak through. */ + event Received1: { + messageToPass : String; + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.js new file mode 100644 index 000000000..ee088f24c --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.js @@ -0,0 +1,11 @@ +const cds = require("@sap/cds"); + +/* Emit a "Received1" event upon receiving a READ request on its entity. */ +module.exports = class Service1 extends cds.ApplicationService { + init() { + this.on("READ", "Entity1/Attribute1", (req) => { + const { messageToPass } = req.data; + this.emit("Received1", { messageToPass }); + }); + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.cds new file mode 100644 index 000000000..fe85df509 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.cds @@ -0,0 +1,7 @@ +@protocol: 'none' +service Service2 { + /* Async API to talk to Service2. */ + event Received2: { + messageToPass: String; + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js new file mode 100644 index 000000000..bdc33f1d0 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js @@ -0,0 +1,11 @@ +const cds = require("@sap/cds"); +const LOG = cds.log("logger"); + +module.exports = cds.service.impl(() => { + /* Log upon receiving an "Received2" event. */ + this.on("Received2", async (msg) => { + const { messageToPass } = msg.data; + /* A log injection sink. */ + LOG.info("Received: ", messageToPass); + }); +}) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md new file mode 100644 index 000000000..0ae0ca457 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md @@ -0,0 +1,5 @@ +# Log Injection PoC + +This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. + +The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/db/schema.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/db/schema.cds new file mode 100644 index 000000000..1d4202fb1 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/db/schema.cds @@ -0,0 +1,11 @@ +namespace advanced_security.log_injection.sample_entities; + +entity Entity1 { + Attribute1 : String(100); + Attribute2 : String(100) +} + +entity Entity2 { + Attribute3 : String(100); + Attribute4 : String(100) +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json new file mode 100644 index 000000000..feaa60a89 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json @@ -0,0 +1,12 @@ +{ + "name": "@advanced-security/log-injection", + "version": "1.0.0", + "dependencies": { + "@sap/cds": "^7", + "express": "^4.17.1" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js new file mode 100644 index 000000000..2f0beac60 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js @@ -0,0 +1,11 @@ +const cds = require('@sap/cds'); +const Service1 = await cds.connect.to("Service1"); + +cds.once('bootstrap', (app) => { + app.serve("/log-injection").from("@advanced-security/log-injection"); +}); + +Service1.on("Received1", async (msg) => { + const { messageToPass } = msg.data; + await Service2.send("Received2", { messageToPass }); +}); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds new file mode 100644 index 000000000..b236d4735 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds @@ -0,0 +1,12 @@ +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + +@protocol: 'none' +service Service1 { + /* Entity to send READ about. */ + entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } + + /* Async API for Service1 to speak through. */ + event Received1: { + messageToPass : String; + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js new file mode 100644 index 000000000..ee088f24c --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js @@ -0,0 +1,11 @@ +const cds = require("@sap/cds"); + +/* Emit a "Received1" event upon receiving a READ request on its entity. */ +module.exports = class Service1 extends cds.ApplicationService { + init() { + this.on("READ", "Entity1/Attribute1", (req) => { + const { messageToPass } = req.data; + this.emit("Received1", { messageToPass }); + }); + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds new file mode 100644 index 000000000..3cfae1f0f --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds @@ -0,0 +1,6 @@ +service Service2 { + /* Async API to talk to Service2. */ + event Received2: { + messageToPass: String; + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js new file mode 100644 index 000000000..bdc33f1d0 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js @@ -0,0 +1,11 @@ +const cds = require("@sap/cds"); +const LOG = cds.log("logger"); + +module.exports = cds.service.impl(() => { + /* Log upon receiving an "Received2" event. */ + this.on("Received2", async (msg) => { + const { messageToPass } = msg.data; + /* A log injection sink. */ + LOG.info("Received: ", messageToPass); + }); +}) From 67357f6d5fdcf2dd29c73d3d5728989acc0955bd Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 21 Feb 2024 17:09:51 -0800 Subject: [PATCH 27/86] Add description to each README and add missing annotation ...to `log-injection-not-depending-on-request` --- .../log-injection-not-depending-on-request/README.md | 4 ++++ .../log-injection-not-depending-on-request/srv/service2.cds | 1 + .../log-injection-with-complete-protocol-none/README.md | 4 ++++ .../log-injection-with-partial-protocol-none/README.md | 4 ++++ .../log-injection-without-protocol-none/README.md | 4 ++++ 5 files changed, 17 insertions(+) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/README.md index 0ae0ca457..f35fac028 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/README.md +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/README.md @@ -3,3 +3,7 @@ This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. + +## It _is not_ a true positive case + +Service1 does not emit an event with data dependent on the request parameter, and Service2 is marked as internal, so its request parameter cannot be controlled from the outside. diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds index 3cfae1f0f..b2821b0d0 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds @@ -1,3 +1,4 @@ +@protocol: 'None' service Service2 { /* Async API to talk to Service2. */ event Received2: { diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/README.md index 0ae0ca457..37fead206 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/README.md +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/README.md @@ -3,3 +3,7 @@ This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. + +## It _is not_ a true positive case + +Both services are marked as internal with the annotation `@protocol:'None'`, so all request handler parameters are not controlled from the outside. diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md index 0ae0ca457..c2670ebe6 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md @@ -3,3 +3,7 @@ This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. + +## It _is_ a true positive case + +Service2 is still not marked as internal, so if `Received2` event is fired from the outside with user-controlled data, Service2 can still trigger log injection. diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/README.md index 0ae0ca457..99c677e92 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/README.md +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/README.md @@ -3,3 +3,7 @@ This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. + +## It _is_ a true positive case + +Service1 receives user-controlled data with the event and emits Received1 event, upon which Service2 responds to a Received2 event and logs the data. From 69c1bf7b51700a225bd0d177ef0a68eeb3278e2b Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 22 Feb 2024 13:06:14 -0800 Subject: [PATCH 28/86] Create `dataflow/` and move `ParseSink` there --- .../javascript/frameworks/cap/CDS.qll | 14 ------------- .../frameworks/cap/dataflow/DataFlow.qll | 20 +++++++++++++++++++ .../frameworks/cap/dataflow/FlowSteps.qll | 3 +++ 3 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll create mode 100644 javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index db3322577..2cc450e75 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -262,17 +262,3 @@ class UserDefinedApplicationService extends TUserDefinedApplicationService { private class CdsApplicationService extends API::Node { CdsApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } } - -/** - * Methods that parse source strings into a CQL expression. - */ -class ParseSink extends DataFlow::Node { - ParseSink() { - this = - any(CdsFacade cds) - .getMember("parse") - .getMember(["expr", "ref", "xpr"]) - .getACall() - .getAnArgument() - } -} diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll new file mode 100644 index 000000000..ed68bcf2b --- /dev/null +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll @@ -0,0 +1,20 @@ +/** + * `DataFlow::Node`s common to all security queries. + */ + +import javascript +import advanced_security.javascript.frameworks.cap.CDS + +/** + * Methods that parse source strings into a CQL expression. + */ +class ParseSink extends DataFlow::Node { + ParseSink() { + this = + any(CdsFacade cds) + .getMember("parse") + .getMember(["expr", "ref", "xpr"]) + .getACall() + .getAnArgument() + } + } \ No newline at end of file diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll new file mode 100644 index 000000000..6ac4278cf --- /dev/null +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll @@ -0,0 +1,3 @@ +/** + * Additional flow steps to be registered to `DataFlow::SharedFlowStep`. + */ \ No newline at end of file From 1e63e5d0f930577fbc028023389639a527ed3fbd Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 22 Feb 2024 17:14:05 -0800 Subject: [PATCH 29/86] Add `PackageJson.qll` --- .../javascript/frameworks/cap/PackageJson.qll | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll new file mode 100644 index 000000000..762c9f16e --- /dev/null +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll @@ -0,0 +1,52 @@ +import javascript + +/** + * The "cds" section of this application's `package.json`. + */ +class CdsManifest extends JsonObject { + CdsManifest() { exists(PackageJson parent | this = parent.getPropValue("cds")) } +} + +/** + * The "requires" section of the "cds" section. + */ +class RequiresSection extends JsonObject { + RequiresSection() { exists(CdsManifest cdsSection | this = cdsSection.getPropValue("requires")) } +} + +/** + * The service required by this application. It should provide a name + * by which `cds.connect().to(name)` looks up the target service. + */ +class RequiredService extends JsonObject { + string name; + string kind; + + RequiredService() { + exists(RequiresSection requires | + this = requires.getPropValue(name) and + kind = this.getPropStringValue("kind") + ) + } + + string getName() { result = name } + + string getKind() { result = kind } + + /** + * Holds if this is a declaration of a remote service. All possible kinds of remote services can + * be found in [this part of CAPire](https://cap.cloud.sap/docs/guides/using-services#import-api). + */ + predicate isRemote() { kind = ["odata", "odata-v4", "odata-v2", "rest", "sql", "sqlite"] } + + /** + * Holds if this is a declaration of a local service, which must provide an + * [implementation](https://cap.cloud.sap/docs/node.js/cds-connect#cds-requires-srv-impl) as its property. + */ + predicate isLocal() { exists(string path | path = this.getPropStringValue("impl")) } + + /** + * Holds if this is a declaration of a database service, which is considered remote. + */ + predicate isDatabase() { kind = ["sql", "sqlite"] } +} From 19912c89c328fd0c4d599c2ad4f972c05b86433e Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 22 Feb 2024 18:30:59 -0800 Subject: [PATCH 30/86] Add more classes --- .../javascript/frameworks/cap/CDS.qll | 27 +++++- .../javascript/frameworks/cap/PackageJson.qll | 18 ++-- .../frameworks/cap/dataflow/DataFlow.qll | 90 ++++++++++++++++--- 3 files changed, 113 insertions(+), 22 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 2cc450e75..0e60e1448 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -1,6 +1,7 @@ import javascript import DataFlow import advanced_security.javascript.frameworks.cap.CDL +import advanced_security.javascript.frameworks.cap.PackageJson /** * ```js @@ -23,7 +24,9 @@ class CdsServeCall extends MethodCallNode { /** * A dataflow node that represents a service. */ -abstract class ServiceInstance extends DataFlow::Node { } // Use `DataFlow::Node` to be the most general. +abstract class ServiceInstance extends DataFlow::Node { + abstract UserDefinedApplicationService getDefinition(); +} /** * A service instance obtained by the service's name, via serving @@ -36,6 +39,10 @@ abstract class ServiceInstance extends DataFlow::Node { } // Use `DataFlow::Node */ class ServiceInstanceFromCdsServe extends ServiceInstance { ServiceInstanceFromCdsServe() { exists(CdsFacade cds | this = cds.getMember("serve").getACall()) } + + override UserDefinedApplicationService getDefinition() { + none() // TODO: how should we deal with serve("all")? + } } /** @@ -56,6 +63,14 @@ class ServiceInstanceFromCdsConnectTo extends MethodCallNode, ServiceInstance { serviceName = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() ) } + + override UserDefinedApplicationService getDefinition() { + exists(RequiredService serviceDecl, string abspath | + serviceDecl.getName() = serviceName and + abspath = serviceDecl.getImplementationFile().getAbsolutePath() and + result.hasLocationInfo(abspath, _, _, _, _) + ) + } } /** @@ -69,6 +84,8 @@ class ServiceInstanceFromCdsConnectTo extends MethodCallNode, ServiceInstance { */ class ServiceInstanceFromConstructor extends ServiceInstance { ServiceInstanceFromConstructor() { this = any(CdsApplicationService cds).getAnInstantiation() } + + override UserDefinedApplicationService getDefinition() { none() } } /** @@ -78,6 +95,10 @@ class ServiceInstanceFromThisNode extends ServiceInstance { ServiceInstanceFromThisNode() { exists(ThisNode thisNode | thisNode.flowsTo(this) and this != thisNode) } + + override UserDefinedApplicationService getDefinition() { + result.getInitFunction().asExpr() = this.asExpr().getEnclosingFunction() + } } /** @@ -98,6 +119,10 @@ class ServiceInstanceFromServeWithParameter extends ParameterNode, ServiceInstan this = withCall.getArgument(0).(FunctionNode).getParameter(0) ) } + + override UserDefinedApplicationService getDefinition() { + none() // TODO + } } /** diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll index 762c9f16e..c6ab49252 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll @@ -20,24 +20,18 @@ class RequiresSection extends JsonObject { */ class RequiredService extends JsonObject { string name; - string kind; - RequiredService() { - exists(RequiresSection requires | - this = requires.getPropValue(name) and - kind = this.getPropStringValue("kind") - ) - } + RequiredService() { exists(RequiresSection requires | this = requires.getPropValue(name)) } string getName() { result = name } - string getKind() { result = kind } - /** * Holds if this is a declaration of a remote service. All possible kinds of remote services can * be found in [this part of CAPire](https://cap.cloud.sap/docs/guides/using-services#import-api). */ - predicate isRemote() { kind = ["odata", "odata-v4", "odata-v2", "rest", "sql", "sqlite"] } + predicate isRemote() { + this.getPropStringValue("kind") = ["odata", "odata-v4", "odata-v2", "rest", "sql", "sqlite"] + } /** * Holds if this is a declaration of a local service, which must provide an @@ -45,8 +39,10 @@ class RequiredService extends JsonObject { */ predicate isLocal() { exists(string path | path = this.getPropStringValue("impl")) } + File getImplementationFile() { "./" + result.getRelativePath() = this.getPropStringValue("impl") } + /** * Holds if this is a declaration of a database service, which is considered remote. */ - predicate isDatabase() { kind = ["sql", "sqlite"] } + predicate isDatabase() { this.getPropStringValue("kind") = ["sql", "sqlite"] } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll index ed68bcf2b..0592c66d6 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll @@ -1,5 +1,5 @@ /** - * `DataFlow::Node`s common to all security queries. + * Security-related `DataFlow::Node`s or relations between two `DataFlow::Node`s. */ import javascript @@ -9,12 +9,82 @@ import advanced_security.javascript.frameworks.cap.CDS * Methods that parse source strings into a CQL expression. */ class ParseSink extends DataFlow::Node { - ParseSink() { - this = - any(CdsFacade cds) - .getMember("parse") - .getMember(["expr", "ref", "xpr"]) - .getACall() - .getAnArgument() - } - } \ No newline at end of file + ParseSink() { + exists(CdsFacade cds | + this = cds.getMember("parse").getMember(["expr", "ref", "xpr"]).getACall().getAnArgument() + ) + } +} + +class SrvRun extends MethodCallNode { + SrvRun() { + exists(ServiceInstance srv | + srv = this.getReceiver() and + this.getMethodName() = "run" + ) + } +} + +class SrvEmit extends MethodCallNode { + SrvEmit() { + exists(ServiceInstance srv | + srv = this.getReceiver() and + this.getMethodName() = "emit" + ) + } +} + +class SrvSend extends MethodCallNode { + SrvSend() { + exists(ServiceInstance srv | + srv = this.getReceiver() and + this.getMethodName() = "send" + ) + } +} + +/** + * A communication happening between `cds.Service`s. This includes: + * 1. Ones based on REST-style API, based on `cds.Service.send`, + * 2. Ones based on query-style API, based on `cds.Services.run`, and + * 3. Ones based on emitting and subscribing to asynchronous event messages. + */ +abstract class InterServiceCommunication extends MethodCallNode { + /* TODO: Generalize UserApplicationService to include built-in services such as log and db */ + /** + * The service that sends the request. + */ + UserDefinedApplicationService sender; + /** + * The service that receives the request and handles it. + */ + UserDefinedApplicationService recipient; + /** + * The handler registration that set ups the communication. + */ + HandlerRegistration registration; +} + +class RestStyleCommunication extends InterServiceCommunication { + override UserDefinedApplicationService sender; + override UserDefinedApplicationService recipient; + override HandlerRegistration registration; + + RestStyleCommunication() { + registration = slfkjsd and + sender = registration.getReceiver().(ServiceInstance).getDefinition() and + recipient = sdlkfjdskf + } +} + +class CrudStyleCommunication extends InterServiceCommunication { + override UserDefinedApplicationService sender; + override UserDefinedApplicationService recipient; + override HandlerRegistration registration; +} + +class AsyncStyleCommunication extends InterServiceCommunication { + override UserDefinedApplicationService sender; + override UserDefinedApplicationService recipient; + override HandlerRegistration registration; +} From 3181a5dc7c2393cd69a901d2303ff4e65f92a022 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 23 Feb 2024 16:08:27 -0800 Subject: [PATCH 31/86] Add more classes and predicates --- .../javascript/frameworks/cap/CDS.qll | 122 ++++++++++++++++-- .../frameworks/cap/dataflow/DataFlow.qll | 78 +++++------ 2 files changed, 148 insertions(+), 52 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 0e60e1448..f7621dbd8 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -1,7 +1,8 @@ import javascript import DataFlow -import advanced_security.javascript.frameworks.cap.CDL import advanced_security.javascript.frameworks.cap.PackageJson +import advanced_security.javascript.frameworks.cap.CDL +import advanced_security.javascript.frameworks.cap.CQL /** * ```js @@ -17,8 +18,8 @@ class CdsFacade extends API::Node { /** * A call to `serve` on a CDS facade. */ -class CdsServeCall extends MethodCallNode { - CdsServeCall() { exists(CdsFacade cds | this = cds.getMember("serve").getACall()) } +class CdsServeCall extends API::Node { + CdsServeCall() { exists(CdsFacade cds | this = cds.getMember("serve")) } } /** @@ -26,6 +27,8 @@ class CdsServeCall extends MethodCallNode { */ abstract class ServiceInstance extends DataFlow::Node { abstract UserDefinedApplicationService getDefinition(); + + abstract MethodCallNode getASrvMethodCall(); } /** @@ -43,6 +46,10 @@ class ServiceInstanceFromCdsServe extends ServiceInstance { override UserDefinedApplicationService getDefinition() { none() // TODO: how should we deal with serve("all")? } + + override MethodCallNode getASrvMethodCall() { + none() // TODO + } } /** @@ -54,13 +61,22 @@ class ServiceInstanceFromCdsServe extends ServiceInstance { * const Service1 = cds.connect.to("service-2"); * ``` */ -class ServiceInstanceFromCdsConnectTo extends MethodCallNode, ServiceInstance { +class ServiceInstanceFromCdsConnectTo extends ServiceInstance { string serviceName; ServiceInstanceFromCdsConnectTo() { exists(CdsFacade cds | this = cds.getMember("connect").getMember("to").getACall() and - serviceName = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + serviceName = + this.(MethodCallNode).getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + ) + or + exists(AwaitExpr await, CdsFacade cds, MethodCallNode awaitOperand | + this = await.flow() and + await.getOperand().flow() = awaitOperand and + awaitOperand = cds.getMember("connect").getMember("to").getACall() and + serviceName = + awaitOperand.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() ) } @@ -71,6 +87,18 @@ class ServiceInstanceFromCdsConnectTo extends MethodCallNode, ServiceInstance { result.hasLocationInfo(abspath, _, _, _, _) ) } + + string getServiceName() { result = serviceName } + + /** + * Gets a method call on this service instance. + */ + override MethodCallNode getASrvMethodCall() { + exists(VarDef definition | + definition.getSource().flow() = this and + definition.getAVariable().getAnAccess() = result.getReceiver().asExpr() + ) + } } /** @@ -86,6 +114,16 @@ class ServiceInstanceFromConstructor extends ServiceInstance { ServiceInstanceFromConstructor() { this = any(CdsApplicationService cds).getAnInstantiation() } override UserDefinedApplicationService getDefinition() { none() } + + /** + * Gets a method call on this service instance. + */ + override MethodCallNode getASrvMethodCall() { + exists(VarDef definition | + definition.getSource().flow() = this and + definition.getAVariable().getAnAccess() = result.getReceiver().asExpr() + ) + } } /** @@ -97,8 +135,13 @@ class ServiceInstanceFromThisNode extends ServiceInstance { } override UserDefinedApplicationService getDefinition() { - result.getInitFunction().asExpr() = this.asExpr().getEnclosingFunction() + result.getInitFunction().asExpr() = this.asExpr().getEnclosingFunction+() } + + /** + * Gets a method call on this service instance. + */ + override MethodCallNode getASrvMethodCall() { result.getReceiver() = this } } /** @@ -115,7 +158,7 @@ class ServiceInstanceFromServeWithParameter extends ParameterNode, ServiceInstan ServiceInstanceFromServeWithParameter() { exists(MethodCallNode withCall, CdsServeCall cdsServe | withCall.getMethodName() = "with" and - withCall.getReceiver() = cdsServe and + withCall.getReceiver() = cdsServe.getACall() and this = withCall.getArgument(0).(FunctionNode).getParameter(0) ) } @@ -123,6 +166,10 @@ class ServiceInstanceFromServeWithParameter extends ParameterNode, ServiceInstan override UserDefinedApplicationService getDefinition() { none() // TODO } + + override MethodCallNode getASrvMethodCall() { + none() // TODO + } } /** @@ -133,7 +180,11 @@ class ServiceInstanceFromServeWithParameter extends ParameterNode, ServiceInstan class HandlerRegistration extends MethodCallNode { HandlerRegistration() { exists(ServiceInstance srv | - (srv.(SourceNode).flowsTo(this.getReceiver()) or srv = this.getReceiver()) and + ( + srv.(SourceNode).flowsTo(this.getReceiver()) + or + srv = this.getReceiver() + ) and ( this.getMethodName() = "before" or this.getMethodName() = "on" or @@ -161,6 +212,11 @@ class HandlerRegistration extends MethodCallNode { * Get the name of the entity that the handler is registered for, if any. */ string getEntityName() { result = this.getArgument(1).asExpr().(StringLiteral).getValue() } + + /** + * Gets the handler that is being registrated to an event by this registering function call. + */ + Handler getHandler() { result = this.getAnArgument() } } /** @@ -282,8 +338,58 @@ class UserDefinedApplicationService extends TUserDefinedApplicationService { this.asClassDefinition().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) or this.asImplMethodCall().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } + + /** + * Gets the name of this service as declared in the ` package.json`. + */ + string getManifestName() { + exists(RequiredService serviceManifest | + this.hasLocationInfo(serviceManifest.getImplementationFile().getAbsolutePath(), _, _, _, _) and + result = serviceManifest.getName() + ) + } } private class CdsApplicationService extends API::Node { CdsApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } } + +abstract class InterServiceCommunicationMethodCall extends MethodCallNode { } + +class SrvRun extends InterServiceCommunicationMethodCall { + SrvRun() { + exists(ServiceInstance srv | + srv = this.getReceiver() and + this.getMethodName() = "run" + ) + } + + CqlClause getCql() { + result.asMethodCall() = this.getArgument(0).asExpr() or + result.asDotExpr() = this.getArgument(0).asExpr() + } +} + +class SrvEmit extends InterServiceCommunicationMethodCall { + ServiceInstance emittingService; + + SrvEmit() { + emittingService = this.getReceiver() and + this.getMethodName() = "emit" + } + + ServiceInstance getEmitter() { result = emittingService } + + string getEmittedEvent() { + result = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + } +} + +class SrvSend extends InterServiceCommunicationMethodCall { + SrvSend() { + exists(ServiceInstance srv | + srv = this.getReceiver() and + this.getMethodName() = "send" + ) + } +} diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll index 0592c66d6..bc13e4c35 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll @@ -16,33 +16,6 @@ class ParseSink extends DataFlow::Node { } } -class SrvRun extends MethodCallNode { - SrvRun() { - exists(ServiceInstance srv | - srv = this.getReceiver() and - this.getMethodName() = "run" - ) - } -} - -class SrvEmit extends MethodCallNode { - SrvEmit() { - exists(ServiceInstance srv | - srv = this.getReceiver() and - this.getMethodName() = "emit" - ) - } -} - -class SrvSend extends MethodCallNode { - SrvSend() { - exists(ServiceInstance srv | - srv = this.getReceiver() and - this.getMethodName() = "send" - ) - } -} - /** * A communication happening between `cds.Service`s. This includes: * 1. Ones based on REST-style API, based on `cds.Service.send`, @@ -50,6 +23,8 @@ class SrvSend extends MethodCallNode { * 3. Ones based on emitting and subscribing to asynchronous event messages. */ abstract class InterServiceCommunication extends MethodCallNode { + InterServiceCommunication() { this.getReceiver() instanceof ServiceInstance } + /* TODO: Generalize UserApplicationService to include built-in services such as log and db */ /** * The service that sends the request. @@ -59,32 +34,47 @@ abstract class InterServiceCommunication extends MethodCallNode { * The service that receives the request and handles it. */ UserDefinedApplicationService recipient; - /** - * The handler registration that set ups the communication. - */ - HandlerRegistration registration; } class RestStyleCommunication extends InterServiceCommunication { - override UserDefinedApplicationService sender; - override UserDefinedApplicationService recipient; - override HandlerRegistration registration; - RestStyleCommunication() { - registration = slfkjsd and - sender = registration.getReceiver().(ServiceInstance).getDefinition() and - recipient = sdlkfjdskf + exists(HandlerRegistration registration, SrvSend srvSend | + sender = registration.getReceiver().(ServiceInstance).getDefinition() and + recipient = srvSend.getReceiver().(ServiceInstance).getDefinition() + ) } } class CrudStyleCommunication extends InterServiceCommunication { - override UserDefinedApplicationService sender; - override UserDefinedApplicationService recipient; - override HandlerRegistration registration; + CrudStyleCommunication() { + exists(HandlerRegistration registration, SrvRun srvRun | + sender = registration.getReceiver().(ServiceInstance).getDefinition() and + recipient = srvRun.getReceiver().(ServiceInstance).getDefinition() + ) + } } class AsyncStyleCommunication extends InterServiceCommunication { - override UserDefinedApplicationService sender; - override UserDefinedApplicationService recipient; - override HandlerRegistration registration; + AsyncStyleCommunication() { + exists( + HandlerRegistration emittingRegistration, HandlerRegistration orchestratingRegistration, + SrvEmit srvEmit, InterServiceCommunicationMethodCall methodCallOnReceiver + | + emittingRegistration != orchestratingRegistration and + /* The service that emits the event and the service that registers the handler are the same; it's the sender. */ + this = orchestratingRegistration and + sender = emittingRegistration.getReceiver().(ServiceInstance).getDefinition() and + /* 1. match by their event name. */ + srvEmit.getEmittedEvent() = orchestratingRegistration.getAnEventName() and + /* 2. match by their service name in cds.connect().to(). */ + srvEmit.getEmitter().getDefinition().getManifestName() = + orchestratingRegistration + .getReceiver() + .getALocalSource() + .(ServiceInstanceFromCdsConnectTo) + .getServiceName() and + recipient = methodCallOnReceiver.getReceiver().(ServiceInstance).getDefinition() and + methodCallOnReceiver.getEnclosingFunction() = orchestratingRegistration.getHandler().asExpr() + ) + } } From 34be53082580d78844612c881a2dbd8237379eb9 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 23 Feb 2024 16:08:40 -0800 Subject: [PATCH 32/86] Debug `log-injection-with-partial-protocol-none` --- .../package.json | 10 ++++++++++ .../log-injection-with-partial-protocol-none/server.js | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json index feaa60a89..ed3999ed7 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json @@ -8,5 +8,15 @@ "scripts": { "start": "cds-serve", "watch": "cds watch" + }, + "cds": { + "requires": { + "service-1": { + "impl": "./srv/service1.js" + }, + "service-2": { + "impl": "./srv/service2.js" + } + } } } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js index 2f0beac60..e5bd708f0 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js @@ -1,5 +1,6 @@ const cds = require('@sap/cds'); -const Service1 = await cds.connect.to("Service1"); +const Service1 = await cds.connect.to("service-1"); +const Service2 = await cds.connect.to("service-2"); cds.once('bootstrap', (app) => { app.serve("/log-injection").from("@advanced-security/log-injection"); From 78b29a29c9136071a7b4636d57f6746db7c2d055 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 23 Feb 2024 21:47:03 -0800 Subject: [PATCH 33/86] Add `AsyncStyleCommunication` in `DataFlow.qll` --- .../javascript/frameworks/cap/CDS.qll | 73 ++++++++++--------- .../frameworks/cap/dataflow/DataFlow.qll | 11 +-- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index f7621dbd8..f4958a6df 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -23,7 +23,30 @@ class CdsServeCall extends API::Node { } /** - * A dataflow node that represents a service. + * A `cds.connect.to(serveName)` call to connect to a local or remote service. + */ +class CdsConnectToCall extends API::Node { + CdsConnectToCall() { exists(CdsFacade cds | this = cds.getMember("connect").getMember("to")) } + + /** + * Gets a variable definition using either `cds.connect.to(...)` or awaiting that at its RHS. + */ + VarDef getVarDefUsingCdsConnect() { + result.getSource() = this.getACall().asExpr() + or + result.getSource().(AwaitExpr).getOperand() = this.getACall().asExpr() + } + + /** + * Gets the service name that this call connects to. + */ + string getServiceName() { + result = this.getACall().getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + } +} + +/** + * A dataflow node that represents a service. Note that its definition is a `UserDefinedApplicationService`, not a `ServiceInstance`. */ abstract class ServiceInstance extends DataFlow::Node { abstract UserDefinedApplicationService getDefinition(); @@ -52,6 +75,7 @@ class ServiceInstanceFromCdsServe extends ServiceInstance { } } +/* TODO: change `ServiceInstanceFromCdsConnectTo` to an VarAccess to the VarDef whose getSource() is CdsConnectToCall or an AwaitExpr wrapping it */ /** * A service instance obtained by the service's name, via connecting * to a service already being served and awaiting its promise. e.g. @@ -64,19 +88,11 @@ class ServiceInstanceFromCdsServe extends ServiceInstance { class ServiceInstanceFromCdsConnectTo extends ServiceInstance { string serviceName; + /* TODO: change this to an VarAccess to the VarDef whose getSource() is CdsConnectToCall or an AwaitExpr wrapping it */ ServiceInstanceFromCdsConnectTo() { - exists(CdsFacade cds | - this = cds.getMember("connect").getMember("to").getACall() and - serviceName = - this.(MethodCallNode).getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() - ) - or - exists(AwaitExpr await, CdsFacade cds, MethodCallNode awaitOperand | - this = await.flow() and - await.getOperand().flow() = awaitOperand and - awaitOperand = cds.getMember("connect").getMember("to").getACall() and - serviceName = - awaitOperand.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + exists(CdsConnectToCall cdsConnectTo | + this = cdsConnectTo.getVarDefUsingCdsConnect().getAVariable().getAnAccess().flow() and + serviceName = cdsConnectTo.getServiceName() ) } @@ -93,12 +109,7 @@ class ServiceInstanceFromCdsConnectTo extends ServiceInstance { /** * Gets a method call on this service instance. */ - override MethodCallNode getASrvMethodCall() { - exists(VarDef definition | - definition.getSource().flow() = this and - definition.getAVariable().getAnAccess() = result.getReceiver().asExpr() - ) - } + override MethodCallNode getASrvMethodCall() { result.getReceiver() = this } } /** @@ -354,15 +365,14 @@ private class CdsApplicationService extends API::Node { CdsApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } } -abstract class InterServiceCommunicationMethodCall extends MethodCallNode { } +abstract class InterServiceCommunicationMethodCall extends MethodCallNode { + InterServiceCommunicationMethodCall() { + exists(ServiceInstance srv | this = srv.getASrvMethodCall()) + } +} class SrvRun extends InterServiceCommunicationMethodCall { - SrvRun() { - exists(ServiceInstance srv | - srv = this.getReceiver() and - this.getMethodName() = "run" - ) - } + SrvRun() { this.getMethodName() = "run" } CqlClause getCql() { result.asMethodCall() = this.getArgument(0).asExpr() or @@ -374,8 +384,8 @@ class SrvEmit extends InterServiceCommunicationMethodCall { ServiceInstance emittingService; SrvEmit() { - emittingService = this.getReceiver() and - this.getMethodName() = "emit" + this.getMethodName() = "emit" and + emittingService = this.getReceiver() } ServiceInstance getEmitter() { result = emittingService } @@ -386,10 +396,5 @@ class SrvEmit extends InterServiceCommunicationMethodCall { } class SrvSend extends InterServiceCommunicationMethodCall { - SrvSend() { - exists(ServiceInstance srv | - srv = this.getReceiver() and - this.getMethodName() = "send" - ) - } + SrvSend() { this.getMethodName() = "send" } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll index bc13e4c35..6494630db 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll @@ -34,6 +34,10 @@ abstract class InterServiceCommunication extends MethodCallNode { * The service that receives the request and handles it. */ UserDefinedApplicationService recipient; + + UserDefinedApplicationService getSender() { result = sender } + + UserDefinedApplicationService getReceipient() { result = recipient } } class RestStyleCommunication extends InterServiceCommunication { @@ -64,15 +68,12 @@ class AsyncStyleCommunication extends InterServiceCommunication { /* The service that emits the event and the service that registers the handler are the same; it's the sender. */ this = orchestratingRegistration and sender = emittingRegistration.getReceiver().(ServiceInstance).getDefinition() and + srvEmit.asExpr().getEnclosingFunction+() = sender.getInitFunction().asExpr() and /* 1. match by their event name. */ srvEmit.getEmittedEvent() = orchestratingRegistration.getAnEventName() and /* 2. match by their service name in cds.connect().to(). */ srvEmit.getEmitter().getDefinition().getManifestName() = - orchestratingRegistration - .getReceiver() - .getALocalSource() - .(ServiceInstanceFromCdsConnectTo) - .getServiceName() and + orchestratingRegistration.getReceiver().(ServiceInstanceFromCdsConnectTo).getServiceName() and recipient = methodCallOnReceiver.getReceiver().(ServiceInstance).getDefinition() and methodCallOnReceiver.getEnclosingFunction() = orchestratingRegistration.getHandler().asExpr() ) From c01f19eacdbfa342d5443a45efb29c54f04fa6be Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 28 Feb 2024 14:08:03 -0800 Subject: [PATCH 34/86] Update all log injection test cases --- .../package.json | 12 +++++- .../server.js | 11 +----- .../srv/service1.cds | 12 +++--- .../srv/service1.js | 4 +- .../srv/service2.cds | 16 +++++--- .../package.json | 31 ++++++++++----- .../server.js | 11 +----- .../srv/service1.cds | 12 +++--- .../srv/service1.js | 7 ++-- .../srv/service2.cds | 15 ++++--- .../srv/service2.js | 6 +-- .../package.json | 39 ++++++++++--------- .../server.js | 12 +----- .../srv/service1.cds | 12 +++--- .../srv/service1.js | 7 ++-- .../srv/service2.cds | 15 ++++--- .../srv/service2.js | 6 +-- .../package.json | 31 ++++++++++----- .../server.js | 11 +----- .../srv/service1.cds | 12 +++--- .../srv/service1.js | 7 ++-- .../srv/service2.cds | 15 ++++--- .../srv/service2.js | 6 +-- 23 files changed, 168 insertions(+), 142 deletions(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/package.json index feaa60a89..fba5217ac 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/package.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/package.json @@ -8,5 +8,15 @@ "scripts": { "start": "cds-serve", "watch": "cds watch" - } + }, + "cds": { + "requires": { + "service-1": { + "impl": "srv/service1.js" + }, + "service-2": { + "impl": "srv/service2.js" + } + } + } } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/server.js index 2f0beac60..dc2ac97ee 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/server.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/server.js @@ -1,11 +1,4 @@ const cds = require('@sap/cds'); -const Service1 = await cds.connect.to("Service1"); +const app = require('express')(); -cds.once('bootstrap', (app) => { - app.serve("/log-injection").from("@advanced-security/log-injection"); -}); - -Service1.on("Received1", async (msg) => { - const { messageToPass } = msg.data; - await Service2.send("Received2", { messageToPass }); -}); +cds.serve('all').in(app); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.cds index d8477454b..34b2e7776 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.cds @@ -1,11 +1,11 @@ using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; -service Service1 { - /* Entity to send READ about. */ +service Service1 @(path: '/service-1') { + /* Entity to send READ/GET about. */ entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } - /* Async API for Service1 to speak through. */ - event Received1: { - messageToPass : String; - } + /* API to talk to Service1. */ + action send1 ( + messageToPass : String + ) returns String; } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js index 2fe02c9e3..681b024b9 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js @@ -3,9 +3,9 @@ const cds = require("@sap/cds"); /* Emit a "Received1" event upon receiving a READ request on its entity. */ module.exports = class Service1 extends cds.ApplicationService { init() { - this.on("READ", "Entity1/Attribute1", (req) => { + this.on("send1", async (req) => { // req is not used at all let datetime = new Date(); - this.emit("Received1", { datetime.toString() }); + Service2.send("send2", { messageToPass: datetime.toString() }); }); } } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds index b2821b0d0..a5963d2ef 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds @@ -1,7 +1,11 @@ -@protocol: 'None' -service Service2 { - /* Async API to talk to Service2. */ - event Received2: { - messageToPass: String; - } +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + +service Service2 @(path: '/service-2') { + /* Entity to send READ/GET about. */ + entity Service2Entity as projection on db_schema.Entity2 excluding { Attribute4 } + + /* API to talk to Service2. */ + action send2 ( + messageToPass: String + ) returns String; } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/package.json index feaa60a89..6e66d859d 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/package.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/package.json @@ -1,12 +1,23 @@ { - "name": "@advanced-security/log-injection", - "version": "1.0.0", - "dependencies": { - "@sap/cds": "^7", - "express": "^4.17.1" - }, - "scripts": { - "start": "cds-serve", - "watch": "cds watch" - } + "name": "@advanced-security/log-injection", + "version": "1.0.0", + "dependencies": { + "@sap/cds": "^7", + "express": "^4.17.1", + "@cap-js/sqlite": "*" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + }, + "cds": { + "requires": { + "service-1": { + "impl": "srv/service1.js" + }, + "service-2": { + "impl": "srv/service2.js" + } + } + } } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/server.js index 2f0beac60..dc2ac97ee 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/server.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/server.js @@ -1,11 +1,4 @@ const cds = require('@sap/cds'); -const Service1 = await cds.connect.to("Service1"); +const app = require('express')(); -cds.once('bootstrap', (app) => { - app.serve("/log-injection").from("@advanced-security/log-injection"); -}); - -Service1.on("Received1", async (msg) => { - const { messageToPass } = msg.data; - await Service2.send("Received2", { messageToPass }); -}); +cds.serve('all').in(app); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.cds index b236d4735..66ceee1c1 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.cds @@ -1,12 +1,12 @@ using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; @protocol: 'none' -service Service1 { - /* Entity to send READ about. */ +service Service1 @(path: '/service-1') { + /* Entity to send READ/GET about. */ entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } - /* Async API for Service1 to speak through. */ - event Received1: { - messageToPass : String; - } + /* API to talk to Service1. */ + action send1 ( + messageToPass : String + ) returns String; } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.js index ee088f24c..88f05c5f8 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.js @@ -3,9 +3,10 @@ const cds = require("@sap/cds"); /* Emit a "Received1" event upon receiving a READ request on its entity. */ module.exports = class Service1 extends cds.ApplicationService { init() { - this.on("READ", "Entity1/Attribute1", (req) => { + this.on("send1", async (req) => { const { messageToPass } = req.data; - this.emit("Received1", { messageToPass }); + const Service2 = await cds.connect.to("service-2"); + Service2.send("send2", { messageToPass }); }); } -} +}; diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.cds index fe85df509..4d1938ff5 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.cds @@ -1,7 +1,12 @@ +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + @protocol: 'none' -service Service2 { - /* Async API to talk to Service2. */ - event Received2: { - messageToPass: String; - } +service Service2 @(path: '/service-2') { + /* Entity to send READ/GET about. */ + entity Service2Entity as projection on db_schema.Entity2 excluding { Attribute4 } + + /* API to talk to Service2. */ + action send2 ( + messageToPass: String + ) returns String; } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js index bdc33f1d0..3874498a8 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js @@ -1,9 +1,9 @@ const cds = require("@sap/cds"); const LOG = cds.log("logger"); -module.exports = cds.service.impl(() => { - /* Log upon receiving an "Received2" event. */ - this.on("Received2", async (msg) => { +module.exports = cds.service.impl(function() { + /* Log upon receiving an "send2" event. */ + this.on("send2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ LOG.info("Received: ", messageToPass); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json index ed3999ed7..6e66d859d 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json @@ -1,22 +1,23 @@ { - "name": "@advanced-security/log-injection", - "version": "1.0.0", - "dependencies": { - "@sap/cds": "^7", - "express": "^4.17.1" - }, - "scripts": { - "start": "cds-serve", - "watch": "cds watch" - }, - "cds": { - "requires": { - "service-1": { - "impl": "./srv/service1.js" - }, - "service-2": { - "impl": "./srv/service2.js" - } + "name": "@advanced-security/log-injection", + "version": "1.0.0", + "dependencies": { + "@sap/cds": "^7", + "express": "^4.17.1", + "@cap-js/sqlite": "*" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + }, + "cds": { + "requires": { + "service-1": { + "impl": "srv/service1.js" + }, + "service-2": { + "impl": "srv/service2.js" + } + } } - } } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js index e5bd708f0..dc2ac97ee 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js @@ -1,12 +1,4 @@ const cds = require('@sap/cds'); -const Service1 = await cds.connect.to("service-1"); -const Service2 = await cds.connect.to("service-2"); +const app = require('express')(); -cds.once('bootstrap', (app) => { - app.serve("/log-injection").from("@advanced-security/log-injection"); -}); - -Service1.on("Received1", async (msg) => { - const { messageToPass } = msg.data; - await Service2.send("Received2", { messageToPass }); -}); +cds.serve('all').in(app); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds index b236d4735..66ceee1c1 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds @@ -1,12 +1,12 @@ using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; @protocol: 'none' -service Service1 { - /* Entity to send READ about. */ +service Service1 @(path: '/service-1') { + /* Entity to send READ/GET about. */ entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } - /* Async API for Service1 to speak through. */ - event Received1: { - messageToPass : String; - } + /* API to talk to Service1. */ + action send1 ( + messageToPass : String + ) returns String; } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js index ee088f24c..88f05c5f8 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js @@ -3,9 +3,10 @@ const cds = require("@sap/cds"); /* Emit a "Received1" event upon receiving a READ request on its entity. */ module.exports = class Service1 extends cds.ApplicationService { init() { - this.on("READ", "Entity1/Attribute1", (req) => { + this.on("send1", async (req) => { const { messageToPass } = req.data; - this.emit("Received1", { messageToPass }); + const Service2 = await cds.connect.to("service-2"); + Service2.send("send2", { messageToPass }); }); } -} +}; diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds index 3cfae1f0f..a5963d2ef 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds @@ -1,6 +1,11 @@ -service Service2 { - /* Async API to talk to Service2. */ - event Received2: { - messageToPass: String; - } +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + +service Service2 @(path: '/service-2') { + /* Entity to send READ/GET about. */ + entity Service2Entity as projection on db_schema.Entity2 excluding { Attribute4 } + + /* API to talk to Service2. */ + action send2 ( + messageToPass: String + ) returns String; } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js index bdc33f1d0..3874498a8 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js @@ -1,9 +1,9 @@ const cds = require("@sap/cds"); const LOG = cds.log("logger"); -module.exports = cds.service.impl(() => { - /* Log upon receiving an "Received2" event. */ - this.on("Received2", async (msg) => { +module.exports = cds.service.impl(function() { + /* Log upon receiving an "send2" event. */ + this.on("send2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ LOG.info("Received: ", messageToPass); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/package.json index feaa60a89..6e66d859d 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/package.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/package.json @@ -1,12 +1,23 @@ { - "name": "@advanced-security/log-injection", - "version": "1.0.0", - "dependencies": { - "@sap/cds": "^7", - "express": "^4.17.1" - }, - "scripts": { - "start": "cds-serve", - "watch": "cds watch" - } + "name": "@advanced-security/log-injection", + "version": "1.0.0", + "dependencies": { + "@sap/cds": "^7", + "express": "^4.17.1", + "@cap-js/sqlite": "*" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + }, + "cds": { + "requires": { + "service-1": { + "impl": "srv/service1.js" + }, + "service-2": { + "impl": "srv/service2.js" + } + } + } } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/server.js index 2f0beac60..dc2ac97ee 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/server.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/server.js @@ -1,11 +1,4 @@ const cds = require('@sap/cds'); -const Service1 = await cds.connect.to("Service1"); +const app = require('express')(); -cds.once('bootstrap', (app) => { - app.serve("/log-injection").from("@advanced-security/log-injection"); -}); - -Service1.on("Received1", async (msg) => { - const { messageToPass } = msg.data; - await Service2.send("Received2", { messageToPass }); -}); +cds.serve('all').in(app); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.cds index d8477454b..34b2e7776 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.cds @@ -1,11 +1,11 @@ using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; -service Service1 { - /* Entity to send READ about. */ +service Service1 @(path: '/service-1') { + /* Entity to send READ/GET about. */ entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } - /* Async API for Service1 to speak through. */ - event Received1: { - messageToPass : String; - } + /* API to talk to Service1. */ + action send1 ( + messageToPass : String + ) returns String; } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.js index ee088f24c..88f05c5f8 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.js @@ -3,9 +3,10 @@ const cds = require("@sap/cds"); /* Emit a "Received1" event upon receiving a READ request on its entity. */ module.exports = class Service1 extends cds.ApplicationService { init() { - this.on("READ", "Entity1/Attribute1", (req) => { + this.on("send1", async (req) => { const { messageToPass } = req.data; - this.emit("Received1", { messageToPass }); + const Service2 = await cds.connect.to("service-2"); + Service2.send("send2", { messageToPass }); }); } -} +}; diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.cds index 3cfae1f0f..a5963d2ef 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.cds @@ -1,6 +1,11 @@ -service Service2 { - /* Async API to talk to Service2. */ - event Received2: { - messageToPass: String; - } +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + +service Service2 @(path: '/service-2') { + /* Entity to send READ/GET about. */ + entity Service2Entity as projection on db_schema.Entity2 excluding { Attribute4 } + + /* API to talk to Service2. */ + action send2 ( + messageToPass: String + ) returns String; } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js index bdc33f1d0..3874498a8 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js @@ -1,9 +1,9 @@ const cds = require("@sap/cds"); const LOG = cds.log("logger"); -module.exports = cds.service.impl(() => { - /* Log upon receiving an "Received2" event. */ - this.on("Received2", async (msg) => { +module.exports = cds.service.impl(function() { + /* Log upon receiving an "send2" event. */ + this.on("send2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ LOG.info("Received: ", messageToPass); From fabd8c4cdbaa985e84c577f4c4a1e2080b1b6217 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 28 Feb 2024 15:46:58 -0800 Subject: [PATCH 35/86] Rename existing test case and add new one - Renamed: log-injection-with-partial-protocol-none -> log-injection-with-service1-protocol-none - Created: log-injection-with-service2-protocol-none --- .../README.md | 0 .../db/schema.cds | 0 .../db/schema.json | 36 ++++++++++ .../package.json | 0 .../server.js | 0 .../srv/service1.cds | 0 .../srv/service1.js | 0 .../srv/service1.json | 67 +++++++++++++++++++ .../srv/service2.cds | 0 .../srv/service2.js | 0 .../srv/service2.json | 21 ++++++ .../README.md | 9 +++ .../db/schema.cds | 11 +++ .../db/schema.json | 36 ++++++++++ .../package.json | 23 +++++++ .../server.js | 4 ++ .../srv/service1.cds | 12 ++++ .../srv/service1.js | 12 ++++ .../srv/service1.json | 67 +++++++++++++++++++ .../srv/service2.cds | 12 ++++ .../srv/service2.js | 11 +++ .../srv/service2.json | 21 ++++++ 22 files changed, 342 insertions(+) rename javascript/frameworks/cap/test/queries/loginjection/{log-injection-with-partial-protocol-none => log-injection-with-service1-protocol-none}/README.md (100%) rename javascript/frameworks/cap/test/queries/loginjection/{log-injection-with-partial-protocol-none => log-injection-with-service1-protocol-none}/db/schema.cds (100%) create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/db/schema.json rename javascript/frameworks/cap/test/queries/loginjection/{log-injection-with-partial-protocol-none => log-injection-with-service1-protocol-none}/package.json (100%) rename javascript/frameworks/cap/test/queries/loginjection/{log-injection-with-partial-protocol-none => log-injection-with-service1-protocol-none}/server.js (100%) rename javascript/frameworks/cap/test/queries/loginjection/{log-injection-with-partial-protocol-none => log-injection-with-service1-protocol-none}/srv/service1.cds (100%) rename javascript/frameworks/cap/test/queries/loginjection/{log-injection-with-partial-protocol-none => log-injection-with-service1-protocol-none}/srv/service1.js (100%) create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json rename javascript/frameworks/cap/test/queries/loginjection/{log-injection-with-partial-protocol-none => log-injection-with-service1-protocol-none}/srv/service2.cds (100%) rename javascript/frameworks/cap/test/queries/loginjection/{log-injection-with-partial-protocol-none => log-injection-with-service1-protocol-none}/srv/service2.js (100%) create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/README.md create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/db/schema.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/db/schema.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/package.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/server.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.cds create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/README.md similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/README.md rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/README.md diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/db/schema.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/db/schema.cds similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/db/schema.cds rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/db/schema.cds diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/db/schema.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/db/schema.json new file mode 100644 index 000000000..74f9430e3 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/db/schema.json @@ -0,0 +1,36 @@ +{ + "namespace": "advanced_security.log_injection.sample_entities", + "definitions": { + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/package.json similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/package.json rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/package.json diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/server.js similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/server.js rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/server.js diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.cds similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.cds rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.cds diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.js similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service1.js rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.js diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json new file mode 100644 index 000000000..6aef472e6 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json @@ -0,0 +1,67 @@ +{ + "definitions": { + "Service1": { + "@source": "srv/service1.cds", + "kind": "service", + "@protocol": "none" + }, + "Service1.Service1Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity1" + ] + }, + "excluding": [ + "Attribute2" + ] + }, + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service1.Received1": { + "kind": "event", + "elements": { + "messageToPass": { + "type": "cds.String" + } + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.cds similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.cds rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.cds diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.js similarity index 100% rename from javascript/frameworks/cap/test/queries/loginjection/log-injection-with-partial-protocol-none/srv/service2.js rename to javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.js diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json new file mode 100644 index 000000000..f970dd84e --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json @@ -0,0 +1,21 @@ +{ + "definitions": { + "Service2": { + "@source": "srv/service2.cds", + "kind": "service" + }, + "Service2.Received2": { + "kind": "event", + "elements": { + "messageToPass": { + "type": "cds.String" + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/README.md b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/README.md new file mode 100644 index 000000000..7ad1607ce --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/README.md @@ -0,0 +1,9 @@ +# Log Injection PoC + +This application demonstrates the possibility of a log injection through communications between several `ApplicationService`s with one dedicated to logging. + +The `ApplicationService`s randomly mixes JS class definitions with `cds.service.impl` for diversity. + +## It _is_ a true positive case + +Service2 is marked as internal, but Service1 still receives and hands over an untrusted value to Service2 from the outside. diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/db/schema.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/db/schema.cds new file mode 100644 index 000000000..1d4202fb1 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/db/schema.cds @@ -0,0 +1,11 @@ +namespace advanced_security.log_injection.sample_entities; + +entity Entity1 { + Attribute1 : String(100); + Attribute2 : String(100) +} + +entity Entity2 { + Attribute3 : String(100); + Attribute4 : String(100) +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/db/schema.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/db/schema.json new file mode 100644 index 000000000..74f9430e3 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/db/schema.json @@ -0,0 +1,36 @@ +{ + "namespace": "advanced_security.log_injection.sample_entities", + "definitions": { + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/package.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/package.json new file mode 100644 index 000000000..6e66d859d --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/package.json @@ -0,0 +1,23 @@ +{ + "name": "@advanced-security/log-injection", + "version": "1.0.0", + "dependencies": { + "@sap/cds": "^7", + "express": "^4.17.1", + "@cap-js/sqlite": "*" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch" + }, + "cds": { + "requires": { + "service-1": { + "impl": "srv/service1.js" + }, + "service-2": { + "impl": "srv/service2.js" + } + } + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/server.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/server.js new file mode 100644 index 000000000..dc2ac97ee --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/server.js @@ -0,0 +1,4 @@ +const cds = require('@sap/cds'); +const app = require('express')(); + +cds.serve('all').in(app); diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.cds new file mode 100644 index 000000000..66ceee1c1 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.cds @@ -0,0 +1,12 @@ +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + +@protocol: 'none' +service Service1 @(path: '/service-1') { + /* Entity to send READ/GET about. */ + entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } + + /* API to talk to Service1. */ + action send1 ( + messageToPass : String + ) returns String; +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.js new file mode 100644 index 000000000..88f05c5f8 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.js @@ -0,0 +1,12 @@ +const cds = require("@sap/cds"); + +/* Emit a "Received1" event upon receiving a READ request on its entity. */ +module.exports = class Service1 extends cds.ApplicationService { + init() { + this.on("send1", async (req) => { + const { messageToPass } = req.data; + const Service2 = await cds.connect.to("service-2"); + Service2.send("send2", { messageToPass }); + }); + } +}; diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json new file mode 100644 index 000000000..6aef472e6 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json @@ -0,0 +1,67 @@ +{ + "definitions": { + "Service1": { + "@source": "srv/service1.cds", + "kind": "service", + "@protocol": "none" + }, + "Service1.Service1Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity1" + ] + }, + "excluding": [ + "Attribute2" + ] + }, + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service1.Received1": { + "kind": "event", + "elements": { + "messageToPass": { + "type": "cds.String" + } + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.cds new file mode 100644 index 000000000..4d1938ff5 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.cds @@ -0,0 +1,12 @@ +using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; + +@protocol: 'none' +service Service2 @(path: '/service-2') { + /* Entity to send READ/GET about. */ + entity Service2Entity as projection on db_schema.Entity2 excluding { Attribute4 } + + /* API to talk to Service2. */ + action send2 ( + messageToPass: String + ) returns String; +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js new file mode 100644 index 000000000..3874498a8 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js @@ -0,0 +1,11 @@ +const cds = require("@sap/cds"); +const LOG = cds.log("logger"); + +module.exports = cds.service.impl(function() { + /* Log upon receiving an "send2" event. */ + this.on("send2", async (msg) => { + const { messageToPass } = msg.data; + /* A log injection sink. */ + LOG.info("Received: ", messageToPass); + }); +}) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json new file mode 100644 index 000000000..f970dd84e --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json @@ -0,0 +1,21 @@ +{ + "definitions": { + "Service2": { + "@source": "srv/service2.cds", + "kind": "service" + }, + "Service2.Received2": { + "kind": "event", + "elements": { + "messageToPass": { + "type": "cds.String" + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file From a70d59a08bd5d13a900db8b2ff3072b261d1524f Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 28 Feb 2024 16:46:23 -0800 Subject: [PATCH 36/86] Update script to only run locally and not on Codespaces --- scripts/create-db.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/create-db.sh b/scripts/create-db.sh index 3bcbaed85..5e488447f 100644 --- a/scripts/create-db.sh +++ b/scripts/create-db.sh @@ -1,13 +1,11 @@ #!/bin/bash -# Run it at test/queries/ - -codeql="/home/codespace/.vscode-remote/data/User/globalStorage/github.vscode-codeql/distribution7/codeql/codeql" +# !!!!!!! Run it at test/queries/ !!!!!!! # Remember current directory TEST_DIR=$(pwd) # Loop over all the directories in the test directory -for dir in xss-*; do +for dir in *; do # Change to the directory cd $dir @@ -21,7 +19,7 @@ for dir in xss-*; do export LGTM_INDEX_FILTERS=include:**/*.json # Create CodeQL database - $codeql database create $FOLDER_NAME --language=javascript --overwrite + codeql database create $FOLDER_NAME --language=javascript --overwrite # Change back to the test directory cd $TEST_DIR From 46923651976d7766024e82929d8c12bdc5baed8b Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 29 Feb 2024 13:19:20 -0800 Subject: [PATCH 37/86] Change UserDefinedApplicationService to abstract class --- .../javascript/frameworks/cap/CDS.qll | 87 ++++++++----------- .../javascript/frameworks/cap/PackageJson.qll | 2 +- .../frameworks/cap/dataflow/DataFlow.qll | 20 +++-- .../frameworks/cap/dataflow/FlowSteps.qll | 18 +++- .../srv/service2.cds | 1 + 5 files changed, 70 insertions(+), 58 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index f4958a6df..e23ccd560 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -1,5 +1,5 @@ import javascript -import DataFlow +import semmle.javascript.dataflow.DataFlow import advanced_security.javascript.frameworks.cap.PackageJson import advanced_security.javascript.frameworks.cap.CDL import advanced_security.javascript.frameworks.cap.CQL @@ -295,59 +295,14 @@ class ErrorHandler extends Handler { ErrorHandler() { this.getAnEventName() = "error" } } -newtype TUserDefinedApplicationService = - /** - * Subclassing `cds.ApplicationService` via a ES6 class definition. - * ```js - * class SomeService extends cds.ApplicationService - * ``` - */ - TClassDefinition(ClassNode classNode) { - exists(CdsApplicationService cdsApplicationService | - classNode.getASuperClassNode() = cdsApplicationService.asSource() - ) - } or - /** - * Subclassing `cds.ApplicationService` via a call to `cds.service.impl`. - * ```js - * const cds = require('@sap/cds') - * module.exports = cds.service.impl (function() { ... }) - * ``` - */ - TImplMethodCall(MethodCallNode cdsServiceImplCall) { - exists(CdsFacade cds | - cdsServiceImplCall.getReceiver() = cds.getMember("service").asSource() and - cdsServiceImplCall.getMethodName() = "impl" - ) - } - /** * A custom application service of type `cds.ApplicationService`, where parts of the business logic are implemented. */ -class UserDefinedApplicationService extends TUserDefinedApplicationService { - ClassNode asClassDefinition() { this = TClassDefinition(result) } - - MethodCallNode asImplMethodCall() { this = TImplMethodCall(result) } - - string toString() { - result = this.asClassDefinition().toString() or - result = this.asImplMethodCall().toString() - } - - FunctionNode getInitFunction() { - result = this.asClassDefinition().getInstanceMethod("init") or - result = this.asImplMethodCall().getArgument(0) - } +abstract class UserDefinedApplicationService extends DataFlow::Node { + abstract FunctionNode getInitFunction(); HandlerRegistration getAHandlerRegistration() { - result.getEnclosingFunction() = getInitFunction().asExpr() - } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.asClassDefinition().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) or - this.asImplMethodCall().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + result.getEnclosingFunction() = this.getInitFunction().asExpr() } /** @@ -361,6 +316,40 @@ class UserDefinedApplicationService extends TUserDefinedApplicationService { } } +/** + * Subclassing `cds.ApplicationService` via a ES6 class definition. + * ```js + * class SomeService extends cds.ApplicationService + * ``` + */ +class ES6Definition extends ClassNode, UserDefinedApplicationService { + ES6Definition() { + exists(CdsApplicationService cdsApplicationService | + this.getASuperClassNode() = cdsApplicationService.asSource() + ) + } + + override FunctionNode getInitFunction() { result = this.getInstanceMethod("init") } +} + +/** + * Subclassing `cds.ApplicationService` via a call to `cds.service.impl`. + * ```js + * const cds = require('@sap/cds') + * module.exports = cds.service.impl (function() { ... }) + * ``` + */ +class ImplMethodCallDefinition extends MethodCallNode, UserDefinedApplicationService { + ImplMethodCallDefinition() { + exists(CdsFacade cds | + this.getReceiver() = cds.getMember("service").asSource() and + this.getMethodName() = "impl" + ) + } + + override FunctionNode getInitFunction() { result = this.getArgument(0) } +} + private class CdsApplicationService extends API::Node { CdsApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll index c6ab49252..65b67b74f 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll @@ -39,7 +39,7 @@ class RequiredService extends JsonObject { */ predicate isLocal() { exists(string path | path = this.getPropStringValue("impl")) } - File getImplementationFile() { "./" + result.getRelativePath() = this.getPropStringValue("impl") } + File getImplementationFile() { result.getRelativePath() = this.getPropStringValue("impl") } /** * Holds if this is a declaration of a database service, which is considered remote. diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll index 6494630db..483bc1e20 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll @@ -22,7 +22,7 @@ class ParseSink extends DataFlow::Node { * 2. Ones based on query-style API, based on `cds.Services.run`, and * 3. Ones based on emitting and subscribing to asynchronous event messages. */ -abstract class InterServiceCommunication extends MethodCallNode { +abstract class InterServiceCommunication extends HandlerRegistration { InterServiceCommunication() { this.getReceiver() instanceof ServiceInstance } /* TODO: Generalize UserApplicationService to include built-in services such as log and db */ @@ -40,20 +40,26 @@ abstract class InterServiceCommunication extends MethodCallNode { UserDefinedApplicationService getReceipient() { result = recipient } } +/** + * A REST style communication method that covers the built-in REST events (`GET`, `POST`, `PUT`, `UPDATE`, and `DELETE`), + * as well as custom actions that are defined in the accompanying `.cds` files. + */ class RestStyleCommunication extends InterServiceCommunication { RestStyleCommunication() { - exists(HandlerRegistration registration, SrvSend srvSend | - sender = registration.getReceiver().(ServiceInstance).getDefinition() and - recipient = srvSend.getReceiver().(ServiceInstance).getDefinition() + exists(SrvSend srvSend | + sender = this.getReceiver().(ServiceInstance).getDefinition() and + recipient = srvSend.getReceiver().(ServiceInstance).getDefinition() and + srvSend.asExpr().getEnclosingFunction+() = this.getHandler().asExpr() ) } } class CrudStyleCommunication extends InterServiceCommunication { CrudStyleCommunication() { - exists(HandlerRegistration registration, SrvRun srvRun | - sender = registration.getReceiver().(ServiceInstance).getDefinition() and - recipient = srvRun.getReceiver().(ServiceInstance).getDefinition() + exists(SrvRun srvRun | + sender = this.getReceiver().(ServiceInstance).getDefinition() and + recipient = srvRun.getReceiver().(ServiceInstance).getDefinition() and + srvRun.asExpr().getEnclosingFunction+() = this.getHandler().asExpr() ) } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll index 6ac4278cf..babfcfc93 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll @@ -1,3 +1,19 @@ /** * Additional flow steps to be registered to `DataFlow::SharedFlowStep`. - */ \ No newline at end of file + */ + +import javascript +import semmle.javascript.dataflow.DataFlow +import advanced_security.javascript.frameworks.cap.dataflow.DataFlow + +/** + * An issuing of and handling of a request or a message in an inter-service communication. + */ +class InterServiceCommunicationStepFromSenderToReceiver extends DataFlow::SharedFlowStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(InterServiceCommunication communication | + pred = communication.getSender() and + succ = communication.getReceipient() + ) + } +} diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.cds index 4d1938ff5..b9071e35f 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.cds @@ -10,3 +10,4 @@ service Service2 @(path: '/service-2') { messageToPass: String ) returns String; } + From f7846a2ccbd80791de86dd144bc9efcb46bdee37 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Mar 2024 13:11:12 -0800 Subject: [PATCH 38/86] Remove `@protocol: 'none'` in Service1.cds --- .../log-injection-with-service2-protocol-none/srv/service1.cds | 1 - 1 file changed, 1 deletion(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.cds index 66ceee1c1..34b2e7776 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.cds @@ -1,6 +1,5 @@ using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; -@protocol: 'none' service Service1 @(path: '/service-1') { /* Entity to send READ/GET about. */ entity Service1Entity as projection on db_schema.Entity1 excluding { Attribute2 } From 818a3943147cf7b260240a8449f3918da7bb6840 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Mar 2024 13:18:33 -0800 Subject: [PATCH 39/86] Minor formatting --- .../log-injection-with-service2-protocol-none/srv/service2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js index 3874498a8..8003f9c96 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js @@ -7,5 +7,5 @@ module.exports = cds.service.impl(function() { const { messageToPass } = msg.data; /* A log injection sink. */ LOG.info("Received: ", messageToPass); - }); + }); }) From 3489055d4fec2b43429b5812546c1b0d3136302c Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Mar 2024 13:40:50 -0800 Subject: [PATCH 40/86] Recompile modified CDS files --- .../srv/service1.json | 11 ++-- .../srv/service2.json | 56 ++++++++++++++++++- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json index 6aef472e6..f9bd8754e 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json @@ -3,7 +3,7 @@ "Service1": { "@source": "srv/service1.cds", "kind": "service", - "@protocol": "none" + "@path": "/service-1" }, "Service1.Service1Entity": { "kind": "entity", @@ -24,12 +24,15 @@ } } }, - "Service1.Received1": { - "kind": "event", - "elements": { + "Service1.send1": { + "kind": "action", + "params": { "messageToPass": { "type": "cds.String" } + }, + "returns": { + "type": "cds.String" } }, "advanced_security.log_injection.sample_entities.Entity1": { diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json index f970dd84e..a7e41e79f 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json @@ -2,14 +2,64 @@ "definitions": { "Service2": { "@source": "srv/service2.cds", - "kind": "service" + "kind": "service", + "@protocol": "none", + "@path": "/service-2" }, - "Service2.Received2": { - "kind": "event", + "Service2.Service2Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity2" + ] + }, + "excluding": [ + "Attribute4" + ] + }, "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service2.send2": { + "kind": "action", + "params": { "messageToPass": { "type": "cds.String" } + }, + "returns": { + "type": "cds.String" + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } } } }, From efd09b304deeb848abec8b70666df906113ab56b Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Mar 2024 16:11:55 -0800 Subject: [PATCH 41/86] Implement `getHandlerRegistration/1` --- .../advanced_security/javascript/frameworks/cap/CDS.qll | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index e23ccd560..3f663ac95 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -88,7 +88,6 @@ class ServiceInstanceFromCdsServe extends ServiceInstance { class ServiceInstanceFromCdsConnectTo extends ServiceInstance { string serviceName; - /* TODO: change this to an VarAccess to the VarDef whose getSource() is CdsConnectToCall or an AwaitExpr wrapping it */ ServiceInstanceFromCdsConnectTo() { exists(CdsConnectToCall cdsConnectTo | this = cdsConnectTo.getVarDefUsingCdsConnect().getAVariable().getAnAccess().flow() and @@ -301,10 +300,13 @@ class ErrorHandler extends Handler { abstract class UserDefinedApplicationService extends DataFlow::Node { abstract FunctionNode getInitFunction(); - HandlerRegistration getAHandlerRegistration() { - result.getEnclosingFunction() = this.getInitFunction().asExpr() + HandlerRegistration getHandlerRegistration(string eventName) { + result.getEnclosingFunction() = this.getInitFunction().asExpr() and + result.getAnEventName() = eventName } + HandlerRegistration getAHandlerRegistration() { result = this.getHandlerRegistration(_) } + /** * Gets the name of this service as declared in the ` package.json`. */ From 3d80c3b33dec4abaafb97380a0e218a49695c7d0 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Mar 2024 16:16:25 -0800 Subject: [PATCH 42/86] Add transitive import of FlowSteps --- .../javascript/frameworks/cap/dataflow/DataFlow.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll index 483bc1e20..b4dea289f 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll @@ -3,7 +3,9 @@ */ import javascript +import semmle.javascript.dataflow.DataFlow import advanced_security.javascript.frameworks.cap.CDS +import advanced_security.javascript.frameworks.cap.dataflow.FlowSteps /** * Methods that parse source strings into a CQL expression. From 3b1ec2638bad4f51e15cf0207ffa580f4dab4562 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Mar 2024 16:18:30 -0800 Subject: [PATCH 43/86] Add more fields and getters to `InterServiceCommunication` --- .../frameworks/cap/dataflow/DataFlow.qll | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll index b4dea289f..ab63f3207 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/DataFlow.qll @@ -22,24 +22,53 @@ class ParseSink extends DataFlow::Node { * A communication happening between `cds.Service`s. This includes: * 1. Ones based on REST-style API, based on `cds.Service.send`, * 2. Ones based on query-style API, based on `cds.Services.run`, and - * 3. Ones based on emitting and subscribing to asynchronous event messages. + * 3. Ones based on emitting and subscribing to asynchronous events. */ abstract class InterServiceCommunication extends HandlerRegistration { InterServiceCommunication() { this.getReceiver() instanceof ServiceInstance } - /* TODO: Generalize UserApplicationService to include built-in services such as log and db */ + /** + * The method call used by the sender to communicate with the recipient. + */ + InterServiceCommunicationMethodCall methodCall; /** * The service that sends the request. */ - UserDefinedApplicationService sender; + ServiceInstance sender; /** * The service that receives the request and handles it. */ - UserDefinedApplicationService recipient; + ServiceInstance recipient; + /** + * The object sent from the sender to the recipient. + */ + DataFlow::Node payload; - UserDefinedApplicationService getSender() { result = sender } + /** + * Gets the object representing the sender. + */ + ServiceInstance getSender() { result = sender } + + /** + * Gets the object representing the recipient. + */ + ServiceInstance getRecipient() { result = recipient } - UserDefinedApplicationService getReceipient() { result = recipient } + /** + * Gets the communication method call that is used on the recipient by the sender. + */ + InterServiceCommunicationMethodCall getCommunicationMethodCall() { result = methodCall } + + /** + * Gets the sender's definition, given that it is user-defined. + */ + /* TODO: Generalize UserApplicationService to include built-in services such as log and db */ + UserDefinedApplicationService getSenderDefinition() { result = sender.getDefinition() } + + /** + * Gets the recipien's definition, given that it is user-defined. + */ + UserDefinedApplicationService getRecipientDefinition() { result = recipient.getDefinition() } } /** @@ -49,8 +78,9 @@ abstract class InterServiceCommunication extends HandlerRegistration { class RestStyleCommunication extends InterServiceCommunication { RestStyleCommunication() { exists(SrvSend srvSend | - sender = this.getReceiver().(ServiceInstance).getDefinition() and - recipient = srvSend.getReceiver().(ServiceInstance).getDefinition() and + methodCall = srvSend and + sender = this.getReceiver() and + recipient = srvSend.getReceiver() and srvSend.asExpr().getEnclosingFunction+() = this.getHandler().asExpr() ) } @@ -59,8 +89,9 @@ class RestStyleCommunication extends InterServiceCommunication { class CrudStyleCommunication extends InterServiceCommunication { CrudStyleCommunication() { exists(SrvRun srvRun | - sender = this.getReceiver().(ServiceInstance).getDefinition() and - recipient = srvRun.getReceiver().(ServiceInstance).getDefinition() and + methodCall = srvRun and + sender = this.getReceiver() and + recipient = srvRun.getReceiver() and srvRun.asExpr().getEnclosingFunction+() = this.getHandler().asExpr() ) } @@ -75,14 +106,15 @@ class AsyncStyleCommunication extends InterServiceCommunication { emittingRegistration != orchestratingRegistration and /* The service that emits the event and the service that registers the handler are the same; it's the sender. */ this = orchestratingRegistration and - sender = emittingRegistration.getReceiver().(ServiceInstance).getDefinition() and - srvEmit.asExpr().getEnclosingFunction+() = sender.getInitFunction().asExpr() and + methodCall = srvEmit and + sender = emittingRegistration.getReceiver() and + srvEmit.asExpr().getEnclosingFunction+() = sender.getDefinition().getInitFunction().asExpr() and /* 1. match by their event name. */ srvEmit.getEmittedEvent() = orchestratingRegistration.getAnEventName() and /* 2. match by their service name in cds.connect().to(). */ srvEmit.getEmitter().getDefinition().getManifestName() = orchestratingRegistration.getReceiver().(ServiceInstanceFromCdsConnectTo).getServiceName() and - recipient = methodCallOnReceiver.getReceiver().(ServiceInstance).getDefinition() and + recipient = methodCallOnReceiver.getReceiver() and methodCallOnReceiver.getEnclosingFunction() = orchestratingRegistration.getHandler().asExpr() ) } From e6e699187f60dab84605cf9b807ca43fc6aad03a Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Mar 2024 16:19:41 -0800 Subject: [PATCH 44/86] Debug `InterServiceCommunicationStepFromSenderToReceiver` --- .../frameworks/cap/dataflow/FlowSteps.qll | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll index babfcfc93..a8b210c36 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll @@ -11,9 +11,23 @@ import advanced_security.javascript.frameworks.cap.dataflow.DataFlow */ class InterServiceCommunicationStepFromSenderToReceiver extends DataFlow::SharedFlowStep { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - exists(InterServiceCommunication communication | - pred = communication.getSender() and - succ = communication.getReceipient() + exists(InterServiceCommunication communication, string communicationEventName | + pred = communication.getCommunicationMethodCall().getArgument(1) and + communicationEventName = + communication + .getCommunicationMethodCall() + .getArgument(0) + .getALocalSource() + .asExpr() + .(StringLiteral) + .getValue() and + succ = + communication + .getRecipient() + .getDefinition() + .getHandlerRegistration(communicationEventName) + .getHandler() + .getParameter(0) ) } } From 14c48072202506da32c37d8618c51071d6082c8f Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Mar 2024 16:20:10 -0800 Subject: [PATCH 45/86] Minor stylistic change --- .../javascript/frameworks/cap/CAPLogInjection.qll | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll index b581a0479..2ff7c5815 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll @@ -10,9 +10,13 @@ class CdsLogger extends MethodCallNode { string name; CdsLogger() { - this = any(CdsFacade cds).getMember("log").getACall() and - name = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + exists(CdsFacade cds | + this = cds.getMember("log").getACall() and + name = this.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue() + ) } + + string getName() { result = name } } /** From a3b837387302d4688bf352df142772882fd99a18 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Mon, 4 Mar 2024 17:00:24 -0800 Subject: [PATCH 46/86] Finish first draft of multi-service log injection --- ...Injection.qll => CAPLogInjectionQuery.qll} | 14 +++++- .../javascript/frameworks/cap/CDL.qll | 45 +++++++++++++++++++ .../javascript/frameworks/cap/CDS.qll | 42 ++++++++++++++--- .../frameworks/cap/RemoteFlowSources.qll | 17 +++++-- .../cap/src/loginjection/LogInjection.ql | 17 +------ 5 files changed, 109 insertions(+), 26 deletions(-) rename javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/{CAPLogInjection.qll => CAPLogInjectionQuery.qll} (66%) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll similarity index 66% rename from javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll rename to javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll index 2ff7c5815..06c261eed 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjection.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll @@ -1,6 +1,9 @@ import javascript -import DataFlow +import semmle.javascript.dataflow.DataFlow +import semmle.javascript.security.dataflow.LogInjectionQuery +import advanced_security.javascript.frameworks.cap.RemoteFlowSources import advanced_security.javascript.frameworks.cap.CDS +import advanced_security.javascript.frameworks.cap.dataflow.FlowSteps /** * A logger obtained by a call to `log` on a CDS facade. Each logger is associated with @@ -33,3 +36,12 @@ class CdsLogSink extends DataFlow::Node { ) } } + +class Configuration extends LogInjectionConfiguration { + override predicate isSource(DataFlow::Node start) { + super.isSource(start) or + start instanceof RemoteFlowSource + } + + override predicate isSink(DataFlow::Node node) { node instanceof CdsLogSink } +} diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll index 7a94033a4..23dc181c5 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDL.qll @@ -25,6 +25,12 @@ abstract class CdlElement extends JsonObject { abstract string getName(); abstract CdlKind getKind(); + + CdlAnnotation getAnnotation(string annotationName) { + this = result.getQualifiedElement() and result.getName() = annotationName + } + + CdlAnnotation getAnAnnotation() { result = this.getAnnotation(_) } } class CdlService extends CdlElement { @@ -122,3 +128,42 @@ class CdlAttribute extends JsonObject { int getLength() { result = this.getPropValue("length").(JsonPrimitiveValue).getIntValue() } } + +abstract class CdlAnnotation extends JsonValue { + string annotationName; + CdlElement element; + + CdlAnnotation() { + this = element.getPropValue(annotationName) and + annotationName.charAt(0) = "@" + } + + /** + * Gets the name of this annotation, without the leading `@` character. + */ + string getName() { "@" + result = annotationName } + + /** + * Gets the CDL Element that this annotation is attached to. + */ + CdlElement getQualifiedElement() { result = element } +} + +class ProtocolAnnotation extends CdlAnnotation { + ProtocolAnnotation() { this = element.(CdlService).getPropValue("@protocol") } + + string getAnExposedProtocol() { + /* e.g. @protocol: 'odata' */ + result = this.(JsonString).getValue() + or + /* e.g. @protocol: ['odata', 'rest', 'graphql'] */ + result = this.(JsonArray).getElementStringValue(_) + or + /* e.g. @protocol: [{ kind: 'odata', path: 'some/path' }] */ + result = this.(JsonArray).getElementValue(_).(JsonObject).getPropStringValue("kind") + } +} + +class CdsFile extends File { + CdsFile() { exists(CdlElement element | this = element.getJsonFile()) } +} diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 3f663ac95..93206ca97 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -121,7 +121,9 @@ class ServiceInstanceFromCdsConnectTo extends ServiceInstance { * ``` */ class ServiceInstanceFromConstructor extends ServiceInstance { - ServiceInstanceFromConstructor() { this = any(CdsApplicationService cds).getAnInstantiation() } + ServiceInstanceFromConstructor() { + exists(CdsApplicationServiceClass cds | this = cds.getAnInstantiation()) + } override UserDefinedApplicationService getDefinition() { none() } @@ -308,7 +310,7 @@ abstract class UserDefinedApplicationService extends DataFlow::Node { HandlerRegistration getAHandlerRegistration() { result = this.getHandlerRegistration(_) } /** - * Gets the name of this service as declared in the ` package.json`. + * Gets the name of this service as declared in the `package.json`. */ string getManifestName() { exists(RequiredService serviceManifest | @@ -316,17 +318,45 @@ abstract class UserDefinedApplicationService extends DataFlow::Node { result = serviceManifest.getName() ) } + + /** + * Gets the CDS definition of this service. + */ + CdlService getCdsDeclaration() { + exists(CdsFile cdsFile | + cdsFile.getStem() = this.getFile().getStem() and + cdsFile.getParentContainer() = this.getFile().getParentContainer() and + result.getFile() = cdsFile + ) + } + + /** + * Holds if this service supports access from the outside through any kind of protocol. + */ + predicate isExposed() { not this.isInternal() } + + /** + * Holds if this service does not support access from the outside through any kind of protocol, thus being internal only. + */ + predicate isInternal() { + exists(CdlService cdsDeclaration | cdsDeclaration = this.getCdsDeclaration() | + cdsDeclaration.getAnnotation("protocol").(ProtocolAnnotation).getAnExposedProtocol() = "none" and + not exists(CdlAnnotation annotation | + annotation = cdsDeclaration.getAnnotation(["rest", "odata", "graphql"]) + ) + ) + } } /** * Subclassing `cds.ApplicationService` via a ES6 class definition. * ```js - * class SomeService extends cds.ApplicationService + * class SomeService extends cds.ApplicationService { init() { ... } } * ``` */ class ES6Definition extends ClassNode, UserDefinedApplicationService { ES6Definition() { - exists(CdsApplicationService cdsApplicationService | + exists(CdsApplicationServiceClass cdsApplicationService | this.getASuperClassNode() = cdsApplicationService.asSource() ) } @@ -352,8 +382,8 @@ class ImplMethodCallDefinition extends MethodCallNode, UserDefinedApplicationSer override FunctionNode getInitFunction() { result = this.getArgument(0) } } -private class CdsApplicationService extends API::Node { - CdsApplicationService() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } +private class CdsApplicationServiceClass extends API::Node { + CdsApplicationServiceClass() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } } abstract class InterServiceCommunicationMethodCall extends MethodCallNode { diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll index dc8ec72b4..1403c997e 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll @@ -11,9 +11,20 @@ import advanced_security.javascript.frameworks.cap.CDS * ``` * All the parameters named `req` and `msg` are captured in the above example. */ -/* TODO: narrow the definition down to exposed events */ class HandlerParameter extends ParameterNode, RemoteFlowSource { - HandlerParameter() { exists(Handler handler | this = handler.getParameter(0)) } + HandlerParameter() { + exists( + Handler handler, HandlerRegistration handlerRegistration, + UserDefinedApplicationService service + | + handler = handlerRegistration.getHandler() and + this = handler.getParameter(0) and + service.getAHandlerRegistration() = handlerRegistration and + service.isExposed() + ) + } - override string getSourceType() { result = "Parameter of an event handler" } + override string getSourceType() { + result = "Parameter of an event handler belonging to an exposed service" + } } diff --git a/javascript/frameworks/cap/src/loginjection/LogInjection.ql b/javascript/frameworks/cap/src/loginjection/LogInjection.ql index c8761eed0..98ca89266 100644 --- a/javascript/frameworks/cap/src/loginjection/LogInjection.ql +++ b/javascript/frameworks/cap/src/loginjection/LogInjection.ql @@ -12,22 +12,7 @@ import javascript import DataFlow::PathGraph -import semmle.javascript.security.dataflow.LogInjectionQuery -import advanced_security.javascript.frameworks.cap.RemoteFlowSources -import advanced_security.javascript.frameworks.cap.CDS -import advanced_security.javascript.frameworks.cap.CAPLogInjection - -/** - * A source of remote user controlled input. - */ -class CapRemoteSource extends Source { - CapRemoteSource() { this instanceof RemoteFlowSource } -} - -/** - * An argument to a logging mechanism. - */ -class CapLoggingSink extends Sink, CdsLogSink { } +import advanced_security.javascript.frameworks.cap.CAPLogInjectionQuery from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) From f34200437b5fe0cec1bb9e7518312b34eac7d306 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 5 Mar 2024 15:36:19 -0800 Subject: [PATCH 47/86] Debug query for log-injection-with-service2-protocol-none courtesy of @rvermeulen --- .../frameworks/cap/CAPLogInjectionQuery.qll | 6 +++--- .../frameworks/cap/dataflow/FlowSteps.qll | 13 +++++++++++++ .../frameworks/cap/src/loginjection/LogInjection.ql | 3 ++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll index 06c261eed..eead8e0b5 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll @@ -3,7 +3,7 @@ import semmle.javascript.dataflow.DataFlow import semmle.javascript.security.dataflow.LogInjectionQuery import advanced_security.javascript.frameworks.cap.RemoteFlowSources import advanced_security.javascript.frameworks.cap.CDS -import advanced_security.javascript.frameworks.cap.dataflow.FlowSteps +import advanced_security.javascript.frameworks.cap.dataflow.DataFlow /** * A logger obtained by a call to `log` on a CDS facade. Each logger is associated with @@ -37,11 +37,11 @@ class CdsLogSink extends DataFlow::Node { } } -class Configuration extends LogInjectionConfiguration { +class CAPLogInjectionConfiguration extends LogInjectionConfiguration { override predicate isSource(DataFlow::Node start) { super.isSource(start) or start instanceof RemoteFlowSource } - override predicate isSink(DataFlow::Node node) { node instanceof CdsLogSink } + override predicate isSink(DataFlow::Node end) { end instanceof CdsLogSink } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll index a8b210c36..5b86a1345 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll @@ -29,5 +29,18 @@ class InterServiceCommunicationStepFromSenderToReceiver extends DataFlow::Shared .getHandler() .getParameter(0) ) + or + exists(InterServiceCommunication communication, string communicationEventName | + succ = communication.getCommunicationMethodCall().getArgument(1) and + communicationEventName = + communication + .getCommunicationMethodCall() + .getArgument(0) + .getALocalSource() + .asExpr() + .(StringLiteral) + .getValue() and + pred = any(PropWrite write | write.getBase() = succ).getRhs() + ) } } diff --git a/javascript/frameworks/cap/src/loginjection/LogInjection.ql b/javascript/frameworks/cap/src/loginjection/LogInjection.ql index 98ca89266..b0ddaaec3 100644 --- a/javascript/frameworks/cap/src/loginjection/LogInjection.ql +++ b/javascript/frameworks/cap/src/loginjection/LogInjection.ql @@ -12,9 +12,10 @@ import javascript import DataFlow::PathGraph +import advanced_security.javascript.frameworks.cap.dataflow.DataFlow import advanced_security.javascript.frameworks.cap.CAPLogInjectionQuery -from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink +from CAPLogInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink.getNode(), source, sink, "Log entry depends on a $@.", source.getNode(), "user-provided value" From c5ecf1e035ddfa9372e4e9962d1141575003a5f9 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 5 Mar 2024 16:07:04 -0800 Subject: [PATCH 48/86] Add separate script to build database with (compiled) cds files --- scripts/create-db-with-cds.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 scripts/create-db-with-cds.sh diff --git a/scripts/create-db-with-cds.sh b/scripts/create-db-with-cds.sh new file mode 100644 index 000000000..ded467ae0 --- /dev/null +++ b/scripts/create-db-with-cds.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# !!!!!!! Run it at javascript/frameworks/cap/test/queries/test/queries/ !!!!!!! + +# Remember current directory +TEST_DIR=$(pwd) + +# Loop over all the directories in the test directory +for dir in *; do + # Change to the directory + cd $dir + + # Remember this folder's name + FOLDER_NAME=$(basename $(pwd)) + + # Enable XML extraction + export LGTM_INDEX_XML_MODE='ALL' + + # Enable JSON extraction + export LGTM_INDEX_FILTERS=include:**/*.json + + # Compile all .cds files to .json + for cds_file in $(find . -type f \( -iname '*.cds' \) -print ); do cds compile $cds_file -2 json -o "$(dirname $cds_file)/$(basename $cds_file .cds).json"; done + + # Create CodeQL database + codeql database create $FOLDER_NAME --language=javascript --overwrite + + # Change back to the test directory + cd $TEST_DIR +done + +echo "Done!" + From f61be3dda9e1b5479beb876c43e9cd266a6da50d Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 5 Mar 2024 16:07:34 -0800 Subject: [PATCH 49/86] Change bit of comment in create-db.sh --- scripts/create-db.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/create-db.sh b/scripts/create-db.sh index 5e488447f..cd31740bd 100644 --- a/scripts/create-db.sh +++ b/scripts/create-db.sh @@ -1,5 +1,5 @@ #!/bin/bash -# !!!!!!! Run it at test/queries/ !!!!!!! +# !!!!!!! Run it at javascript/frameworks/ui5/test/queries/test/queries/ !!!!!!! # Remember current directory TEST_DIR=$(pwd) From bef79db00397979589a9da0533cbbf4be85797a6 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 5 Mar 2024 17:15:41 -0800 Subject: [PATCH 50/86] Update unit test cases and .expected files --- ...njection-not-depending-on-request.expected | 3 + .../srv/service2.cds | 1 + .../loginjection.expected | 18 ------ ...ction-with-complete-protocol-none.expected | 3 + ...ction-with-service1-protocol-none.expected | 21 +++++++ .../srv/service1.json | 12 ++-- .../srv/service2.json | 55 ++++++++++++++++++- 7 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.expected create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.expected create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.expected diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.expected b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.expected new file mode 100644 index 000000000..ac992895b --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.expected @@ -0,0 +1,3 @@ +nodes +edges +#select diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds index a5963d2ef..4d1938ff5 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.cds @@ -1,5 +1,6 @@ using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema'; +@protocol: 'none' service Service2 @(path: '/service-2') { /* Entity to send READ/GET about. */ entity Service2Entity as projection on db_schema.Entity2 excluding { Attribute4 } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected b/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected index 6922f7f76..85a8e0b18 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected @@ -9,15 +9,6 @@ nodes | loginjection.js:11:14:11:26 | "test" + book | | loginjection.js:11:14:11:26 | "test" + book | | loginjection.js:11:23:11:26 | book | -| sample-app/srv/service2.js:6:33:6:35 | msg | -| sample-app/srv/service2.js:6:33:6:35 | msg | -| sample-app/srv/service2.js:7:15:7:31 | { messageToPass } | -| sample-app/srv/service2.js:7:15:7:42 | messageToPass | -| sample-app/srv/service2.js:7:17:7:29 | messageToPass | -| sample-app/srv/service2.js:7:35:7:37 | msg | -| sample-app/srv/service2.js:7:35:7:42 | msg.data | -| sample-app/srv/service2.js:9:32:9:44 | messageToPass | -| sample-app/srv/service2.js:9:32:9:44 | messageToPass | edges | loginjection.js:7:33:7:35 | req | loginjection.js:8:29:8:31 | req | | loginjection.js:7:33:7:35 | req | loginjection.js:8:29:8:31 | req | @@ -28,14 +19,5 @@ edges | loginjection.js:8:29:8:36 | req.data | loginjection.js:8:11:8:25 | {book,quantity} | | loginjection.js:11:23:11:26 | book | loginjection.js:11:14:11:26 | "test" + book | | loginjection.js:11:23:11:26 | book | loginjection.js:11:14:11:26 | "test" + book | -| sample-app/srv/service2.js:6:33:6:35 | msg | sample-app/srv/service2.js:7:35:7:37 | msg | -| sample-app/srv/service2.js:6:33:6:35 | msg | sample-app/srv/service2.js:7:35:7:37 | msg | -| sample-app/srv/service2.js:7:15:7:31 | { messageToPass } | sample-app/srv/service2.js:7:17:7:29 | messageToPass | -| sample-app/srv/service2.js:7:15:7:42 | messageToPass | sample-app/srv/service2.js:9:32:9:44 | messageToPass | -| sample-app/srv/service2.js:7:15:7:42 | messageToPass | sample-app/srv/service2.js:9:32:9:44 | messageToPass | -| sample-app/srv/service2.js:7:17:7:29 | messageToPass | sample-app/srv/service2.js:7:15:7:42 | messageToPass | -| sample-app/srv/service2.js:7:35:7:37 | msg | sample-app/srv/service2.js:7:35:7:42 | msg.data | -| sample-app/srv/service2.js:7:35:7:42 | msg.data | sample-app/srv/service2.js:7:15:7:31 | { messageToPass } | #select | loginjection.js:11:14:11:26 | "test" + book | loginjection.js:7:33:7:35 | req | loginjection.js:11:14:11:26 | "test" + book | Log entry depends on a $@. | loginjection.js:7:33:7:35 | req | user-provided value | -| sample-app/srv/service2.js:9:32:9:44 | messageToPass | sample-app/srv/service2.js:6:33:6:35 | msg | sample-app/srv/service2.js:9:32:9:44 | messageToPass | Log entry depends on a $@. | sample-app/srv/service2.js:6:33:6:35 | msg | user-provided value | diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.expected b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.expected new file mode 100644 index 000000000..ac992895b --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.expected @@ -0,0 +1,3 @@ +nodes +edges +#select diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.expected b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.expected new file mode 100644 index 000000000..708cb0500 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.expected @@ -0,0 +1,21 @@ +nodes +| srv/service2.js:6:29:6:31 | msg | +| srv/service2.js:6:29:6:31 | msg | +| srv/service2.js:7:15:7:31 | { messageToPass } | +| srv/service2.js:7:15:7:42 | messageToPass | +| srv/service2.js:7:17:7:29 | messageToPass | +| srv/service2.js:7:35:7:37 | msg | +| srv/service2.js:7:35:7:42 | msg.data | +| srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:9:32:9:44 | messageToPass | +edges +| srv/service2.js:6:29:6:31 | msg | srv/service2.js:7:35:7:37 | msg | +| srv/service2.js:6:29:6:31 | msg | srv/service2.js:7:35:7:37 | msg | +| srv/service2.js:7:15:7:31 | { messageToPass } | srv/service2.js:7:17:7:29 | messageToPass | +| srv/service2.js:7:15:7:42 | messageToPass | srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:7:15:7:42 | messageToPass | srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:7:17:7:29 | messageToPass | srv/service2.js:7:15:7:42 | messageToPass | +| srv/service2.js:7:35:7:37 | msg | srv/service2.js:7:35:7:42 | msg.data | +| srv/service2.js:7:35:7:42 | msg.data | srv/service2.js:7:15:7:31 | { messageToPass } | +#select +| srv/service2.js:9:32:9:44 | messageToPass | srv/service2.js:6:29:6:31 | msg | srv/service2.js:9:32:9:44 | messageToPass | Log entry depends on a $@. | srv/service2.js:6:29:6:31 | msg | user-provided value | diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json index 6aef472e6..d8aae510a 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json @@ -3,7 +3,8 @@ "Service1": { "@source": "srv/service1.cds", "kind": "service", - "@protocol": "none" + "@protocol": "none", + "@path": "/service-1" }, "Service1.Service1Entity": { "kind": "entity", @@ -24,12 +25,15 @@ } } }, - "Service1.Received1": { - "kind": "event", - "elements": { + "Service1.send1": { + "kind": "action", + "params": { "messageToPass": { "type": "cds.String" } + }, + "returns": { + "type": "cds.String" } }, "advanced_security.log_injection.sample_entities.Entity1": { diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json index f970dd84e..4e1c5c4d6 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json @@ -2,14 +2,63 @@ "definitions": { "Service2": { "@source": "srv/service2.cds", - "kind": "service" + "kind": "service", + "@path": "/service-2" }, - "Service2.Received2": { - "kind": "event", + "Service2.Service2Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity2" + ] + }, + "excluding": [ + "Attribute4" + ] + }, "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service2.send2": { + "kind": "action", + "params": { "messageToPass": { "type": "cds.String" } + }, + "returns": { + "type": "cds.String" + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } } } }, From 91c835842263dca77626985560e9531bdbde0916 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 6 Mar 2024 15:20:00 -0800 Subject: [PATCH 51/86] Fix all unit tests --- .../javascript/frameworks/cap/Application.qll | 19 +++++++++ .../javascript/frameworks/cap/PackageJson.qll | 7 +++- ...g-injection-not-depending-on-request.qlref | 1 + ...njection-with-complete-protocol-none.qlref | 1 + ...njection-with-service1-protocol-none.qlref | 1 + ...ction-with-service2-protocol-none.expected | 37 +++++++++++++++++ ...njection-with-service2-protocol-none.qlref | 1 + ...g-injection-without-protocol-none.expected | 40 +++++++++++++++++++ .../log-injection-without-protocol-none.qlref | 1 + 9 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/Application.qll create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.qlref create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.qlref create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.qlref create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.expected create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.qlref create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.expected create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.qlref diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/Application.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/Application.qll new file mode 100644 index 000000000..76b53cf6d --- /dev/null +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/Application.qll @@ -0,0 +1,19 @@ +/** + * Definitions pertaining to the application as a whole. + */ + +import javascript +import advanced_security.javascript.frameworks.cap.PackageJson + +class RootDirectory extends Folder { + RootDirectory() { + exists(PackageJson packageJson | this = packageJson.getJsonFile().getParentContainer()) + } + + /** + * Gets the path of a file relative to this root directory. + */ + string getFilePathRelativeToRoot(File file) { + result = file.getAbsolutePath().regexpReplaceAll(this.getAbsolutePath(), ".") + } +} diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll index 65b67b74f..edc2df3a7 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/PackageJson.qll @@ -1,4 +1,5 @@ import javascript +import advanced_security.javascript.frameworks.cap.Application /** * The "cds" section of this application's `package.json`. @@ -39,7 +40,11 @@ class RequiredService extends JsonObject { */ predicate isLocal() { exists(string path | path = this.getPropStringValue("impl")) } - File getImplementationFile() { result.getRelativePath() = this.getPropStringValue("impl") } + File getImplementationFile() { + exists(RootDirectory root | + root.getFilePathRelativeToRoot(result) = "./" + this.getPropStringValue("impl") + ) + } /** * Holds if this is a declaration of a database service, which is considered remote. diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.qlref b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.qlref new file mode 100644 index 000000000..b97580aa2 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.qlref @@ -0,0 +1 @@ +loginjection/LogInjection.ql \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.qlref b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.qlref new file mode 100644 index 000000000..b97580aa2 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.qlref @@ -0,0 +1 @@ +loginjection/LogInjection.ql \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.qlref b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.qlref new file mode 100644 index 000000000..c33ccfc65 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.qlref @@ -0,0 +1 @@ +loginjection/LogInjection.ql diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.expected b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.expected new file mode 100644 index 000000000..8666c3389 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.expected @@ -0,0 +1,37 @@ +nodes +| srv/service1.js:6:33:6:35 | req | +| srv/service1.js:6:33:6:35 | req | +| srv/service1.js:7:19:7:35 | { messageToPass } | +| srv/service1.js:7:19:7:46 | messageToPass | +| srv/service1.js:7:21:7:33 | messageToPass | +| srv/service1.js:7:39:7:41 | req | +| srv/service1.js:7:39:7:46 | req.data | +| srv/service1.js:9:36:9:52 | { messageToPass } | +| srv/service1.js:9:38:9:50 | messageToPass | +| srv/service2.js:6:29:6:31 | msg | +| srv/service2.js:7:15:7:31 | { messageToPass } | +| srv/service2.js:7:15:7:42 | messageToPass | +| srv/service2.js:7:17:7:29 | messageToPass | +| srv/service2.js:7:35:7:37 | msg | +| srv/service2.js:7:35:7:42 | msg.data | +| srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:9:32:9:44 | messageToPass | +edges +| srv/service1.js:6:33:6:35 | req | srv/service1.js:7:39:7:41 | req | +| srv/service1.js:6:33:6:35 | req | srv/service1.js:7:39:7:41 | req | +| srv/service1.js:7:19:7:35 | { messageToPass } | srv/service1.js:7:21:7:33 | messageToPass | +| srv/service1.js:7:19:7:46 | messageToPass | srv/service1.js:9:38:9:50 | messageToPass | +| srv/service1.js:7:21:7:33 | messageToPass | srv/service1.js:7:19:7:46 | messageToPass | +| srv/service1.js:7:39:7:41 | req | srv/service1.js:7:39:7:46 | req.data | +| srv/service1.js:7:39:7:46 | req.data | srv/service1.js:7:19:7:35 | { messageToPass } | +| srv/service1.js:9:36:9:52 | { messageToPass } | srv/service2.js:6:29:6:31 | msg | +| srv/service1.js:9:38:9:50 | messageToPass | srv/service1.js:9:36:9:52 | { messageToPass } | +| srv/service2.js:6:29:6:31 | msg | srv/service2.js:7:35:7:37 | msg | +| srv/service2.js:7:15:7:31 | { messageToPass } | srv/service2.js:7:17:7:29 | messageToPass | +| srv/service2.js:7:15:7:42 | messageToPass | srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:7:15:7:42 | messageToPass | srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:7:17:7:29 | messageToPass | srv/service2.js:7:15:7:42 | messageToPass | +| srv/service2.js:7:35:7:37 | msg | srv/service2.js:7:35:7:42 | msg.data | +| srv/service2.js:7:35:7:42 | msg.data | srv/service2.js:7:15:7:31 | { messageToPass } | +#select +| srv/service2.js:9:32:9:44 | messageToPass | srv/service1.js:6:33:6:35 | req | srv/service2.js:9:32:9:44 | messageToPass | Log entry depends on a $@. | srv/service1.js:6:33:6:35 | req | user-provided value | diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.qlref b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.qlref new file mode 100644 index 000000000..c33ccfc65 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.qlref @@ -0,0 +1 @@ +loginjection/LogInjection.ql diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.expected b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.expected new file mode 100644 index 000000000..06af9807a --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.expected @@ -0,0 +1,40 @@ +nodes +| srv/service1.js:6:33:6:35 | req | +| srv/service1.js:6:33:6:35 | req | +| srv/service1.js:7:19:7:35 | { messageToPass } | +| srv/service1.js:7:19:7:46 | messageToPass | +| srv/service1.js:7:21:7:33 | messageToPass | +| srv/service1.js:7:39:7:41 | req | +| srv/service1.js:7:39:7:46 | req.data | +| srv/service1.js:9:36:9:52 | { messageToPass } | +| srv/service1.js:9:38:9:50 | messageToPass | +| srv/service2.js:6:29:6:31 | msg | +| srv/service2.js:6:29:6:31 | msg | +| srv/service2.js:7:15:7:31 | { messageToPass } | +| srv/service2.js:7:15:7:42 | messageToPass | +| srv/service2.js:7:17:7:29 | messageToPass | +| srv/service2.js:7:35:7:37 | msg | +| srv/service2.js:7:35:7:42 | msg.data | +| srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:9:32:9:44 | messageToPass | +edges +| srv/service1.js:6:33:6:35 | req | srv/service1.js:7:39:7:41 | req | +| srv/service1.js:6:33:6:35 | req | srv/service1.js:7:39:7:41 | req | +| srv/service1.js:7:19:7:35 | { messageToPass } | srv/service1.js:7:21:7:33 | messageToPass | +| srv/service1.js:7:19:7:46 | messageToPass | srv/service1.js:9:38:9:50 | messageToPass | +| srv/service1.js:7:21:7:33 | messageToPass | srv/service1.js:7:19:7:46 | messageToPass | +| srv/service1.js:7:39:7:41 | req | srv/service1.js:7:39:7:46 | req.data | +| srv/service1.js:7:39:7:46 | req.data | srv/service1.js:7:19:7:35 | { messageToPass } | +| srv/service1.js:9:36:9:52 | { messageToPass } | srv/service2.js:6:29:6:31 | msg | +| srv/service1.js:9:38:9:50 | messageToPass | srv/service1.js:9:36:9:52 | { messageToPass } | +| srv/service2.js:6:29:6:31 | msg | srv/service2.js:7:35:7:37 | msg | +| srv/service2.js:6:29:6:31 | msg | srv/service2.js:7:35:7:37 | msg | +| srv/service2.js:7:15:7:31 | { messageToPass } | srv/service2.js:7:17:7:29 | messageToPass | +| srv/service2.js:7:15:7:42 | messageToPass | srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:7:15:7:42 | messageToPass | srv/service2.js:9:32:9:44 | messageToPass | +| srv/service2.js:7:17:7:29 | messageToPass | srv/service2.js:7:15:7:42 | messageToPass | +| srv/service2.js:7:35:7:37 | msg | srv/service2.js:7:35:7:42 | msg.data | +| srv/service2.js:7:35:7:42 | msg.data | srv/service2.js:7:15:7:31 | { messageToPass } | +#select +| srv/service2.js:9:32:9:44 | messageToPass | srv/service1.js:6:33:6:35 | req | srv/service2.js:9:32:9:44 | messageToPass | Log entry depends on a $@. | srv/service1.js:6:33:6:35 | req | user-provided value | +| srv/service2.js:9:32:9:44 | messageToPass | srv/service2.js:6:29:6:31 | msg | srv/service2.js:9:32:9:44 | messageToPass | Log entry depends on a $@. | srv/service2.js:6:29:6:31 | msg | user-provided value | diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.qlref b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.qlref new file mode 100644 index 000000000..c33ccfc65 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.qlref @@ -0,0 +1 @@ +loginjection/LogInjection.ql From 2be5dab084c389e3ae2a2ab2c503a5cca328b9bf Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 7 Mar 2024 13:07:21 -0800 Subject: [PATCH 52/86] Fix log-injection-single-file --- .../frameworks/cap/CAPLogInjectionQuery.qll | 4 +-- .../javascript/frameworks/cap/CDS.qll | 2 +- .../cap/src/cqlinjection/CqlInjection.ql | 2 +- .../loginjection.expected | 34 ------------------- 4 files changed, 4 insertions(+), 38 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll index eead8e0b5..10f206b9f 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CAPLogInjectionQuery.qll @@ -29,10 +29,10 @@ class CdsLogSink extends DataFlow::Node { CdsLogSink() { exists(CdsLogger log, MethodCallNode loggingMethod | this = loggingMethod.getAnArgument() and + loggingMethod.getMethodName() = ["trace", "debug", "info", "log", "warn", "error"] and not this.asExpr() instanceof Literal and not this.asExpr() instanceof TemplateLiteral and - loggingMethod.getReceiver().getALocalSource() = log and - loggingMethod.getMethodName() = ["trace", "debug", "info", "log", "warn", "error"] + loggingMethod.getReceiver().getALocalSource() = log ) } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 93206ca97..0a0790285 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -10,7 +10,7 @@ import advanced_security.javascript.frameworks.cap.CQL * ``` */ class CdsFacade extends API::Node { - CdsFacade() { this = API::moduleImport("@sap/cds") } + CdsFacade() { this = API::moduleImport(["@sap/cds", "@sap/cds/lib"]) } Node getNode() { result = this.asSource() } } diff --git a/javascript/frameworks/cap/src/cqlinjection/CqlInjection.ql b/javascript/frameworks/cap/src/cqlinjection/CqlInjection.ql index d4596500e..291dfec99 100644 --- a/javascript/frameworks/cap/src/cqlinjection/CqlInjection.ql +++ b/javascript/frameworks/cap/src/cqlinjection/CqlInjection.ql @@ -21,7 +21,7 @@ class CqlIConfiguration extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } - override predicate isSink(DataFlow::Node sink) { sink instanceof Sink or sink instanceof CQLSink } + override predicate isSink(DataFlow::Node sink) { sink instanceof CQLSink } override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected b/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected index bbbbfd3f0..96f76c841 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-single-file/loginjection.expected @@ -1,24 +1,14 @@ nodes | loginjection.js:7:34:7:36 | req | | loginjection.js:7:34:7:36 | req | -| loginjection.js:7:34:7:36 | req | -| loginjection.js:7:34:7:36 | req | -| loginjection.js:8:13:8:30 | { book, quantity } | | loginjection.js:8:13:8:30 | { book, quantity } | | loginjection.js:8:13:8:41 | book | -| loginjection.js:8:13:8:41 | book | -| loginjection.js:8:15:8:18 | book | | loginjection.js:8:15:8:18 | book | | loginjection.js:8:34:8:36 | req | -| loginjection.js:8:34:8:36 | req | -| loginjection.js:8:34:8:41 | req.data | | loginjection.js:8:34:8:41 | req.data | | loginjection.js:11:16:11:28 | "CAP:" + book | | loginjection.js:11:16:11:28 | "CAP:" + book | | loginjection.js:11:25:11:28 | book | -| loginjection.js:12:19:12:35 | "console:" + book | -| loginjection.js:12:19:12:35 | "console:" + book | -| loginjection.js:12:32:12:35 | book | | loginjection.js:15:24:15:26 | req | | loginjection.js:15:24:15:26 | req | | loginjection.js:18:13:18:24 | $ | @@ -27,40 +17,23 @@ nodes | loginjection.js:18:47:18:47 | $ | | loginjection.js:18:47:18:47 | $ | | loginjection.js:23:13:23:30 | { book, quantity } | -| loginjection.js:23:13:23:30 | { book, quantity } | -| loginjection.js:23:13:23:53 | book | | loginjection.js:23:13:23:53 | book | | loginjection.js:23:15:23:18 | book | -| loginjection.js:23:15:23:18 | book | -| loginjection.js:23:34:23:53 | req2.params.category | -| loginjection.js:23:34:23:53 | req2.params.category | | loginjection.js:23:34:23:53 | req2.params.category | | loginjection.js:23:34:23:53 | req2.params.category | | loginjection.js:25:16:25:28 | "CAP:" + book | | loginjection.js:25:16:25:28 | "CAP:" + book | | loginjection.js:25:25:25:28 | book | -| loginjection.js:26:19:26:35 | "console:" + book | -| loginjection.js:26:19:26:35 | "console:" + book | -| loginjection.js:26:32:26:35 | book | edges | loginjection.js:7:34:7:36 | req | loginjection.js:8:34:8:36 | req | | loginjection.js:7:34:7:36 | req | loginjection.js:8:34:8:36 | req | -| loginjection.js:7:34:7:36 | req | loginjection.js:8:34:8:36 | req | -| loginjection.js:7:34:7:36 | req | loginjection.js:8:34:8:36 | req | -| loginjection.js:8:13:8:30 | { book, quantity } | loginjection.js:8:15:8:18 | book | | loginjection.js:8:13:8:30 | { book, quantity } | loginjection.js:8:15:8:18 | book | | loginjection.js:8:13:8:41 | book | loginjection.js:11:25:11:28 | book | -| loginjection.js:8:13:8:41 | book | loginjection.js:12:32:12:35 | book | -| loginjection.js:8:15:8:18 | book | loginjection.js:8:13:8:41 | book | | loginjection.js:8:15:8:18 | book | loginjection.js:8:13:8:41 | book | | loginjection.js:8:34:8:36 | req | loginjection.js:8:34:8:41 | req.data | -| loginjection.js:8:34:8:36 | req | loginjection.js:8:34:8:41 | req.data | -| loginjection.js:8:34:8:41 | req.data | loginjection.js:8:13:8:30 | { book, quantity } | | loginjection.js:8:34:8:41 | req.data | loginjection.js:8:13:8:30 | { book, quantity } | | loginjection.js:11:25:11:28 | book | loginjection.js:11:16:11:28 | "CAP:" + book | | loginjection.js:11:25:11:28 | book | loginjection.js:11:16:11:28 | "CAP:" + book | -| loginjection.js:12:32:12:35 | book | loginjection.js:12:19:12:35 | "console:" + book | -| loginjection.js:12:32:12:35 | book | loginjection.js:12:19:12:35 | "console:" + book | | loginjection.js:15:24:15:26 | req | loginjection.js:18:17:18:19 | req | | loginjection.js:15:24:15:26 | req | loginjection.js:18:17:18:19 | req | | loginjection.js:18:13:18:24 | $ | loginjection.js:18:47:18:47 | $ | @@ -68,19 +41,12 @@ edges | loginjection.js:18:17:18:19 | req | loginjection.js:18:17:18:24 | req.data | | loginjection.js:18:17:18:24 | req.data | loginjection.js:18:13:18:24 | $ | | loginjection.js:23:13:23:30 | { book, quantity } | loginjection.js:23:15:23:18 | book | -| loginjection.js:23:13:23:30 | { book, quantity } | loginjection.js:23:15:23:18 | book | | loginjection.js:23:13:23:53 | book | loginjection.js:25:25:25:28 | book | -| loginjection.js:23:13:23:53 | book | loginjection.js:26:32:26:35 | book | -| loginjection.js:23:15:23:18 | book | loginjection.js:23:13:23:53 | book | | loginjection.js:23:15:23:18 | book | loginjection.js:23:13:23:53 | book | | loginjection.js:23:34:23:53 | req2.params.category | loginjection.js:23:13:23:30 | { book, quantity } | | loginjection.js:23:34:23:53 | req2.params.category | loginjection.js:23:13:23:30 | { book, quantity } | -| loginjection.js:23:34:23:53 | req2.params.category | loginjection.js:23:13:23:30 | { book, quantity } | -| loginjection.js:23:34:23:53 | req2.params.category | loginjection.js:23:13:23:30 | { book, quantity } | | loginjection.js:25:25:25:28 | book | loginjection.js:25:16:25:28 | "CAP:" + book | | loginjection.js:25:25:25:28 | book | loginjection.js:25:16:25:28 | "CAP:" + book | -| loginjection.js:26:32:26:35 | book | loginjection.js:26:19:26:35 | "console:" + book | -| loginjection.js:26:32:26:35 | book | loginjection.js:26:19:26:35 | "console:" + book | #select | loginjection.js:11:16:11:28 | "CAP:" + book | loginjection.js:7:34:7:36 | req | loginjection.js:11:16:11:28 | "CAP:" + book | Log entry depends on a $@. | loginjection.js:7:34:7:36 | req | user-provided value | | loginjection.js:18:47:18:47 | $ | loginjection.js:15:24:15:26 | req | loginjection.js:18:47:18:47 | $ | Log entry depends on a $@. | loginjection.js:15:24:15:26 | req | user-provided value | From 37eeac35d7f7fb8223a988b09c4bead269a9265b Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 7 Mar 2024 13:37:27 -0800 Subject: [PATCH 53/86] Fix `userdefinedservice` test --- .../javascript/frameworks/cap/CDS.qll | 40 ++++++++++++++----- .../userdefinedservice/userdefinedservice.ql | 2 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index 0a0790285..fd2fed8e6 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -296,12 +296,32 @@ class ErrorHandler extends Handler { ErrorHandler() { this.getAnEventName() = "error" } } +private class CdsServiceClass extends API::Node { + CdsServiceClass() { exists(CdsFacade c | this = c.getMember("Service")) } +} + +private class CdsApplicationServiceClass extends API::Node { + CdsApplicationServiceClass() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } +} + +abstract class UserDefinedService extends DataFlow::Node { + abstract FunctionNode getInitFunction(); +} + +abstract class UserDefinedBaseService extends UserDefinedService { } + +class ES6BaseServiceDefinition extends ClassNode, UserDefinedBaseService { + ES6BaseServiceDefinition() { + exists(CdsServiceClass cdsService | this.getASuperClassNode() = cdsService.asSource()) + } + + override FunctionNode getInitFunction() { result = this.getInstanceMethod("init") } +} + /** * A custom application service of type `cds.ApplicationService`, where parts of the business logic are implemented. */ -abstract class UserDefinedApplicationService extends DataFlow::Node { - abstract FunctionNode getInitFunction(); - +abstract class UserDefinedApplicationService extends UserDefinedService { HandlerRegistration getHandlerRegistration(string eventName) { result.getEnclosingFunction() = this.getInitFunction().asExpr() and result.getAnEventName() = eventName @@ -354,8 +374,8 @@ abstract class UserDefinedApplicationService extends DataFlow::Node { * class SomeService extends cds.ApplicationService { init() { ... } } * ``` */ -class ES6Definition extends ClassNode, UserDefinedApplicationService { - ES6Definition() { +class ES6ApplicationServiceDefinition extends ClassNode, UserDefinedApplicationService { + ES6ApplicationServiceDefinition() { exists(CdsApplicationServiceClass cdsApplicationService | this.getASuperClassNode() = cdsApplicationService.asSource() ) @@ -371,8 +391,10 @@ class ES6Definition extends ClassNode, UserDefinedApplicationService { * module.exports = cds.service.impl (function() { ... }) * ``` */ -class ImplMethodCallDefinition extends MethodCallNode, UserDefinedApplicationService { - ImplMethodCallDefinition() { +class ImplMethodCallApplicationServiceDefinition extends MethodCallNode, + UserDefinedApplicationService +{ + ImplMethodCallApplicationServiceDefinition() { exists(CdsFacade cds | this.getReceiver() = cds.getMember("service").asSource() and this.getMethodName() = "impl" @@ -382,10 +404,6 @@ class ImplMethodCallDefinition extends MethodCallNode, UserDefinedApplicationSer override FunctionNode getInitFunction() { result = this.getArgument(0) } } -private class CdsApplicationServiceClass extends API::Node { - CdsApplicationServiceClass() { exists(CdsFacade c | this = c.getMember("ApplicationService")) } -} - abstract class InterServiceCommunicationMethodCall extends MethodCallNode { InterServiceCommunicationMethodCall() { exists(ServiceInstance srv | this = srv.getASrvMethodCall()) diff --git a/javascript/frameworks/cap/test/models/cds/userdefinedservice/userdefinedservice.ql b/javascript/frameworks/cap/test/models/cds/userdefinedservice/userdefinedservice.ql index 3b7e50102..2b7644041 100644 --- a/javascript/frameworks/cap/test/models/cds/userdefinedservice/userdefinedservice.ql +++ b/javascript/frameworks/cap/test/models/cds/userdefinedservice/userdefinedservice.ql @@ -1,5 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CDS -from UserDefinedApplicationService svc +from UserDefinedService svc select svc \ No newline at end of file From cd9bbe7a69f23a40af37ade399d35b109462ea9b Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 7 Mar 2024 14:15:25 -0800 Subject: [PATCH 54/86] Fix `requesthandler` unit test Note that `requesthandler` initially aimed to see if the request handlers are correctly identified; so we look for the handler parameter explicitly rather than selecting the `RemoteFlowSources.HandlerParameter`. --- .../cap/test/models/cds/requesthandler/requesthandler.ql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.ql b/javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.ql index d57079fbe..f26db704a 100644 --- a/javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.ql +++ b/javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.ql @@ -1,5 +1,5 @@ import javascript -import advanced_security.javascript.frameworks.cap.RemoteFlowSources +import advanced_security.javascript.frameworks.cap.CDS -from HandlerParameter src -select src \ No newline at end of file +from Handler handler +select handler.getAParameter() \ No newline at end of file From 62b5c91a082afe15293552390479164c79c279a7 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 7 Mar 2024 14:18:15 -0800 Subject: [PATCH 55/86] Fix failing unit tests due to compilation error --- javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql | 2 +- javascript/frameworks/cap/test/models/cds/logger/logger.ql | 2 +- .../test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql b/javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql index b20a1498c..f877d02e0 100644 --- a/javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql +++ b/javascript/frameworks/cap/test/models/cds/cxn/parseexpr.ql @@ -1,5 +1,5 @@ import javascript -import advanced_security.javascript.frameworks.cap.CDS +import advanced_security.javascript.frameworks.cap.dataflow.DataFlow from ParseSink sink select sink \ No newline at end of file diff --git a/javascript/frameworks/cap/test/models/cds/logger/logger.ql b/javascript/frameworks/cap/test/models/cds/logger/logger.ql index 1da737cc9..847b6ce56 100644 --- a/javascript/frameworks/cap/test/models/cds/logger/logger.ql +++ b/javascript/frameworks/cap/test/models/cds/logger/logger.ql @@ -1,5 +1,5 @@ import javascript -import advanced_security.javascript.frameworks.cap.CAPLogInjection +import advanced_security.javascript.frameworks.cap.CAPLogInjectionQuery from CdsLogSink sink select sink \ No newline at end of file diff --git a/javascript/frameworks/cap/test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql b/javascript/frameworks/cap/test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql index ad3affced..1069f3ac2 100644 --- a/javascript/frameworks/cap/test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql +++ b/javascript/frameworks/cap/test/models/cds/oldstyleuserdefined/oldstyleuserdefined.ql @@ -1,6 +1,5 @@ import javascript import advanced_security.javascript.frameworks.cap.CDS -from MethodCallNode cdsServiceImplCall -where exists(TUserDefinedApplicationService svc | svc = TImplMethodCall(cdsServiceImplCall)) +from ImplMethodCallApplicationServiceDefinition cdsServiceImplCall select cdsServiceImplCall From b57a7c616ff6a929e7cf58c784614f580342c3f8 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 7 Mar 2024 14:42:13 -0800 Subject: [PATCH 56/86] Fix `applicationserviceinstance` test --- .../lib/advanced_security/javascript/frameworks/cap/CDS.qll | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll index fd2fed8e6..f7ba11185 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll @@ -90,7 +90,10 @@ class ServiceInstanceFromCdsConnectTo extends ServiceInstance { ServiceInstanceFromCdsConnectTo() { exists(CdsConnectToCall cdsConnectTo | - this = cdsConnectTo.getVarDefUsingCdsConnect().getAVariable().getAnAccess().flow() and + ( + this = cdsConnectTo.getACall() or + this = cdsConnectTo.getVarDefUsingCdsConnect().getAVariable().getAnAccess().flow() + ) and serviceName = cdsConnectTo.getServiceName() ) } From f2111694175107d15ea59b597a16adc6cbc31d7a Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 8 Mar 2024 12:57:37 -0800 Subject: [PATCH 57/86] Update untracked json files compiled from cds files --- .../db/schema.json | 36 ++++++++++ .../srv/service1.json | 70 ++++++++++++++++++ .../srv/service2.json | 71 +++++++++++++++++++ .../db/schema.json | 36 ++++++++++ .../srv/service1.json | 71 +++++++++++++++++++ .../srv/service2.json | 71 +++++++++++++++++++ .../db/schema.json | 36 ++++++++++ .../srv/service1.json | 70 ++++++++++++++++++ .../srv/service2.json | 70 ++++++++++++++++++ 9 files changed, 531 insertions(+) create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/db/schema.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/db/schema.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/db/schema.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.json create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.json diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/db/schema.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/db/schema.json new file mode 100644 index 000000000..74f9430e3 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/db/schema.json @@ -0,0 +1,36 @@ +{ + "namespace": "advanced_security.log_injection.sample_entities", + "definitions": { + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.json new file mode 100644 index 000000000..f9bd8754e --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.json @@ -0,0 +1,70 @@ +{ + "definitions": { + "Service1": { + "@source": "srv/service1.cds", + "kind": "service", + "@path": "/service-1" + }, + "Service1.Service1Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity1" + ] + }, + "excluding": [ + "Attribute2" + ] + }, + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service1.send1": { + "kind": "action", + "params": { + "messageToPass": { + "type": "cds.String" + } + }, + "returns": { + "type": "cds.String" + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.json new file mode 100644 index 000000000..a7e41e79f --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.json @@ -0,0 +1,71 @@ +{ + "definitions": { + "Service2": { + "@source": "srv/service2.cds", + "kind": "service", + "@protocol": "none", + "@path": "/service-2" + }, + "Service2.Service2Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity2" + ] + }, + "excluding": [ + "Attribute4" + ] + }, + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service2.send2": { + "kind": "action", + "params": { + "messageToPass": { + "type": "cds.String" + } + }, + "returns": { + "type": "cds.String" + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/db/schema.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/db/schema.json new file mode 100644 index 000000000..74f9430e3 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/db/schema.json @@ -0,0 +1,36 @@ +{ + "namespace": "advanced_security.log_injection.sample_entities", + "definitions": { + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.json new file mode 100644 index 000000000..d8aae510a --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.json @@ -0,0 +1,71 @@ +{ + "definitions": { + "Service1": { + "@source": "srv/service1.cds", + "kind": "service", + "@protocol": "none", + "@path": "/service-1" + }, + "Service1.Service1Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity1" + ] + }, + "excluding": [ + "Attribute2" + ] + }, + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service1.send1": { + "kind": "action", + "params": { + "messageToPass": { + "type": "cds.String" + } + }, + "returns": { + "type": "cds.String" + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.json new file mode 100644 index 000000000..a7e41e79f --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.json @@ -0,0 +1,71 @@ +{ + "definitions": { + "Service2": { + "@source": "srv/service2.cds", + "kind": "service", + "@protocol": "none", + "@path": "/service-2" + }, + "Service2.Service2Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity2" + ] + }, + "excluding": [ + "Attribute4" + ] + }, + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service2.send2": { + "kind": "action", + "params": { + "messageToPass": { + "type": "cds.String" + } + }, + "returns": { + "type": "cds.String" + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/db/schema.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/db/schema.json new file mode 100644 index 000000000..74f9430e3 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/db/schema.json @@ -0,0 +1,36 @@ +{ + "namespace": "advanced_security.log_injection.sample_entities", + "definitions": { + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.json new file mode 100644 index 000000000..f9bd8754e --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.json @@ -0,0 +1,70 @@ +{ + "definitions": { + "Service1": { + "@source": "srv/service1.cds", + "kind": "service", + "@path": "/service-1" + }, + "Service1.Service1Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity1" + ] + }, + "excluding": [ + "Attribute2" + ] + }, + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service1.send1": { + "kind": "action", + "params": { + "messageToPass": { + "type": "cds.String" + } + }, + "returns": { + "type": "cds.String" + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.json new file mode 100644 index 000000000..4e1c5c4d6 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.json @@ -0,0 +1,70 @@ +{ + "definitions": { + "Service2": { + "@source": "srv/service2.cds", + "kind": "service", + "@path": "/service-2" + }, + "Service2.Service2Entity": { + "kind": "entity", + "projection": { + "from": { + "ref": [ + "advanced_security.log_injection.sample_entities.Entity2" + ] + }, + "excluding": [ + "Attribute4" + ] + }, + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + } + } + }, + "Service2.send2": { + "kind": "action", + "params": { + "messageToPass": { + "type": "cds.String" + } + }, + "returns": { + "type": "cds.String" + } + }, + "advanced_security.log_injection.sample_entities.Entity1": { + "kind": "entity", + "elements": { + "Attribute1": { + "type": "cds.String", + "length": 100 + }, + "Attribute2": { + "type": "cds.String", + "length": 100 + } + } + }, + "advanced_security.log_injection.sample_entities.Entity2": { + "kind": "entity", + "elements": { + "Attribute3": { + "type": "cds.String", + "length": 100 + }, + "Attribute4": { + "type": "cds.String", + "length": 100 + } + } + } + }, + "meta": { + "creator": "CDS Compiler v4.1.2", + "flavor": "inferred" + }, + "$version": "2.0" +} \ No newline at end of file From 3ab3c72706626b005f32f1bef2e6fd9ece599f96 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 13 Mar 2024 00:16:46 -0700 Subject: [PATCH 58/86] Check diagram svgs into repository --- ...log-injection-not-depending-on-request.svg | 312 ++++++++++++++++ ...-injection-with-complete-protocol-none.svg | 292 +++++++++++++++ ...-injection-with-service1-protocol-none.svg | 316 ++++++++++++++++ ...-injection-with-service2-protocol-none.svg | 316 ++++++++++++++++ .../log-injection-without-protocol-none.svg | 340 ++++++++++++++++++ 5 files changed, 1576 insertions(+) create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.svg create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.svg create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.svg create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.svg create mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.svg diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.svg b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.svg new file mode 100644 index 000000000..c61166961 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/log-injection-not-depending-on-request.svg @@ -0,0 +1,312 @@ + + + + + + + + + +
+
+
+ Service1
+
+
+
+ Service1 +
+
+ + + + + +
+
+
action: + send1
+
+
+
+ action: send1 +
+
+ + + + +
+
+
+ new Datetime() +
+
+
+
+ new Datetime() +
+
+ + + + +
+
+
OData + exposed
+
+
+
+ OData exposed +
+
+ + + + +
+
+
+ Service1.on("send1")
+
+
+
+ Service1.on("send1") +
+
+ + + + + + +
+
+
+ Service2
+
+
+
+ Service2 +
+
+ + + + + +
+
+
action: + send2
+
+
+
+ action: send2 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
+ Service2.on("send2")
+
+
+
+ Service2.on("send2") +
+
+ + + + + + +
+
+
+ STDOUT
+
+
+
+ STDOUT +
+
+ + + + + + +
+
+
+ Service2.send("send2")
+
+
+
+ Service2.send("send2") +
+
+ + + + +
+
+
Internal + only
+
+
+
+ Internal only +
+
+ + + + + + +
+
+
Outside + actor
+
+
+
+ Outside actor +
+
+
+ + + + Text is not SVG - cannot + display + + +
\ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.svg b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.svg new file mode 100644 index 000000000..a725e5ef4 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/log-injection-with-complete-protocol-none.svg @@ -0,0 +1,292 @@ + + + + + + + + + +
+
+
+ Service1
+
+
+
+ Service1 +
+
+ + + + + +
+
+
action: + send1
+
+
+
+ action: send1 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
OData + exposed
+
+
+
+ OData exposed +
+
+ + + + +
+
+
+ Service1.on("send1")
+
+
+
+ Service1.on("send1") +
+
+ + + + + + +
+
+
+ Service2
+
+
+
+ Service2 +
+
+ + + + + +
+
+
action: + send2
+
+
+
+ action: send2 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
+ Service2.on("send2")
+
+
+
+ Service2.on("send2") +
+
+ + + + + + +
+
+
+ STDOUT
+
+
+
+ STDOUT +
+
+ + + + + + +
+
+
+ Service2.send("send2")
+
+
+
+ Service2.send("send2") +
+
+ + + + +
+
+
Internal + only
+
+
+
+ Internal only +
+
+
+ + + + Text is not SVG - cannot + display + + +
\ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.svg b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.svg new file mode 100644 index 000000000..f7e61cce4 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/log-injection-with-service1-protocol-none.svg @@ -0,0 +1,316 @@ + + + + + + + + + +
+
+
+ Service1
+
+
+
+ Service1 +
+
+ + + + + +
+
+
action: + send1
+
+
+
+ action: send1 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
OData + exposed
+
+
+
+ OData exposed +
+
+ + + + +
+
+
+ Service1.on("send1")
+
+
+
+ Service1.on("send1") +
+
+ + + + + + +
+
+
+ Service2
+
+
+
+ Service2 +
+
+ + + + + +
+
+
action: + send2
+
+
+
+ action: send2 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
+ Service2.on("send2")
+
+
+
+ Service2.on("send2") +
+
+ + + + + + +
+
+
+ STDOUT
+
+
+
+ STDOUT +
+
+ + + + + + +
+
+
+ Service2.send("send2")
+
+
+
+ Service2.send("send2") +
+
+ + + + +
+
+
Internal + only
+
+
+
+ Internal only +
+
+ + + + + + +
+
+
Outside + actor
+
+
+
+ Outside actor +
+
+
+ + + + Text is not SVG - cannot + display + + +
\ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.svg b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.svg new file mode 100644 index 000000000..4020fb6a0 --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/log-injection-with-service2-protocol-none.svg @@ -0,0 +1,316 @@ + + + + + + + + + +
+
+
+ Service1
+
+
+
+ Service1 +
+
+ + + + + +
+
+
action: + send1
+
+
+
+ action: send1 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
OData + exposed
+
+
+
+ OData exposed +
+
+ + + + +
+
+
+ Service1.on("send1")
+
+
+
+ Service1.on("send1") +
+
+ + + + + + +
+
+
+ Service2
+
+
+
+ Service2 +
+
+ + + + + +
+
+
action: + send2
+
+
+
+ action: send2 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
+ Service2.on("send2")
+
+
+
+ Service2.on("send2") +
+
+ + + + + + +
+
+
+ STDOUT
+
+
+
+ STDOUT +
+
+ + + + + + +
+
+
+ Service2.send("send2")
+
+
+
+ Service2.send("send2") +
+
+ + + + +
+
+
Internal + only
+
+
+
+ Internal only +
+
+ + + + + + +
+
+
Outside + actor
+
+
+
+ Outside actor +
+
+
+ + + + Text is not SVG - cannot + display + + +
\ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.svg b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.svg new file mode 100644 index 000000000..aaf9417aa --- /dev/null +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/log-injection-without-protocol-none.svg @@ -0,0 +1,340 @@ + + + + + + + + + +
+
+
+ Service1
+
+
+
+ Service1 +
+
+ + + + + +
+
+
action: + send1
+
+
+
+ action: send1 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
OData + exposed
+
+
+
+ OData exposed +
+
+ + + + +
+
+
+ Service1.on("send1")
+
+
+
+ Service1.on("send1") +
+
+ + + + + + +
+
+
+ Service2
+
+
+
+ Service2 +
+
+ + + + + +
+
+
action: + send2
+
+
+
+ action: send2 +
+
+ + + + + + +
+
+
+ messageToPass: String +
+
+
+
+ messageToPass: String +
+
+ + + + +
+
+
+ Service2.on("send2")
+
+
+
+ Service2.on("send2") +
+
+ + + + + + +
+
+
+ STDOUT
+
+
+
+ STDOUT +
+
+ + + + + + +
+
+
+ Service2.send("send2")
+
+
+
+ Service2.send("send2") +
+
+ + + + +
+
+
Internal + only
+
+
+
+ Internal only +
+
+ + + + + + +
+
+
Outside + actor
+
+
+
+ Outside actor +
+
+ + + + + + +
+
+
Outside + actor
+
+
+
+ Outside actor +
+
+
+ + + + Text is not SVG - cannot + display + + +
\ No newline at end of file From ecdb36f39bde6639ad0ad1fd1b721c21ace15ab3 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Mon, 18 Mar 2024 18:14:37 -0700 Subject: [PATCH 59/86] Add inline comment to signal CAP/vanilla log injection sinks Co-authored-by: Mauro Baluda --- .../log-injection-not-depending-on-request/srv/service2.js | 2 +- .../log-injection-with-complete-protocol-none/srv/service2.js | 2 +- .../log-injection-with-service1-protocol-none/srv/service2.js | 2 +- .../log-injection-with-service2-protocol-none/srv/service2.js | 2 +- .../log-injection-without-protocol-none/srv/service2.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js index bdc33f1d0..d4e0aa1e9 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js @@ -6,6 +6,6 @@ module.exports = cds.service.impl(() => { this.on("Received2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ - LOG.info("Received: ", messageToPass); + LOG.info("Received: ", messageToPass); // CAP log injection alert }); }) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js index 3874498a8..2e7546123 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.js @@ -6,6 +6,6 @@ module.exports = cds.service.impl(function() { this.on("send2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ - LOG.info("Received: ", messageToPass); + LOG.info("Received: ", messageToPass); // CAP log injection alert }); }) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.js index 3874498a8..2e7546123 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.js @@ -6,6 +6,6 @@ module.exports = cds.service.impl(function() { this.on("send2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ - LOG.info("Received: ", messageToPass); + LOG.info("Received: ", messageToPass); // CAP log injection alert }); }) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js index 8003f9c96..4fcca1407 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.js @@ -6,6 +6,6 @@ module.exports = cds.service.impl(function() { this.on("send2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ - LOG.info("Received: ", messageToPass); + LOG.info("Received: ", messageToPass); // CAP log injection alert }); }) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js index 3874498a8..2e7546123 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.js @@ -6,6 +6,6 @@ module.exports = cds.service.impl(function() { this.on("send2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ - LOG.info("Received: ", messageToPass); + LOG.info("Received: ", messageToPass); // CAP log injection alert }); }) From eb5b49ebc2a56f327db1eb16728e90f3d1f8bd69 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 20 Mar 2024 14:27:06 -0700 Subject: [PATCH 60/86] Fix `log-injection-not-depending-on-request` --- .../log-injection-not-depending-on-request/srv/service1.js | 1 + .../log-injection-not-depending-on-request/srv/service2.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js index 681b024b9..45ab74163 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.js @@ -5,6 +5,7 @@ module.exports = class Service1 extends cds.ApplicationService { init() { this.on("send1", async (req) => { // req is not used at all let datetime = new Date(); + const Service2 = await cds.connect.to("service-2"); Service2.send("send2", { messageToPass: datetime.toString() }); }); } diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js index d4e0aa1e9..9eda0e953 100644 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js +++ b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.js @@ -3,7 +3,7 @@ const LOG = cds.log("logger"); module.exports = cds.service.impl(() => { /* Log upon receiving an "Received2" event. */ - this.on("Received2", async (msg) => { + this.on("send2", async (msg) => { const { messageToPass } = msg.data; /* A log injection sink. */ LOG.info("Received: ", messageToPass); // CAP log injection alert From 06b685f392655d076cbdb7c7c13783306440bc00 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 20 Mar 2024 14:28:07 -0700 Subject: [PATCH 61/86] Restrict search space to the same application --- .../javascript/frameworks/cap/Application.qll | 8 ++- .../frameworks/cap/dataflow/FlowSteps.qll | 67 ++++++++++--------- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/Application.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/Application.qll index 76b53cf6d..1082c1bb9 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/Application.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/Application.qll @@ -14,6 +14,12 @@ class RootDirectory extends Folder { * Gets the path of a file relative to this root directory. */ string getFilePathRelativeToRoot(File file) { - result = file.getAbsolutePath().regexpReplaceAll(this.getAbsolutePath(), ".") + result = file.getAbsolutePath().regexpReplaceAll(this.getAbsolutePath(), ".") and + result.charAt(0) = "." } + + /** + * Holds if this root directory of the application contains the given file. + */ + predicate contains(File file) { exists(this.getFilePathRelativeToRoot(file)) } } diff --git a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll index 5b86a1345..b95ad0399 100644 --- a/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll +++ b/javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/dataflow/FlowSteps.qll @@ -11,36 +11,43 @@ import advanced_security.javascript.frameworks.cap.dataflow.DataFlow */ class InterServiceCommunicationStepFromSenderToReceiver extends DataFlow::SharedFlowStep { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - exists(InterServiceCommunication communication, string communicationEventName | - pred = communication.getCommunicationMethodCall().getArgument(1) and - communicationEventName = - communication - .getCommunicationMethodCall() - .getArgument(0) - .getALocalSource() - .asExpr() - .(StringLiteral) - .getValue() and - succ = - communication - .getRecipient() - .getDefinition() - .getHandlerRegistration(communicationEventName) - .getHandler() - .getParameter(0) - ) - or - exists(InterServiceCommunication communication, string communicationEventName | - succ = communication.getCommunicationMethodCall().getArgument(1) and - communicationEventName = - communication - .getCommunicationMethodCall() - .getArgument(0) - .getALocalSource() - .asExpr() - .(StringLiteral) - .getValue() and - pred = any(PropWrite write | write.getBase() = succ).getRhs() + ( + exists(InterServiceCommunication communication, string communicationEventName | + pred = communication.getCommunicationMethodCall().getArgument(1) and + communicationEventName = + communication + .getCommunicationMethodCall() + .getArgument(0) + .getALocalSource() + .asExpr() + .(StringLiteral) + .getValue() and + succ = + communication + .getRecipient() + .getDefinition() + .getHandlerRegistration(communicationEventName) + .getHandler() + .getParameter(0) + ) + or + exists(InterServiceCommunication communication, string communicationEventName | + succ = communication.getCommunicationMethodCall().getArgument(1) and + communicationEventName = + communication + .getCommunicationMethodCall() + .getArgument(0) + .getALocalSource() + .asExpr() + .(StringLiteral) + .getValue() and + pred = any(PropWrite write | write.getBase() = succ).getRhs() + ) + ) and + /* Restrict search space to the same application. */ + exists(RootDirectory rootDirectory | + rootDirectory.contains(pred.getFile()) and + rootDirectory.contains(succ.getFile()) ) } } From 3f668c3939f9dfce465a6bd3676bd9aad4beb3a6 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 20 Mar 2024 15:13:27 -0700 Subject: [PATCH 62/86] Untrack `.json` files compiled from `.cds` files --- .../srv/service1.json | 70 ------------------- 1 file changed, 70 deletions(-) delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.json diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.json deleted file mode 100644 index f9bd8754e..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service1.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "definitions": { - "Service1": { - "@source": "srv/service1.cds", - "kind": "service", - "@path": "/service-1" - }, - "Service1.Service1Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity1" - ] - }, - "excluding": [ - "Attribute2" - ] - }, - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service1.send1": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file From a3f5099b7b80d04b41ff4590f3352e894e4c1383 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 20 Mar 2024 15:14:37 -0700 Subject: [PATCH 63/86] Untrack `.json` files compiled from `.cds` files --- .../srv/service2.json | 71 ------------------- .../srv/service1.json | 71 ------------------- .../srv/service2.json | 71 ------------------- .../srv/service1.json | 71 ------------------- .../srv/service2.json | 70 ------------------ .../srv/service1.json | 70 ------------------ .../srv/service2.json | 71 ------------------- .../srv/service1.json | 70 ------------------ .../srv/service2.json | 70 ------------------ 9 files changed, 635 deletions(-) delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.json delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.json delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.json delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.json delete mode 100644 javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.json diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.json deleted file mode 100644 index a7e41e79f..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-not-depending-on-request/srv/service2.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "definitions": { - "Service2": { - "@source": "srv/service2.cds", - "kind": "service", - "@protocol": "none", - "@path": "/service-2" - }, - "Service2.Service2Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity2" - ] - }, - "excluding": [ - "Attribute4" - ] - }, - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service2.send2": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.json deleted file mode 100644 index d8aae510a..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service1.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "definitions": { - "Service1": { - "@source": "srv/service1.cds", - "kind": "service", - "@protocol": "none", - "@path": "/service-1" - }, - "Service1.Service1Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity1" - ] - }, - "excluding": [ - "Attribute2" - ] - }, - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service1.send1": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.json deleted file mode 100644 index a7e41e79f..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-complete-protocol-none/srv/service2.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "definitions": { - "Service2": { - "@source": "srv/service2.cds", - "kind": "service", - "@protocol": "none", - "@path": "/service-2" - }, - "Service2.Service2Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity2" - ] - }, - "excluding": [ - "Attribute4" - ] - }, - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service2.send2": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json deleted file mode 100644 index d8aae510a..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service1.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "definitions": { - "Service1": { - "@source": "srv/service1.cds", - "kind": "service", - "@protocol": "none", - "@path": "/service-1" - }, - "Service1.Service1Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity1" - ] - }, - "excluding": [ - "Attribute2" - ] - }, - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service1.send1": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json deleted file mode 100644 index 4e1c5c4d6..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service1-protocol-none/srv/service2.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "definitions": { - "Service2": { - "@source": "srv/service2.cds", - "kind": "service", - "@path": "/service-2" - }, - "Service2.Service2Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity2" - ] - }, - "excluding": [ - "Attribute4" - ] - }, - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service2.send2": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json deleted file mode 100644 index f9bd8754e..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service1.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "definitions": { - "Service1": { - "@source": "srv/service1.cds", - "kind": "service", - "@path": "/service-1" - }, - "Service1.Service1Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity1" - ] - }, - "excluding": [ - "Attribute2" - ] - }, - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service1.send1": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json deleted file mode 100644 index a7e41e79f..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-with-service2-protocol-none/srv/service2.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "definitions": { - "Service2": { - "@source": "srv/service2.cds", - "kind": "service", - "@protocol": "none", - "@path": "/service-2" - }, - "Service2.Service2Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity2" - ] - }, - "excluding": [ - "Attribute4" - ] - }, - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service2.send2": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.json deleted file mode 100644 index f9bd8754e..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service1.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "definitions": { - "Service1": { - "@source": "srv/service1.cds", - "kind": "service", - "@path": "/service-1" - }, - "Service1.Service1Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity1" - ] - }, - "excluding": [ - "Attribute2" - ] - }, - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service1.send1": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file diff --git a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.json b/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.json deleted file mode 100644 index 4e1c5c4d6..000000000 --- a/javascript/frameworks/cap/test/queries/loginjection/log-injection-without-protocol-none/srv/service2.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "definitions": { - "Service2": { - "@source": "srv/service2.cds", - "kind": "service", - "@path": "/service-2" - }, - "Service2.Service2Entity": { - "kind": "entity", - "projection": { - "from": { - "ref": [ - "advanced_security.log_injection.sample_entities.Entity2" - ] - }, - "excluding": [ - "Attribute4" - ] - }, - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - } - } - }, - "Service2.send2": { - "kind": "action", - "params": { - "messageToPass": { - "type": "cds.String" - } - }, - "returns": { - "type": "cds.String" - } - }, - "advanced_security.log_injection.sample_entities.Entity1": { - "kind": "entity", - "elements": { - "Attribute1": { - "type": "cds.String", - "length": 100 - }, - "Attribute2": { - "type": "cds.String", - "length": 100 - } - } - }, - "advanced_security.log_injection.sample_entities.Entity2": { - "kind": "entity", - "elements": { - "Attribute3": { - "type": "cds.String", - "length": 100 - }, - "Attribute4": { - "type": "cds.String", - "length": 100 - } - } - } - }, - "meta": { - "creator": "CDS Compiler v4.1.2", - "flavor": "inferred" - }, - "$version": "2.0" -} \ No newline at end of file From 054be51b279f522102db902509ffa5cad55da555 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 20 Mar 2024 16:52:13 -0700 Subject: [PATCH 64/86] Update `javascript.sarif.expected` --- .github/workflows/javascript.sarif.expected | 13574 ++++++++++-------- 1 file changed, 7897 insertions(+), 5677 deletions(-) diff --git a/.github/workflows/javascript.sarif.expected b/.github/workflows/javascript.sarif.expected index 37a9f3592..619cca50c 100644 --- a/.github/workflows/javascript.sarif.expected +++ b/.github/workflows/javascript.sarif.expected @@ -6,7 +6,7 @@ "driver" : { "name" : "CodeQL", "organization" : "GitHub", - "semanticVersion" : "2.16.3", + "semanticVersion" : "2.16.4", "notifications" : [ { "id" : "cli/expected-extracted-files/javascript", "name" : "cli/expected-extracted-files/javascript", @@ -43,36 +43,78 @@ "rules" : [ ] }, "extensions" : [ { - "name" : "advanced-security/javascript-sap-ui5-queries", - "semanticVersion" : "0.5.0+6519bc2c9f3d9be292ae8322aa3ea18c081bb38b", + "name" : "advanced-security/javascript-sap-cap-queries", + "semanticVersion" : "0.1.0+b1fbd510971da2b65671de1296cb2c3288f6b252", "rules" : [ { - "id" : "js/ui5-clickjacking", - "name" : "js/ui5-clickjacking", + "id" : "js/cap-sql-injection", + "name" : "js/cap-sql-injection", "shortDescription" : { - "text" : "UI5 Clickjacking" + "text" : "CQL query built from user-controlled sources" }, "fullDescription" : { - "text" : "The absence of frame options allows for clickjacking." + "text" : "Building a CQL query from user-controlled sources is vulnerable to insertion of malicious code by the user." }, "defaultConfiguration" : { "enabled" : true, "level" : "error" }, "help" : { - "text" : "# Clickjacking\n\nUI5 applications that do not explicitly set the frame options to `deny` may be vulnerable to UI redress attacks (”clickjacking”). In these attacks, the vulnerable site is loaded in a frame on an attacker-controlled site which uses opaque or transparent layers to trick the user into unintentionally clicking a button or link on the vulnerable site.\n\n## Recommendation\n\nExplicitly set the frame options to `\"deny\"`, either through `window[\"sap-ui-config\"]`, or `data-sap-ui-frameOptions` attribute of the script tag where it sources the bootstrap script `\"sap-ui-core.js\"`:\n\n``` javascript\nwindow[\"sap-ui-config\"] = {\n frameOptions: \"deny\",\n ...\n};\n```\n\n``` javascript\nwindow[\"sap-ui-config\"].frameOptions = \"deny\";\n```\n\n``` html\n\n```\n\n## Example\n\n### Setting the Frame Options to `\"allow\"`\n\nThis UI5 application explicitly allows to be embedded in other applications.\n\n```javascript\n\n\n \n ...\n \n\n \n \n ...\n\n```\n\n### Not Setting the Frame Options to Anything\n\nThe default value of `window[\"sap-ui-config\"]` and `data-sap-ui-frameOptions` are both `\"allow\"`, which makes leaving it untouched allows the application to be embedded.\n\n## References\n* OWASP: [Clickjacking Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html).\n* Mozilla: [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).\n* SAP UI5 Documentation: [Frame Options](https://sapui5.hana.ondemand.com/sdk/#/topic/62d9c4d8f5ad49aa914624af9551beb7.html).\n* SAP UI5 Documentation: [Allowlist Service](https://sapui5.hana.ondemand.com/sdk/#/topic/d04a6d41480c4396af16b5d2b25509ec.html).\n* Common Weakness Enumeration: [CWE-451](https://cwe.mitre.org/data/definitions/451.html).\n", - "markdown" : "# Clickjacking\n\nUI5 applications that do not explicitly set the frame options to `deny` may be vulnerable to UI redress attacks (”clickjacking”). In these attacks, the vulnerable site is loaded in a frame on an attacker-controlled site which uses opaque or transparent layers to trick the user into unintentionally clicking a button or link on the vulnerable site.\n\n## Recommendation\n\nExplicitly set the frame options to `\"deny\"`, either through `window[\"sap-ui-config\"]`, or `data-sap-ui-frameOptions` attribute of the script tag where it sources the bootstrap script `\"sap-ui-core.js\"`:\n\n``` javascript\nwindow[\"sap-ui-config\"] = {\n frameOptions: \"deny\",\n ...\n};\n```\n\n``` javascript\nwindow[\"sap-ui-config\"].frameOptions = \"deny\";\n```\n\n``` html\n\n```\n\n## Example\n\n### Setting the Frame Options to `\"allow\"`\n\nThis UI5 application explicitly allows to be embedded in other applications.\n\n```javascript\n\n\n \n ...\n \n\n \n \n ...\n\n```\n\n### Not Setting the Frame Options to Anything\n\nThe default value of `window[\"sap-ui-config\"]` and `data-sap-ui-frameOptions` are both `\"allow\"`, which makes leaving it untouched allows the application to be embedded.\n\n## References\n* OWASP: [Clickjacking Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html).\n* Mozilla: [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).\n* SAP UI5 Documentation: [Frame Options](https://sapui5.hana.ondemand.com/sdk/#/topic/62d9c4d8f5ad49aa914624af9551beb7.html).\n* SAP UI5 Documentation: [Allowlist Service](https://sapui5.hana.ondemand.com/sdk/#/topic/d04a6d41480c4396af16b5d2b25509ec.html).\n* Common Weakness Enumeration: [CWE-451](https://cwe.mitre.org/data/definitions/451.html).\n" + "text" : "# CQL query built from user-controlled sources\n\nIf a database query is built from user-provided data without sufficient sanitization, a malicious user may be able to run malicious database queries.\n\n## Recommendation\n\nCAP's intrinsic data querying engine is immune with regards to SQL injections that are introduced by query parameter values that are derived from malicious user input. CQL statements are transformed into prepared statements that are executed in SQL databases such as SAP HANA. \nInjections are still possible even via CQL when the query structure (e.g. target entity, columns etc.) is based on user input.\n\n## Examples\n\nThis CAP application uses user submitted input as entity and column in a CQL query without any validation.\n\n``` javascript\nconst entity = \nconst column = \nSELECT.from(entity).columns(column)\n```\n\n## References\n\n- OWASP: [Log Injection](https://owasp.org/www-community/attacks/Log_Injection).\n- OWASP: [Log Injection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html).\n- SAP CAPire Documentation: [Security Aspects](https://cap.cloud.sap/docs/guides/security/aspects#common-injection-attacks).\n", + "markdown" : "# CQL query built from user-controlled sources\n\nIf a database query is built from user-provided data without sufficient sanitization, a malicious user may be able to run malicious database queries.\n\n## Recommendation\n\nCAP's intrinsic data querying engine is immune with regards to SQL injections that are introduced by query parameter values that are derived from malicious user input. CQL statements are transformed into prepared statements that are executed in SQL databases such as SAP HANA. \nInjections are still possible even via CQL when the query structure (e.g. target entity, columns etc.) is based on user input.\n\n## Examples\n\nThis CAP application uses user submitted input as entity and column in a CQL query without any validation.\n\n``` javascript\nconst entity = \nconst column = \nSELECT.from(entity).columns(column)\n```\n\n## References\n\n- OWASP: [Log Injection](https://owasp.org/www-community/attacks/Log_Injection).\n- OWASP: [Log Injection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html).\n- SAP CAPire Documentation: [Security Aspects](https://cap.cloud.sap/docs/guides/security/aspects#common-injection-attacks).\n" }, "properties" : { - "tags" : [ "security", "external/cwe/cwe-451" ], - "description" : "The absence of frame options allows for clickjacking.", - "id" : "js/ui5-clickjacking", - "kind" : "problem", - "name" : "UI5 Clickjacking", + "tags" : [ "security" ], + "description" : "Building a CQL query from user-controlled sources is vulnerable to insertion of\n malicious code by the user.", + "id" : "js/cap-sql-injection", + "kind" : "path-problem", + "name" : "CQL query built from user-controlled sources", + "precision" : "high", + "problem.severity" : "error", + "security-severity" : "8.8" + } + }, { + "id" : "js/cap-log-injection", + "name" : "js/cap-log-injection", + "shortDescription" : { + "text" : "CAP Log injection" + }, + "fullDescription" : { + "text" : "Building log entries from user-controlled sources is vulnerable to insertion of forged log entries by a malicious user." + }, + "defaultConfiguration" : { + "enabled" : true, + "level" : "error" + }, + "help" : { + "text" : "# CAP Log Injection\n\nIf unsanitized user input is written to a log entry using the CAP Node.js logging API, a malicious user may be able to forge new log entries.\n\nCAP Node.js offers a CLRF-safe logging API that should be used for application log entries that are logged as plaintext. If the entry is interpreted as HTML, then arbitrary HTML code my be included to forge log entries.\n\n## Recommendation\n\nCAP applications need to care for escaping user data that is used as input parameter for application logging. It's recommended to make use of an existing Encoder such as OWASP ESAPI.\n\n## Examples\n\nThis CAP service directly logs what the user submitted via the `req` request.\n\n``` javascript\nimport cds from '@sap/cds'\nconst { Books } = cds.entities ('sap.capire.bookshop')\n\nclass SampleVulnService extends cds.ApplicationService { init(){\n this.on ('submitOrder', async req => {\n const {book,quantity} = req.data\n const LOG = cds.log(\"nodejs\");\n LOG.info(\"test\" + book); // Log injection alert\n })\n\n return super.init()\n}}\n```\n\n## References\n\n- OWASP: [Log Injection](https://owasp.org/www-community/attacks/Log_Injection).\n- OWASP: [Log Injection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html).\n- SAP CAPire Documentation: [Security Aspects](https://cap.cloud.sap/docs/guides/security/aspects#common-injection-attacks).\n", + "markdown" : "# CAP Log Injection\n\nIf unsanitized user input is written to a log entry using the CAP Node.js logging API, a malicious user may be able to forge new log entries.\n\nCAP Node.js offers a CLRF-safe logging API that should be used for application log entries that are logged as plaintext. If the entry is interpreted as HTML, then arbitrary HTML code my be included to forge log entries.\n\n## Recommendation\n\nCAP applications need to care for escaping user data that is used as input parameter for application logging. It's recommended to make use of an existing Encoder such as OWASP ESAPI.\n\n## Examples\n\nThis CAP service directly logs what the user submitted via the `req` request.\n\n``` javascript\nimport cds from '@sap/cds'\nconst { Books } = cds.entities ('sap.capire.bookshop')\n\nclass SampleVulnService extends cds.ApplicationService { init(){\n this.on ('submitOrder', async req => {\n const {book,quantity} = req.data\n const LOG = cds.log(\"nodejs\");\n LOG.info(\"test\" + book); // Log injection alert\n })\n\n return super.init()\n}}\n```\n\n## References\n\n- OWASP: [Log Injection](https://owasp.org/www-community/attacks/Log_Injection).\n- OWASP: [Log Injection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html).\n- SAP CAPire Documentation: [Security Aspects](https://cap.cloud.sap/docs/guides/security/aspects#common-injection-attacks).\n" + }, + "properties" : { + "tags" : [ "security" ], + "description" : "Building log entries from user-controlled sources is vulnerable to\n insertion of forged log entries by a malicious user.", + "id" : "js/cap-log-injection", + "kind" : "path-problem", + "name" : "CAP Log injection", "precision" : "medium", "problem.severity" : "error", "security-severity" : "6.1" } + } ], + "locations" : [ { + "uri" : "file:///home/runner/work/codeql-sap-js/codeql-sap-js/javascript/frameworks/cap/src/", + "description" : { + "text" : "The QL pack root directory." + } }, { + "uri" : "file:///home/runner/work/codeql-sap-js/codeql-sap-js/javascript/frameworks/cap/src/qlpack.yml", + "description" : { + "text" : "The QL pack definition file." + } + } ] + }, { + "name" : "advanced-security/javascript-sap-ui5-queries", + "semanticVersion" : "0.5.0+b1fbd510971da2b65671de1296cb2c3288f6b252", + "rules" : [ { "id" : "js/ui5-xss", "name" : "js/ui5-xss", "shortDescription" : { @@ -180,90 +222,65 @@ "problem.severity" : "error", "security-severity" : "7.8" } - } ], - "locations" : [ { - "uri" : "file:///home/runner/work/codeql-sap-js/codeql-sap-js/javascript/frameworks/ui5/src/", - "description" : { - "text" : "The QL pack root directory." - } - }, { - "uri" : "file:///home/runner/work/codeql-sap-js/codeql-sap-js/javascript/frameworks/ui5/src/qlpack.yml", - "description" : { - "text" : "The QL pack definition file." - } - } ] - }, { - "name" : "advanced-security/javascript-sap-cap-queries", - "semanticVersion" : "0.1.0+6519bc2c9f3d9be292ae8322aa3ea18c081bb38b", - "rules" : [ { - "id" : "js/cap-sql-injection", - "name" : "js/cap-sql-injection", - "shortDescription" : { - "text" : "CQL query built from user-controlled sources" - }, - "fullDescription" : { - "text" : "Building a CQL query from user-controlled sources is vulnerable to insertion of malicious code by the user." - }, - "defaultConfiguration" : { - "enabled" : true, - "level" : "error" - }, - "help" : { - "text" : "# SQL Injection\n\nIf a database query is built from user-provided data without sufficient sanitization, a malicious user may be able to run malicious database queries.\n\n## Recommendation\n\nCAP's intrinsic data querying engine is immune with regards to SQL injections that are introduced by query parameter values that are derived from malicious user input. CQL statements are transformed into prepared statements that are executed in SQL databases such as SAP HANA. \nInjections are still possible even via CQL when the query structure (e.g. target entity, columns etc.) is based on user input.\n\n## Examples\n\nThis CAP application uses user submitted input as entity and column in a CQL query without any validation.\n\n``` javascript\nconst entity = \nconst column = \nSELECT.from(entity).columns(column)\n```\n\n## References\n\n- OWASP: [Log Injection](https://owasp.org/www-community/attacks/Log_Injection).\n- OWASP: [Log Injection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html).\n- SAP CAPire Documentation: [Security Aspects](https://cap.cloud.sap/docs/guides/security/aspects#common-injection-attacks).\n", - "markdown" : "# SQL Injection\n\nIf a database query is built from user-provided data without sufficient sanitization, a malicious user may be able to run malicious database queries.\n\n## Recommendation\n\nCAP's intrinsic data querying engine is immune with regards to SQL injections that are introduced by query parameter values that are derived from malicious user input. CQL statements are transformed into prepared statements that are executed in SQL databases such as SAP HANA. \nInjections are still possible even via CQL when the query structure (e.g. target entity, columns etc.) is based on user input.\n\n## Examples\n\nThis CAP application uses user submitted input as entity and column in a CQL query without any validation.\n\n``` javascript\nconst entity = \nconst column = \nSELECT.from(entity).columns(column)\n```\n\n## References\n\n- OWASP: [Log Injection](https://owasp.org/www-community/attacks/Log_Injection).\n- OWASP: [Log Injection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html).\n- SAP CAPire Documentation: [Security Aspects](https://cap.cloud.sap/docs/guides/security/aspects#common-injection-attacks).\n" - }, - "properties" : { - "tags" : [ "security" ], - "description" : "Building a CQL query from user-controlled sources is vulnerable to insertion of\n malicious code by the user.", - "id" : "js/cap-sql-injection", - "kind" : "path-problem", - "name" : "CQL query built from user-controlled sources", - "precision" : "high", - "problem.severity" : "error", - "security-severity" : "8.8" - } }, { - "id" : "js/cap-log-injection", - "name" : "js/cap-log-injection", + "id" : "js/ui5-clickjacking", + "name" : "js/ui5-clickjacking", "shortDescription" : { - "text" : "CAP Log injection" + "text" : "UI5 Clickjacking" }, "fullDescription" : { - "text" : "Building log entries from user-controlled sources is vulnerable to insertion of forged log entries by a malicious user." + "text" : "The absence of frame options allows for clickjacking." }, "defaultConfiguration" : { "enabled" : true, "level" : "error" }, "help" : { - "text" : "# Log Injection\n\nIf unsanitized user input is written to a log entry using the CAP Node.js logging API, a malicious user may be able to forge new log entries.\n\nCAP Node.js offers a CLRF-safe logging API that should be used for application log entries that are logged as plaintext. If the entry is interpreted as HTML, then arbitrary HTML code my be included to forge log entries.\n\n## Recommendation\n\nCAP applications need to care for escaping user data that is used as input parameter for application logging. It's recommended to make use of an existing Encoder such as OWASP ESAPI.\n\n## Examples\n\nThis CAP service directly logs what the user submitted via the `req` request.\n\n``` javascript\nimport cds from '@sap/cds'\nconst { Books } = cds.entities ('sap.capire.bookshop')\n\nclass SampleVulnService extends cds.ApplicationService { init(){\n this.on ('submitOrder', async req => {\n const {book,quantity} = req.data\n const LOG = cds.log(\"nodejs\");\n LOG.info(\"test\" + book); // Log injection alert\n })\n\n return super.init()\n}}\n```\n\n## References\n\n- OWASP: [Log Injection](https://owasp.org/www-community/attacks/Log_Injection).\n- OWASP: [Log Injection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html).\n- SAP CAPire Documentation: [Security Aspects](https://cap.cloud.sap/docs/guides/security/aspects#common-injection-attacks).\n", - "markdown" : "# Log Injection\n\nIf unsanitized user input is written to a log entry using the CAP Node.js logging API, a malicious user may be able to forge new log entries.\n\nCAP Node.js offers a CLRF-safe logging API that should be used for application log entries that are logged as plaintext. If the entry is interpreted as HTML, then arbitrary HTML code my be included to forge log entries.\n\n## Recommendation\n\nCAP applications need to care for escaping user data that is used as input parameter for application logging. It's recommended to make use of an existing Encoder such as OWASP ESAPI.\n\n## Examples\n\nThis CAP service directly logs what the user submitted via the `req` request.\n\n``` javascript\nimport cds from '@sap/cds'\nconst { Books } = cds.entities ('sap.capire.bookshop')\n\nclass SampleVulnService extends cds.ApplicationService { init(){\n this.on ('submitOrder', async req => {\n const {book,quantity} = req.data\n const LOG = cds.log(\"nodejs\");\n LOG.info(\"test\" + book); // Log injection alert\n })\n\n return super.init()\n}}\n```\n\n## References\n\n- OWASP: [Log Injection](https://owasp.org/www-community/attacks/Log_Injection).\n- OWASP: [Log Injection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html).\n- SAP CAPire Documentation: [Security Aspects](https://cap.cloud.sap/docs/guides/security/aspects#common-injection-attacks).\n" + "text" : "# Clickjacking\n\nUI5 applications that do not explicitly set the frame options to `deny` may be vulnerable to UI redress attacks (”clickjacking”). In these attacks, the vulnerable site is loaded in a frame on an attacker-controlled site which uses opaque or transparent layers to trick the user into unintentionally clicking a button or link on the vulnerable site.\n\n## Recommendation\n\nExplicitly set the frame options to `\"deny\"`, either through `window[\"sap-ui-config\"]`, or `data-sap-ui-frameOptions` attribute of the script tag where it sources the bootstrap script `\"sap-ui-core.js\"`:\n\n``` javascript\nwindow[\"sap-ui-config\"] = {\n frameOptions: \"deny\",\n ...\n};\n```\n\n``` javascript\nwindow[\"sap-ui-config\"].frameOptions = \"deny\";\n```\n\n``` html\n\n```\n\n## Example\n\n### Setting the Frame Options to `\"allow\"`\n\nThis UI5 application explicitly allows to be embedded in other applications.\n\n```javascript\n\n\n \n ...\n \n\n \n \n ...\n\n```\n\n### Not Setting the Frame Options to Anything\n\nThe default value of `window[\"sap-ui-config\"]` and `data-sap-ui-frameOptions` are both `\"allow\"`, which makes leaving it untouched allows the application to be embedded.\n\n## References\n* OWASP: [Clickjacking Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html).\n* Mozilla: [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).\n* SAP UI5 Documentation: [Frame Options](https://sapui5.hana.ondemand.com/sdk/#/topic/62d9c4d8f5ad49aa914624af9551beb7.html).\n* SAP UI5 Documentation: [Allowlist Service](https://sapui5.hana.ondemand.com/sdk/#/topic/d04a6d41480c4396af16b5d2b25509ec.html).\n* Common Weakness Enumeration: [CWE-451](https://cwe.mitre.org/data/definitions/451.html).\n", + "markdown" : "# Clickjacking\n\nUI5 applications that do not explicitly set the frame options to `deny` may be vulnerable to UI redress attacks (”clickjacking”). In these attacks, the vulnerable site is loaded in a frame on an attacker-controlled site which uses opaque or transparent layers to trick the user into unintentionally clicking a button or link on the vulnerable site.\n\n## Recommendation\n\nExplicitly set the frame options to `\"deny\"`, either through `window[\"sap-ui-config\"]`, or `data-sap-ui-frameOptions` attribute of the script tag where it sources the bootstrap script `\"sap-ui-core.js\"`:\n\n``` javascript\nwindow[\"sap-ui-config\"] = {\n frameOptions: \"deny\",\n ...\n};\n```\n\n``` javascript\nwindow[\"sap-ui-config\"].frameOptions = \"deny\";\n```\n\n``` html\n\n```\n\n## Example\n\n### Setting the Frame Options to `\"allow\"`\n\nThis UI5 application explicitly allows to be embedded in other applications.\n\n```javascript\n\n\n \n ...\n \n\n \n \n ...\n\n```\n\n### Not Setting the Frame Options to Anything\n\nThe default value of `window[\"sap-ui-config\"]` and `data-sap-ui-frameOptions` are both `\"allow\"`, which makes leaving it untouched allows the application to be embedded.\n\n## References\n* OWASP: [Clickjacking Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html).\n* Mozilla: [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).\n* SAP UI5 Documentation: [Frame Options](https://sapui5.hana.ondemand.com/sdk/#/topic/62d9c4d8f5ad49aa914624af9551beb7.html).\n* SAP UI5 Documentation: [Allowlist Service](https://sapui5.hana.ondemand.com/sdk/#/topic/d04a6d41480c4396af16b5d2b25509ec.html).\n* Common Weakness Enumeration: [CWE-451](https://cwe.mitre.org/data/definitions/451.html).\n" }, "properties" : { - "tags" : [ "security" ], - "description" : "Building log entries from user-controlled sources is vulnerable to\n insertion of forged log entries by a malicious user.", - "id" : "js/cap-log-injection", - "kind" : "path-problem", - "name" : "CAP Log injection", + "tags" : [ "security", "external/cwe/cwe-451" ], + "description" : "The absence of frame options allows for clickjacking.", + "id" : "js/ui5-clickjacking", + "kind" : "problem", + "name" : "UI5 Clickjacking", "precision" : "medium", "problem.severity" : "error", "security-severity" : "6.1" } } ], "locations" : [ { - "uri" : "file:///home/runner/work/codeql-sap-js/codeql-sap-js/javascript/frameworks/cap/src/", + "uri" : "file:///home/runner/work/codeql-sap-js/codeql-sap-js/javascript/frameworks/ui5/src/", "description" : { "text" : "The QL pack root directory." } }, { - "uri" : "file:///home/runner/work/codeql-sap-js/codeql-sap-js/javascript/frameworks/cap/src/qlpack.yml", + "uri" : "file:///home/runner/work/codeql-sap-js/codeql-sap-js/javascript/frameworks/ui5/src/qlpack.yml", "description" : { "text" : "The QL pack definition file." } } ] + }, { + "name" : "generated/extension-pack", + "semanticVersion" : "0.0.0", + "locations" : [ { + "uri" : "file:///home/runner/work/_temp/codeql_databases/javascript/temp/extension-pack/", + "description" : { + "text" : "The QL pack root directory." + } + }, { + "uri" : "file:///home/runner/work/_temp/codeql_databases/javascript/temp/extension-pack/codeql-pack.yml", + "description" : { + "text" : "The QL pack definition file." + } + } ], + "properties" : { + "isCodeQLModelPack" : true + } }, { "name" : "codeql/javascript-queries", - "semanticVersion" : "0.8.9+8a00a45b32679ddced400ab256706c79c1169e38", + "semanticVersion" : "0.8.10+2daf50500ca8f7eb914c82e88dec36652bfbe8fd", "notifications" : [ { "id" : "js/diagnostics/extraction-errors", "name" : "js/diagnostics/extraction-errors", @@ -303,1020 +320,966 @@ } } ], "rules" : [ { - "id" : "js/disabling-electron-websecurity", - "name" : "js/disabling-electron-websecurity", + "id" : "js/unsafe-external-link", + "name" : "js/unsafe-external-link", "shortDescription" : { - "text" : "Disabling Electron webSecurity" + "text" : "Potentially unsafe external link" }, "fullDescription" : { - "text" : "Disabling webSecurity can cause critical security vulnerabilities." + "text" : "External links that open in a new tab or window but do not specify link type 'noopener' or 'noreferrer' are a potential security risk." }, "defaultConfiguration" : { "enabled" : true, - "level" : "error" + "level" : "warning" }, "help" : { - "text" : "# Disabling Electron webSecurity\nElectron is secure by default through a same-origin policy requiring all JavaScript and CSS code to originate from the machine running the Electron application. Setting the `webSecurity` property of a `webPreferences` object to `false` will disable the same-origin policy.\n\nDisabling the same-origin policy is strongly discouraged.\n\n\n## Recommendation\nDo not disable `webSecurity`.\n\n\n## Example\nThe following example shows `webSecurity` being disabled.\n\n\n```javascript\nconst mainWindow = new BrowserWindow({\n webPreferences: {\n webSecurity: false\n }\n})\n```\nThis is problematic, since it allows the execution of insecure code from other domains.\n\n\n## References\n* Electron Documentation: [Security, Native Capabilities, and Your Responsibility](https://electronjs.org/docs/tutorial/security#5-do-not-disable-websecurity)\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n", - "markdown" : "# Disabling Electron webSecurity\nElectron is secure by default through a same-origin policy requiring all JavaScript and CSS code to originate from the machine running the Electron application. Setting the `webSecurity` property of a `webPreferences` object to `false` will disable the same-origin policy.\n\nDisabling the same-origin policy is strongly discouraged.\n\n\n## Recommendation\nDo not disable `webSecurity`.\n\n\n## Example\nThe following example shows `webSecurity` being disabled.\n\n\n```javascript\nconst mainWindow = new BrowserWindow({\n webPreferences: {\n webSecurity: false\n }\n})\n```\nThis is problematic, since it allows the execution of insecure code from other domains.\n\n\n## References\n* Electron Documentation: [Security, Native Capabilities, and Your Responsibility](https://electronjs.org/docs/tutorial/security#5-do-not-disable-websecurity)\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n" + "text" : "# Potentially unsafe external link\nHTML links that open in a new tab or window allow the target page to access the DOM of the origin page using `window.opener` unless link type `noopener` or `noreferrer` is specified. This is a potential security risk.\n\n\n## Recommendation\nSpecify the link type by adding an attribute `rel=\"noopener noreferrer\"`.\n\n\n## Example\nIn the following example, a JSX element is created that corresponds to an HTML link opening the URL `http://example.com` in a new tab. Since it does not specify a link type, that page will be able to access the DOM of the origin page.\n\n\n```javascript\nvar link = Example;\n\n```\nTo fix this vulnerability, add a `rel` attribute:\n\n\n```javascript\nvar link = Example;\n\n```\n\n## References\n* Mathias Bynens: [About rel=noopener](https://mathiasbynens.github.io/rel-noopener/).\n* Mozilla Developer Network: [HTML Anchor Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a).\n* Common Weakness Enumeration: [CWE-200](https://cwe.mitre.org/data/definitions/200.html).\n* Common Weakness Enumeration: [CWE-1022](https://cwe.mitre.org/data/definitions/1022.html).\n", + "markdown" : "# Potentially unsafe external link\nHTML links that open in a new tab or window allow the target page to access the DOM of the origin page using `window.opener` unless link type `noopener` or `noreferrer` is specified. This is a potential security risk.\n\n\n## Recommendation\nSpecify the link type by adding an attribute `rel=\"noopener noreferrer\"`.\n\n\n## Example\nIn the following example, a JSX element is created that corresponds to an HTML link opening the URL `http://example.com` in a new tab. Since it does not specify a link type, that page will be able to access the DOM of the origin page.\n\n\n```javascript\nvar link = Example;\n\n```\nTo fix this vulnerability, add a `rel` attribute:\n\n\n```javascript\nvar link = Example;\n\n```\n\n## References\n* Mathias Bynens: [About rel=noopener](https://mathiasbynens.github.io/rel-noopener/).\n* Mozilla Developer Network: [HTML Anchor Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a).\n* Common Weakness Enumeration: [CWE-200](https://cwe.mitre.org/data/definitions/200.html).\n* Common Weakness Enumeration: [CWE-1022](https://cwe.mitre.org/data/definitions/1022.html).\n" }, "properties" : { - "tags" : [ "security", "frameworks/electron", "external/cwe/cwe-79" ], - "description" : "Disabling webSecurity can cause critical security vulnerabilities.", - "id" : "js/disabling-electron-websecurity", + "tags" : [ "maintainability", "security", "external/cwe/cwe-200", "external/cwe/cwe-1022" ], + "description" : "External links that open in a new tab or window but do not specify\n link type 'noopener' or 'noreferrer' are a potential security risk.", + "id" : "js/unsafe-external-link", "kind" : "problem", - "name" : "Disabling Electron webSecurity", + "name" : "Potentially unsafe external link", "precision" : "very-high", - "problem.severity" : "error", - "security-severity" : "6.1" + "problem.severity" : "warning", + "security-severity" : "6.5" } }, { - "id" : "js/enabling-electron-insecure-content", - "name" : "js/enabling-electron-insecure-content", + "id" : "js/clear-text-cookie", + "name" : "js/clear-text-cookie", "shortDescription" : { - "text" : "Enabling Electron allowRunningInsecureContent" + "text" : "Clear text transmission of sensitive cookie" }, "fullDescription" : { - "text" : "Enabling allowRunningInsecureContent can allow remote code execution." + "text" : "Sending sensitive information in a cookie without requring SSL encryption can expose the cookie to an attacker." }, "defaultConfiguration" : { "enabled" : true, - "level" : "error" + "level" : "warning" }, "help" : { - "text" : "# Enabling Electron allowRunningInsecureContent\nElectron is secure by default through a policy banning the execution of content loaded over HTTP. Setting the `allowRunningInsecureContent` property of a `webPreferences` object to `true` will disable this policy.\n\nEnabling the execution of insecure content is strongly discouraged.\n\n\n## Recommendation\nDo not enable the `allowRunningInsecureContent` property.\n\n\n## Example\nThe following example shows `allowRunningInsecureContent` being enabled.\n\n\n```javascript\nconst mainWindow = new BrowserWindow({\n webPreferences: {\n allowRunningInsecureContent: true\n }\n})\n```\nThis is problematic, since it allows the execution of code from an untrusted origin.\n\n\n## References\n* Electron Documentation: [Security, Native Capabilities, and Your Responsibility](https://electronjs.org/docs/tutorial/security#8-do-not-set-allowrunninginsecurecontent-to-true)\n* Common Weakness Enumeration: [CWE-494](https://cwe.mitre.org/data/definitions/494.html).\n", - "markdown" : "# Enabling Electron allowRunningInsecureContent\nElectron is secure by default through a policy banning the execution of content loaded over HTTP. Setting the `allowRunningInsecureContent` property of a `webPreferences` object to `true` will disable this policy.\n\nEnabling the execution of insecure content is strongly discouraged.\n\n\n## Recommendation\nDo not enable the `allowRunningInsecureContent` property.\n\n\n## Example\nThe following example shows `allowRunningInsecureContent` being enabled.\n\n\n```javascript\nconst mainWindow = new BrowserWindow({\n webPreferences: {\n allowRunningInsecureContent: true\n }\n})\n```\nThis is problematic, since it allows the execution of code from an untrusted origin.\n\n\n## References\n* Electron Documentation: [Security, Native Capabilities, and Your Responsibility](https://electronjs.org/docs/tutorial/security#8-do-not-set-allowrunninginsecurecontent-to-true)\n* Common Weakness Enumeration: [CWE-494](https://cwe.mitre.org/data/definitions/494.html).\n" + "text" : "# Clear text transmission of sensitive cookie\nCookies that are transmitted in clear text can be intercepted by an attacker. If sensitive cookies are intercepted, the attacker can read the cookie and use it to perform actions on the user's behalf.\n\n\n## Recommendation\nAlways transmit sensitive cookies using SSL by setting the `secure` attribute on the cookie.\n\n\n## Example\nThe following example stores an authentication token in a cookie that can be transmitted in clear text.\n\n\n```javascript\nconst http = require('http');\n\nconst server = http.createServer((req, res) => {\n res.setHeader(\"Set-Cookie\", `authKey=${makeAuthkey()}`);\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end('

Hello world

');\n});\n```\nTo force the cookie to be transmitted using SSL, set the `secure` attribute on the cookie.\n\n\n```javascript\nconst http = require('http');\n\nconst server = http.createServer((req, res) => {\n res.setHeader(\"Set-Cookie\", `authKey=${makeAuthkey()}; secure; httpOnly`);\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end('

Hello world

');\n});\n```\n\n## References\n* ExpressJS: [Use cookies securely](https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely).\n* OWASP: [Set cookie flags appropriately](https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#set-cookie-flags-appropriately).\n* Mozilla: [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).\n* Common Weakness Enumeration: [CWE-614](https://cwe.mitre.org/data/definitions/614.html).\n* Common Weakness Enumeration: [CWE-311](https://cwe.mitre.org/data/definitions/311.html).\n* Common Weakness Enumeration: [CWE-312](https://cwe.mitre.org/data/definitions/312.html).\n* Common Weakness Enumeration: [CWE-319](https://cwe.mitre.org/data/definitions/319.html).\n", + "markdown" : "# Clear text transmission of sensitive cookie\nCookies that are transmitted in clear text can be intercepted by an attacker. If sensitive cookies are intercepted, the attacker can read the cookie and use it to perform actions on the user's behalf.\n\n\n## Recommendation\nAlways transmit sensitive cookies using SSL by setting the `secure` attribute on the cookie.\n\n\n## Example\nThe following example stores an authentication token in a cookie that can be transmitted in clear text.\n\n\n```javascript\nconst http = require('http');\n\nconst server = http.createServer((req, res) => {\n res.setHeader(\"Set-Cookie\", `authKey=${makeAuthkey()}`);\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end('

Hello world

');\n});\n```\nTo force the cookie to be transmitted using SSL, set the `secure` attribute on the cookie.\n\n\n```javascript\nconst http = require('http');\n\nconst server = http.createServer((req, res) => {\n res.setHeader(\"Set-Cookie\", `authKey=${makeAuthkey()}; secure; httpOnly`);\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end('

Hello world

');\n});\n```\n\n## References\n* ExpressJS: [Use cookies securely](https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely).\n* OWASP: [Set cookie flags appropriately](https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#set-cookie-flags-appropriately).\n* Mozilla: [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).\n* Common Weakness Enumeration: [CWE-614](https://cwe.mitre.org/data/definitions/614.html).\n* Common Weakness Enumeration: [CWE-311](https://cwe.mitre.org/data/definitions/311.html).\n* Common Weakness Enumeration: [CWE-312](https://cwe.mitre.org/data/definitions/312.html).\n* Common Weakness Enumeration: [CWE-319](https://cwe.mitre.org/data/definitions/319.html).\n" }, "properties" : { - "tags" : [ "security", "frameworks/electron", "external/cwe/cwe-494" ], - "description" : "Enabling allowRunningInsecureContent can allow remote code execution.", - "id" : "js/enabling-electron-insecure-content", + "tags" : [ "security", "external/cwe/cwe-614", "external/cwe/cwe-311", "external/cwe/cwe-312", "external/cwe/cwe-319" ], + "description" : "Sending sensitive information in a cookie without requring SSL encryption\n can expose the cookie to an attacker.", + "id" : "js/clear-text-cookie", "kind" : "problem", - "name" : "Enabling Electron allowRunningInsecureContent", - "precision" : "very-high", - "problem.severity" : "error", - "security-severity" : "8.8" + "name" : "Clear text transmission of sensitive cookie", + "precision" : "high", + "problem.severity" : "warning", + "security-severity" : "5.0" } }, { - "id" : "js/unsafe-external-link", - "name" : "js/unsafe-external-link", + "id" : "js/incomplete-sanitization", + "name" : "js/incomplete-sanitization", "shortDescription" : { - "text" : "Potentially unsafe external link" + "text" : "Incomplete string escaping or encoding" }, "fullDescription" : { - "text" : "External links that open in a new tab or window but do not specify link type 'noopener' or 'noreferrer' are a potential security risk." + "text" : "A string transformer that does not replace or escape all occurrences of a meta-character may be ineffective." }, "defaultConfiguration" : { "enabled" : true, "level" : "warning" }, "help" : { - "text" : "# Potentially unsafe external link\nHTML links that open in a new tab or window allow the target page to access the DOM of the origin page using `window.opener` unless link type `noopener` or `noreferrer` is specified. This is a potential security risk.\n\n\n## Recommendation\nSpecify the link type by adding an attribute `rel=\"noopener noreferrer\"`.\n\n\n## Example\nIn the following example, a JSX element is created that corresponds to an HTML link opening the URL `http://example.com` in a new tab. Since it does not specify a link type, that page will be able to access the DOM of the origin page.\n\n\n```javascript\nvar link = Example;\n\n```\nTo fix this vulnerability, add a `rel` attribute:\n\n\n```javascript\nvar link = Example;\n\n```\n\n## References\n* Mathias Bynens: [About rel=noopener](https://mathiasbynens.github.io/rel-noopener/).\n* Mozilla Developer Network: [HTML Anchor Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a).\n* Common Weakness Enumeration: [CWE-200](https://cwe.mitre.org/data/definitions/200.html).\n* Common Weakness Enumeration: [CWE-1022](https://cwe.mitre.org/data/definitions/1022.html).\n", - "markdown" : "# Potentially unsafe external link\nHTML links that open in a new tab or window allow the target page to access the DOM of the origin page using `window.opener` unless link type `noopener` or `noreferrer` is specified. This is a potential security risk.\n\n\n## Recommendation\nSpecify the link type by adding an attribute `rel=\"noopener noreferrer\"`.\n\n\n## Example\nIn the following example, a JSX element is created that corresponds to an HTML link opening the URL `http://example.com` in a new tab. Since it does not specify a link type, that page will be able to access the DOM of the origin page.\n\n\n```javascript\nvar link = Example;\n\n```\nTo fix this vulnerability, add a `rel` attribute:\n\n\n```javascript\nvar link = Example;\n\n```\n\n## References\n* Mathias Bynens: [About rel=noopener](https://mathiasbynens.github.io/rel-noopener/).\n* Mozilla Developer Network: [HTML Anchor Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a).\n* Common Weakness Enumeration: [CWE-200](https://cwe.mitre.org/data/definitions/200.html).\n* Common Weakness Enumeration: [CWE-1022](https://cwe.mitre.org/data/definitions/1022.html).\n" + "text" : "# Incomplete string escaping or encoding\nSanitizing untrusted input is a common technique for preventing injection attacks such as SQL injection or cross-site scripting. Usually, this is done by escaping meta-characters such as quotes in a domain-specific way so that they are treated as normal characters.\n\nHowever, directly using the string `replace` method to perform escaping is notoriously error-prone. Common mistakes include only replacing the first occurrence of a meta-character, or backslash-escaping various meta-characters but not the backslash itself.\n\nIn the former case, later meta-characters are left undisturbed and can be used to subvert the sanitization. In the latter case, preceding a meta-character with a backslash leads to the backslash being escaped, but the meta-character appearing un-escaped, which again makes the sanitization ineffective.\n\nEven if the escaped string is not used in a security-critical context, incomplete escaping may still have undesirable effects, such as badly rendered or confusing output.\n\n\n## Recommendation\nUse a (well-tested) sanitization library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation.\n\nAn even safer alternative is to design the application so that sanitization is not needed, for instance by using prepared statements for SQL queries.\n\nOtherwise, make sure to use a regular expression with the `g` flag to ensure that all occurrences are replaced, and remember to escape backslashes if applicable.\n\n\n## Example\nFor example, assume that we want to embed a user-controlled string `accountNumber` into a SQL query as part of a string literal. To avoid SQL injection, we need to ensure that the string does not contain un-escaped single-quote characters. The following function attempts to ensure this by doubling single quotes, and thereby escaping them:\n\n\n```javascript\nfunction escapeQuotes(s) {\n return s.replace(\"'\", \"''\");\n}\n\n```\nAs written, this sanitizer is ineffective: if the first argument to `replace` is a string literal (as in this case), only the *first* occurrence of that string is replaced.\n\nAs mentioned above, the function `escapeQuotes` should be replaced with a purpose-built sanitization library, such as the npm module `sqlstring`. Many other sanitization libraries are available from npm and other sources.\n\nIf this is not an option, `escapeQuotes` should be rewritten to use a regular expression with the `g` (\"global\") flag instead:\n\n\n```javascript\nfunction escapeQuotes(s) {\n return s.replace(/'/g, \"''\");\n}\n\n```\nNote that it is very important to include the global flag: `s.replace(/'/, \"''\")` *without* the global flag is equivalent to the first example above and only replaces the first quote.\n\n\n## References\n* OWASP Top 10: [A1 Injection](https://www.owasp.org/index.php/Top_10-2017_A1-Injection).\n* npm: [sqlstring](https://www.npmjs.com/package/sqlstring) package.\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n* Common Weakness Enumeration: [CWE-80](https://cwe.mitre.org/data/definitions/80.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n", + "markdown" : "# Incomplete string escaping or encoding\nSanitizing untrusted input is a common technique for preventing injection attacks such as SQL injection or cross-site scripting. Usually, this is done by escaping meta-characters such as quotes in a domain-specific way so that they are treated as normal characters.\n\nHowever, directly using the string `replace` method to perform escaping is notoriously error-prone. Common mistakes include only replacing the first occurrence of a meta-character, or backslash-escaping various meta-characters but not the backslash itself.\n\nIn the former case, later meta-characters are left undisturbed and can be used to subvert the sanitization. In the latter case, preceding a meta-character with a backslash leads to the backslash being escaped, but the meta-character appearing un-escaped, which again makes the sanitization ineffective.\n\nEven if the escaped string is not used in a security-critical context, incomplete escaping may still have undesirable effects, such as badly rendered or confusing output.\n\n\n## Recommendation\nUse a (well-tested) sanitization library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation.\n\nAn even safer alternative is to design the application so that sanitization is not needed, for instance by using prepared statements for SQL queries.\n\nOtherwise, make sure to use a regular expression with the `g` flag to ensure that all occurrences are replaced, and remember to escape backslashes if applicable.\n\n\n## Example\nFor example, assume that we want to embed a user-controlled string `accountNumber` into a SQL query as part of a string literal. To avoid SQL injection, we need to ensure that the string does not contain un-escaped single-quote characters. The following function attempts to ensure this by doubling single quotes, and thereby escaping them:\n\n\n```javascript\nfunction escapeQuotes(s) {\n return s.replace(\"'\", \"''\");\n}\n\n```\nAs written, this sanitizer is ineffective: if the first argument to `replace` is a string literal (as in this case), only the *first* occurrence of that string is replaced.\n\nAs mentioned above, the function `escapeQuotes` should be replaced with a purpose-built sanitization library, such as the npm module `sqlstring`. Many other sanitization libraries are available from npm and other sources.\n\nIf this is not an option, `escapeQuotes` should be rewritten to use a regular expression with the `g` (\"global\") flag instead:\n\n\n```javascript\nfunction escapeQuotes(s) {\n return s.replace(/'/g, \"''\");\n}\n\n```\nNote that it is very important to include the global flag: `s.replace(/'/, \"''\")` *without* the global flag is equivalent to the first example above and only replaces the first quote.\n\n\n## References\n* OWASP Top 10: [A1 Injection](https://www.owasp.org/index.php/Top_10-2017_A1-Injection).\n* npm: [sqlstring](https://www.npmjs.com/package/sqlstring) package.\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n* Common Weakness Enumeration: [CWE-80](https://cwe.mitre.org/data/definitions/80.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n" }, "properties" : { - "tags" : [ "maintainability", "security", "external/cwe/cwe-200", "external/cwe/cwe-1022" ], - "description" : "External links that open in a new tab or window but do not specify\n link type 'noopener' or 'noreferrer' are a potential security risk.", - "id" : "js/unsafe-external-link", + "tags" : [ "correctness", "security", "external/cwe/cwe-020", "external/cwe/cwe-080", "external/cwe/cwe-116" ], + "description" : "A string transformer that does not replace or escape all occurrences of a\n meta-character may be ineffective.", + "id" : "js/incomplete-sanitization", "kind" : "problem", - "name" : "Potentially unsafe external link", - "precision" : "very-high", + "name" : "Incomplete string escaping or encoding", + "precision" : "high", "problem.severity" : "warning", - "security-severity" : "6.5" + "security-severity" : "7.8" } }, { - "id" : "js/angular/double-compilation", - "name" : "js/angular/double-compilation", + "id" : "js/unsafe-html-expansion", + "name" : "js/unsafe-html-expansion", "shortDescription" : { - "text" : "Double compilation" + "text" : "Unsafe expansion of self-closing HTML tag" }, "fullDescription" : { - "text" : "Recompiling an already compiled part of the DOM can lead to unexpected behavior of directives, performance problems, and memory leaks." + "text" : "Using regular expressions to expand self-closing HTML tags may lead to cross-site scripting vulnerabilities." }, "defaultConfiguration" : { "enabled" : true, "level" : "warning" }, "help" : { - "text" : "# Double compilation\nThe AngularJS compiler processes (parts of) the DOM, determining which directives match which DOM elements, and then applies the directives to the elements. Each DOM element should only be compiled once, otherwise unexpected behavior may result.\n\n\n## Recommendation\nOnly compile new DOM elements.\n\n\n## Example\nThe following example (adapted from the AngularJS developer guide) shows a directive that adds a tooltip to a DOM element, and then compiles the entire element to apply nested directives.\n\n\n```javascript\nangular.module('myapp')\n .directive('addToolTip', function($compile) {\n return {\n link: function(scope, element, attrs) {\n var tooltip = angular.element('A tooltip');\n tooltip.on('mouseenter mouseleave', function() {\n scope.$apply('showToolTip = !showToolTip');\n });\n element.append(tooltip);\n $compile(element)(scope); // NOT OK\n }\n };\n});\n\n```\nThis is problematic, since it will recompile all of `element`, including parts that have already been compiled.\n\nInstead, only the new element should be compiled:\n\n\n```javascript\nangular.module('myapp')\n .directive('addToolTip', function($compile) {\n return {\n link: function(scope, element, attrs) {\n var tooltip = angular.element('A tooltip');\n tooltip.on('mouseenter mouseleave', function() {\n scope.$apply('showToolTip = !showToolTip');\n });\n element.append(tooltip);\n $compile(tooltip)(scope); // OK\n }\n };\n});\n\n```\n\n## References\n* AngularJS Developer Guide: [Double Compilation, and how to avoid it](https://docs.angularjs.org/guide/compiler#double-compilation-and-how-to-avoid-it).\n* Common Weakness Enumeration: [CWE-1176](https://cwe.mitre.org/data/definitions/1176.html).\n", - "markdown" : "# Double compilation\nThe AngularJS compiler processes (parts of) the DOM, determining which directives match which DOM elements, and then applies the directives to the elements. Each DOM element should only be compiled once, otherwise unexpected behavior may result.\n\n\n## Recommendation\nOnly compile new DOM elements.\n\n\n## Example\nThe following example (adapted from the AngularJS developer guide) shows a directive that adds a tooltip to a DOM element, and then compiles the entire element to apply nested directives.\n\n\n```javascript\nangular.module('myapp')\n .directive('addToolTip', function($compile) {\n return {\n link: function(scope, element, attrs) {\n var tooltip = angular.element('A tooltip');\n tooltip.on('mouseenter mouseleave', function() {\n scope.$apply('showToolTip = !showToolTip');\n });\n element.append(tooltip);\n $compile(element)(scope); // NOT OK\n }\n };\n});\n\n```\nThis is problematic, since it will recompile all of `element`, including parts that have already been compiled.\n\nInstead, only the new element should be compiled:\n\n\n```javascript\nangular.module('myapp')\n .directive('addToolTip', function($compile) {\n return {\n link: function(scope, element, attrs) {\n var tooltip = angular.element('A tooltip');\n tooltip.on('mouseenter mouseleave', function() {\n scope.$apply('showToolTip = !showToolTip');\n });\n element.append(tooltip);\n $compile(tooltip)(scope); // OK\n }\n };\n});\n\n```\n\n## References\n* AngularJS Developer Guide: [Double Compilation, and how to avoid it](https://docs.angularjs.org/guide/compiler#double-compilation-and-how-to-avoid-it).\n* Common Weakness Enumeration: [CWE-1176](https://cwe.mitre.org/data/definitions/1176.html).\n" + "text" : "# Unsafe expansion of self-closing HTML tag\nSanitizing untrusted input for HTML meta-characters is a common technique for preventing cross-site scripting attacks. But even a sanitized input can be dangerous to use if it is modified further before a browser treats it as HTML. A seemingly innocent transformation that expands a self-closing HTML tag from `
` to `
` may in fact cause cross-site scripting vulnerabilities.\n\n\n## Recommendation\nUse a well-tested sanitization library if at all possible, and avoid modifying sanitized values further before treating them as HTML.\n\nAn even safer alternative is to design the application so that sanitization is not needed, for instance by using HTML templates that are explicit about the values they treat as HTML.\n\n\n## Example\nThe following function transforms a self-closing HTML tag to a pair of open/close tags. It does so for all non-`img` and non-`area` tags, by using a regular expression with two capture groups. The first capture group corresponds to the name of the tag, and the second capture group to the content of the tag.\n\n\n```javascript\nfunction expandSelfClosingTags(html) {\n\tvar rxhtmlTag = /<(?!img|area)(([a-z][^\\w\\/>]*)[^>]*)\\/>/gi;\n\treturn html.replace(rxhtmlTag, \"<$1>\"); // BAD\n}\n\n```\nWhile it is generally known regular expressions are ill-suited for parsing HTML, variants of this particular transformation pattern have long been considered safe.\n\nHowever, the function is not safe. As an example, consider the following string:\n\n\n```html\n
\n\"/>\n\n```\nWhen the above function transforms the string, it becomes a string that results in an alert when a browser treats it as HTML.\n\n\n```html\n
\n\"/>\n\n```\n\n## References\n* jQuery: [Security fixes in jQuery 3.5.0](https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/)\n* OWASP: [DOM based XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html).\n* OWASP: [XSS (Cross Site Scripting) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).\n* OWASP [Types of Cross-Site](https://owasp.org/www-community/Types_of_Cross-Site_Scripting).\n* Wikipedia: [Cross-site scripting](http://en.wikipedia.org/wiki/Cross-site_scripting).\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n", + "markdown" : "# Unsafe expansion of self-closing HTML tag\nSanitizing untrusted input for HTML meta-characters is a common technique for preventing cross-site scripting attacks. But even a sanitized input can be dangerous to use if it is modified further before a browser treats it as HTML. A seemingly innocent transformation that expands a self-closing HTML tag from `
` to `
` may in fact cause cross-site scripting vulnerabilities.\n\n\n## Recommendation\nUse a well-tested sanitization library if at all possible, and avoid modifying sanitized values further before treating them as HTML.\n\nAn even safer alternative is to design the application so that sanitization is not needed, for instance by using HTML templates that are explicit about the values they treat as HTML.\n\n\n## Example\nThe following function transforms a self-closing HTML tag to a pair of open/close tags. It does so for all non-`img` and non-`area` tags, by using a regular expression with two capture groups. The first capture group corresponds to the name of the tag, and the second capture group to the content of the tag.\n\n\n```javascript\nfunction expandSelfClosingTags(html) {\n\tvar rxhtmlTag = /<(?!img|area)(([a-z][^\\w\\/>]*)[^>]*)\\/>/gi;\n\treturn html.replace(rxhtmlTag, \"<$1>\"); // BAD\n}\n\n```\nWhile it is generally known regular expressions are ill-suited for parsing HTML, variants of this particular transformation pattern have long been considered safe.\n\nHowever, the function is not safe. As an example, consider the following string:\n\n\n```html\n
\n\"/>\n\n```\nWhen the above function transforms the string, it becomes a string that results in an alert when a browser treats it as HTML.\n\n\n```html\n
\n\"/>\n\n```\n\n## References\n* jQuery: [Security fixes in jQuery 3.5.0](https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/)\n* OWASP: [DOM based XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html).\n* OWASP: [XSS (Cross Site Scripting) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).\n* OWASP [Types of Cross-Site](https://owasp.org/www-community/Types_of_Cross-Site_Scripting).\n* Wikipedia: [Cross-site scripting](http://en.wikipedia.org/wiki/Cross-site_scripting).\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n" }, "properties" : { - "tags" : [ "reliability", "frameworks/angularjs", "security", "external/cwe/cwe-1176" ], - "description" : "Recompiling an already compiled part of the DOM can lead to\n unexpected behavior of directives, performance problems, and memory leaks.", - "id" : "js/angular/double-compilation", + "tags" : [ "correctness", "security", "external/cwe/cwe-079", "external/cwe/cwe-116" ], + "description" : "Using regular expressions to expand self-closing HTML\n tags may lead to cross-site scripting vulnerabilities.", + "id" : "js/unsafe-html-expansion", "kind" : "problem", - "name" : "Double compilation", + "name" : "Unsafe expansion of self-closing HTML tag", "precision" : "very-high", "problem.severity" : "warning", - "security-severity" : "8.8" + "security-severity" : "6.1" } }, { - "id" : "js/angular/insecure-url-whitelist", - "name" : "js/angular/insecure-url-whitelist", + "id" : "js/bad-tag-filter", + "name" : "js/bad-tag-filter", "shortDescription" : { - "text" : "Insecure URL whitelist" + "text" : "Bad HTML filtering regexp" }, "fullDescription" : { - "text" : "URL whitelists that are too permissive can cause security vulnerabilities." + "text" : "Matching HTML tags using regular expressions is hard to do right, and can easily lead to security issues." }, "defaultConfiguration" : { "enabled" : true, "level" : "warning" }, "help" : { - "text" : "# Insecure URL whitelist\nAngularJS uses filters to ensure that the URLs used for sourcing AngularJS templates and other script-running URLs are safe. One such filter is a whitelist of URL patterns to allow.\n\nA URL pattern that is too permissive can cause security vulnerabilities.\n\n\n## Recommendation\nMake the whitelist URL patterns as restrictive as possible.\n\n\n## Example\nThe following example shows an AngularJS application with whitelist URL patterns that all are too permissive.\n\n\n```javascript\nangular.module('myApp', [])\n .config(function($sceDelegateProvider) {\n $sceDelegateProvider.resourceUrlWhitelist([\n \"*://example.org/*\", // BAD\n \"https://**.example.com/*\", // BAD\n \"https://example.**\", // BAD\n \"https://example.*\" // BAD\n ]);\n });\n\n```\nThis is problematic, since the four patterns match the following malicious URLs, respectively:\n\n* `javascript://example.org/a%0A%0Dalert(1)` (`%0A%0D` is a linebreak)\n* `https://evil.com/?ignore=://example.com/a`\n* `https://example.evil.com`\n* `https://example.evilTld`\n\n## References\n* OWASP/Google presentation: [Securing AngularJS Applications](https://www.owasp.org/images/6/6e/Benelus_day_20161125_S_Lekies_Securing_AngularJS_Applications.pdf)\n* AngularJS Developer Guide: [Format of items in resourceUrlWhitelist/Blacklist](https://docs.angularjs.org/api/ng/service/$sce#resourceUrlPatternItem).\n* Common Weakness Enumeration: [CWE-183](https://cwe.mitre.org/data/definitions/183.html).\n* Common Weakness Enumeration: [CWE-625](https://cwe.mitre.org/data/definitions/625.html).\n", - "markdown" : "# Insecure URL whitelist\nAngularJS uses filters to ensure that the URLs used for sourcing AngularJS templates and other script-running URLs are safe. One such filter is a whitelist of URL patterns to allow.\n\nA URL pattern that is too permissive can cause security vulnerabilities.\n\n\n## Recommendation\nMake the whitelist URL patterns as restrictive as possible.\n\n\n## Example\nThe following example shows an AngularJS application with whitelist URL patterns that all are too permissive.\n\n\n```javascript\nangular.module('myApp', [])\n .config(function($sceDelegateProvider) {\n $sceDelegateProvider.resourceUrlWhitelist([\n \"*://example.org/*\", // BAD\n \"https://**.example.com/*\", // BAD\n \"https://example.**\", // BAD\n \"https://example.*\" // BAD\n ]);\n });\n\n```\nThis is problematic, since the four patterns match the following malicious URLs, respectively:\n\n* `javascript://example.org/a%0A%0Dalert(1)` (`%0A%0D` is a linebreak)\n* `https://evil.com/?ignore=://example.com/a`\n* `https://example.evil.com`\n* `https://example.evilTld`\n\n## References\n* OWASP/Google presentation: [Securing AngularJS Applications](https://www.owasp.org/images/6/6e/Benelus_day_20161125_S_Lekies_Securing_AngularJS_Applications.pdf)\n* AngularJS Developer Guide: [Format of items in resourceUrlWhitelist/Blacklist](https://docs.angularjs.org/api/ng/service/$sce#resourceUrlPatternItem).\n* Common Weakness Enumeration: [CWE-183](https://cwe.mitre.org/data/definitions/183.html).\n* Common Weakness Enumeration: [CWE-625](https://cwe.mitre.org/data/definitions/625.html).\n" + "text" : "# Bad HTML filtering regexp\nIt is possible to match some single HTML tags using regular expressions (parsing general HTML using regular expressions is impossible). However, if the regular expression is not written well it might be possible to circumvent it, which can lead to cross-site scripting or other security issues.\n\nSome of these mistakes are caused by browsers having very forgiving HTML parsers, and will often render invalid HTML containing syntax errors. Regular expressions that attempt to match HTML should also recognize tags containing such syntax errors.\n\n\n## Recommendation\nUse a well-tested sanitization or parser library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation.\n\n\n## Example\nThe following example attempts to filters out all `` as script end tags, but also tags such as `` even though it is a parser error. This means that an attack string such as `` will not be filtered by the function, and `alert(1)` will be executed by a browser if the string is rendered as HTML.\n\nOther corner cases include that HTML comments can end with `--!>`, and that HTML tag names can contain upper case characters.\n\n\n## References\n* Securitum: [The Curious Case of Copy & Paste](https://research.securitum.com/the-curious-case-of-copy-paste/).\n* stackoverflow.com: [You can't parse \\[X\\]HTML with regex](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags#answer-1732454).\n* HTML Standard: [Comment end bang state](https://html.spec.whatwg.org/multipage/parsing.html#comment-end-bang-state).\n* stackoverflow.com: [Why aren't browsers strict about HTML?](https://stackoverflow.com/questions/25559999/why-arent-browsers-strict-about-html).\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n* Common Weakness Enumeration: [CWE-80](https://cwe.mitre.org/data/definitions/80.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n* Common Weakness Enumeration: [CWE-184](https://cwe.mitre.org/data/definitions/184.html).\n* Common Weakness Enumeration: [CWE-185](https://cwe.mitre.org/data/definitions/185.html).\n* Common Weakness Enumeration: [CWE-186](https://cwe.mitre.org/data/definitions/186.html).\n", + "markdown" : "# Bad HTML filtering regexp\nIt is possible to match some single HTML tags using regular expressions (parsing general HTML using regular expressions is impossible). However, if the regular expression is not written well it might be possible to circumvent it, which can lead to cross-site scripting or other security issues.\n\nSome of these mistakes are caused by browsers having very forgiving HTML parsers, and will often render invalid HTML containing syntax errors. Regular expressions that attempt to match HTML should also recognize tags containing such syntax errors.\n\n\n## Recommendation\nUse a well-tested sanitization or parser library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation.\n\n\n## Example\nThe following example attempts to filters out all `` as script end tags, but also tags such as `` even though it is a parser error. This means that an attack string such as `` will not be filtered by the function, and `alert(1)` will be executed by a browser if the string is rendered as HTML.\n\nOther corner cases include that HTML comments can end with `--!>`, and that HTML tag names can contain upper case characters.\n\n\n## References\n* Securitum: [The Curious Case of Copy & Paste](https://research.securitum.com/the-curious-case-of-copy-paste/).\n* stackoverflow.com: [You can't parse \\[X\\]HTML with regex](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags#answer-1732454).\n* HTML Standard: [Comment end bang state](https://html.spec.whatwg.org/multipage/parsing.html#comment-end-bang-state).\n* stackoverflow.com: [Why aren't browsers strict about HTML?](https://stackoverflow.com/questions/25559999/why-arent-browsers-strict-about-html).\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n* Common Weakness Enumeration: [CWE-80](https://cwe.mitre.org/data/definitions/80.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n* Common Weakness Enumeration: [CWE-184](https://cwe.mitre.org/data/definitions/184.html).\n* Common Weakness Enumeration: [CWE-185](https://cwe.mitre.org/data/definitions/185.html).\n* Common Weakness Enumeration: [CWE-186](https://cwe.mitre.org/data/definitions/186.html).\n" }, "properties" : { - "tags" : [ "security", "frameworks/angularjs", "external/cwe/cwe-183", "external/cwe/cwe-625" ], - "description" : "URL whitelists that are too permissive can cause security vulnerabilities.", - "id" : "js/angular/insecure-url-whitelist", + "tags" : [ "correctness", "security", "external/cwe/cwe-020", "external/cwe/cwe-080", "external/cwe/cwe-116", "external/cwe/cwe-184", "external/cwe/cwe-185", "external/cwe/cwe-186" ], + "description" : "Matching HTML tags using regular expressions is hard to do right, and can easily lead to security issues.", + "id" : "js/bad-tag-filter", "kind" : "problem", - "name" : "Insecure URL whitelist", - "precision" : "very-high", + "name" : "Bad HTML filtering regexp", + "precision" : "high", "problem.severity" : "warning", - "security-severity" : "7.5" + "security-severity" : "7.8" } }, { - "id" : "js/angular/disabling-sce", - "name" : "js/angular/disabling-sce", + "id" : "js/double-escaping", + "name" : "js/double-escaping", "shortDescription" : { - "text" : "Disabling SCE" + "text" : "Double escaping or unescaping" }, "fullDescription" : { - "text" : "Disabling strict contextual escaping (SCE) can cause security vulnerabilities." + "text" : "When escaping special characters using a meta-character like backslash or ampersand, the meta-character has to be escaped first to avoid double-escaping, and conversely it has to be unescaped last to avoid double-unescaping." }, "defaultConfiguration" : { "enabled" : true, "level" : "warning" }, "help" : { - "text" : "# Disabling SCE\nAngularJS is secure by default through automated sanitization and filtering of untrusted values that could cause vulnerabilities such as XSS. Strict Contextual Escaping (SCE) is an execution mode in AngularJS that provides this security mechanism.\n\nDisabling SCE in an AngularJS application is strongly discouraged. It is even more discouraged to disable SCE in a library, since it is an application-wide setting.\n\n\n## Recommendation\nDo not disable SCE.\n\n\n## Example\nThe following example shows an AngularJS application that disables SCE in order to dynamically construct an HTML fragment, which is later inserted into the DOM through `$scope.html`.\n\n\n```javascript\nangular.module('app', [])\n .config(function($sceProvider) {\n $sceProvider.enabled(false); // BAD\n }).controller('controller', function($scope) {\n // ...\n $scope.html = '
  • ' + item.toString() + '
';\n });\n\n```\nThis is problematic, since it disables SCE for the entire AngularJS application.\n\nInstead, just mark the dynamically constructed HTML fragment as safe using `$sce.trustAsHtml`, before assigning it to `$scope.html`:\n\n\n```javascript\nangular.module('app', [])\n .controller('controller', function($scope, $sce) {\n // ...\n // GOOD (but should use the templating system instead)\n $scope.html = $sce.trustAsHtml('
  • ' + item.toString() + '
'); \n });\n\n```\nPlease note that this example is for illustrative purposes only; use the AngularJS templating system to dynamically construct HTML when possible.\n\n\n## References\n* AngularJS Developer Guide: [Strict Contextual Escaping](https://docs.angularjs.org/api/ng/service/$sce)\n* AngularJS Developer Guide: [Can I disable SCE completely?](https://docs.angularjs.org/api/ng/service/$sce#can-i-disable-sce-completely-).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n", - "markdown" : "# Disabling SCE\nAngularJS is secure by default through automated sanitization and filtering of untrusted values that could cause vulnerabilities such as XSS. Strict Contextual Escaping (SCE) is an execution mode in AngularJS that provides this security mechanism.\n\nDisabling SCE in an AngularJS application is strongly discouraged. It is even more discouraged to disable SCE in a library, since it is an application-wide setting.\n\n\n## Recommendation\nDo not disable SCE.\n\n\n## Example\nThe following example shows an AngularJS application that disables SCE in order to dynamically construct an HTML fragment, which is later inserted into the DOM through `$scope.html`.\n\n\n```javascript\nangular.module('app', [])\n .config(function($sceProvider) {\n $sceProvider.enabled(false); // BAD\n }).controller('controller', function($scope) {\n // ...\n $scope.html = '
  • ' + item.toString() + '
';\n });\n\n```\nThis is problematic, since it disables SCE for the entire AngularJS application.\n\nInstead, just mark the dynamically constructed HTML fragment as safe using `$sce.trustAsHtml`, before assigning it to `$scope.html`:\n\n\n```javascript\nangular.module('app', [])\n .controller('controller', function($scope, $sce) {\n // ...\n // GOOD (but should use the templating system instead)\n $scope.html = $sce.trustAsHtml('
  • ' + item.toString() + '
'); \n });\n\n```\nPlease note that this example is for illustrative purposes only; use the AngularJS templating system to dynamically construct HTML when possible.\n\n\n## References\n* AngularJS Developer Guide: [Strict Contextual Escaping](https://docs.angularjs.org/api/ng/service/$sce)\n* AngularJS Developer Guide: [Can I disable SCE completely?](https://docs.angularjs.org/api/ng/service/$sce#can-i-disable-sce-completely-).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n" + "text" : "# Double escaping or unescaping\nEscaping meta-characters in untrusted input is an important technique for preventing injection attacks such as cross-site scripting. One particular example of this is HTML entity encoding, where HTML special characters are replaced by HTML character entities to prevent them from being interpreted as HTML markup. For example, the less-than character is encoded as `<` and the double-quote character as `"`. Other examples include backslash-escaping for including untrusted data in string literals and percent-encoding for URI components.\n\nThe reverse process of replacing escape sequences with the characters they represent is known as unescaping.\n\nNote that the escape characters themselves (such as ampersand in the case of HTML encoding) play a special role during escaping and unescaping: they are themselves escaped, but also form part of the escaped representations of other characters. Hence care must be taken to avoid double escaping and unescaping: when escaping, the escape character must be escaped first, when unescaping it has to be unescaped last.\n\nIf used in the context of sanitization, double unescaping may render the sanitization ineffective. Even if it is not used in a security-critical context, it may still result in confusing or garbled output.\n\n\n## Recommendation\nUse a (well-tested) sanitization library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation. For URI encoding, you can use the standard `encodeURIComponent` and `decodeURIComponent` functions.\n\nOtherwise, make sure to always escape the escape character first, and unescape it last.\n\n\n## Example\nThe following example shows a pair of hand-written HTML encoding and decoding functions:\n\n\n```javascript\nmodule.exports.encode = function(s) {\n return s.replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nmodule.exports.decode = function(s) {\n return s.replace(/&/g, \"&\")\n .replace(/"/g, \"\\\"\")\n .replace(/'/g, \"'\");\n};\n\n```\nThe encoding function correctly handles ampersand before the other characters. For example, the string `me & \"you\"` is encoded as `me & "you"`, and the string `"` is encoded as `&quot;`.\n\nThe decoding function, however, incorrectly decodes `&` into `&` before handling the other characters. So while it correctly decodes the first example above, it decodes the second example (`&quot;`) to `\"` (a single double quote), which is not correct.\n\nInstead, the decoding function should decode the ampersand last:\n\n\n```javascript\nmodule.exports.encode = function(s) {\n return s.replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nmodule.exports.decode = function(s) {\n return s.replace(/"/g, \"\\\"\")\n .replace(/'/g, \"'\")\n .replace(/&/g, \"&\");\n};\n\n```\n\n## References\n* OWASP Top 10: [A1 Injection](https://www.owasp.org/index.php/Top_10-2017_A1-Injection).\n* npm: [html-entities](https://www.npmjs.com/package/html-entities) package.\n* npm: [js-string-escape](https://www.npmjs.com/package/js-string-escape) package.\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n", + "markdown" : "# Double escaping or unescaping\nEscaping meta-characters in untrusted input is an important technique for preventing injection attacks such as cross-site scripting. One particular example of this is HTML entity encoding, where HTML special characters are replaced by HTML character entities to prevent them from being interpreted as HTML markup. For example, the less-than character is encoded as `<` and the double-quote character as `"`. Other examples include backslash-escaping for including untrusted data in string literals and percent-encoding for URI components.\n\nThe reverse process of replacing escape sequences with the characters they represent is known as unescaping.\n\nNote that the escape characters themselves (such as ampersand in the case of HTML encoding) play a special role during escaping and unescaping: they are themselves escaped, but also form part of the escaped representations of other characters. Hence care must be taken to avoid double escaping and unescaping: when escaping, the escape character must be escaped first, when unescaping it has to be unescaped last.\n\nIf used in the context of sanitization, double unescaping may render the sanitization ineffective. Even if it is not used in a security-critical context, it may still result in confusing or garbled output.\n\n\n## Recommendation\nUse a (well-tested) sanitization library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation. For URI encoding, you can use the standard `encodeURIComponent` and `decodeURIComponent` functions.\n\nOtherwise, make sure to always escape the escape character first, and unescape it last.\n\n\n## Example\nThe following example shows a pair of hand-written HTML encoding and decoding functions:\n\n\n```javascript\nmodule.exports.encode = function(s) {\n return s.replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nmodule.exports.decode = function(s) {\n return s.replace(/&/g, \"&\")\n .replace(/"/g, \"\\\"\")\n .replace(/'/g, \"'\");\n};\n\n```\nThe encoding function correctly handles ampersand before the other characters. For example, the string `me & \"you\"` is encoded as `me & "you"`, and the string `"` is encoded as `&quot;`.\n\nThe decoding function, however, incorrectly decodes `&` into `&` before handling the other characters. So while it correctly decodes the first example above, it decodes the second example (`&quot;`) to `\"` (a single double quote), which is not correct.\n\nInstead, the decoding function should decode the ampersand last:\n\n\n```javascript\nmodule.exports.encode = function(s) {\n return s.replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nmodule.exports.decode = function(s) {\n return s.replace(/"/g, \"\\\"\")\n .replace(/'/g, \"'\")\n .replace(/&/g, \"&\");\n};\n\n```\n\n## References\n* OWASP Top 10: [A1 Injection](https://www.owasp.org/index.php/Top_10-2017_A1-Injection).\n* npm: [html-entities](https://www.npmjs.com/package/html-entities) package.\n* npm: [js-string-escape](https://www.npmjs.com/package/js-string-escape) package.\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n" }, "properties" : { - "tags" : [ "security", "maintainability", "frameworks/angularjs", "external/cwe/cwe-116" ], - "description" : "Disabling strict contextual escaping (SCE) can cause security vulnerabilities.", - "id" : "js/angular/disabling-sce", + "tags" : [ "correctness", "security", "external/cwe/cwe-116", "external/cwe/cwe-020" ], + "description" : "When escaping special characters using a meta-character like backslash or\n ampersand, the meta-character has to be escaped first to avoid double-escaping,\n and conversely it has to be unescaped last to avoid double-unescaping.", + "id" : "js/double-escaping", "kind" : "problem", - "name" : "Disabling SCE", - "precision" : "very-high", + "name" : "Double escaping or unescaping", + "precision" : "high", "problem.severity" : "warning", "security-severity" : "7.8" } }, { - "id" : "js/loop-bound-injection", - "name" : "js/loop-bound-injection", + "id" : "js/incomplete-multi-character-sanitization", + "name" : "js/incomplete-multi-character-sanitization", "shortDescription" : { - "text" : "Loop bound injection" + "text" : "Incomplete multi-character sanitization" }, "fullDescription" : { - "text" : "Iterating over an object with a user-controlled .length property can cause indefinite looping." + "text" : "A sanitizer that removes a sequence of characters may reintroduce the dangerous sequence." }, "defaultConfiguration" : { "enabled" : true, "level" : "warning" }, "help" : { - "text" : "# Loop bound injection\nUsing the `.length` property of an untrusted object as a loop bound may cause indefinite looping since a malicious attacker can set the `.length` property to a very large number. For example, when a program that expects an array is passed a JSON object such as `{length: 1e100}`, the loop will be run for 10100 iterations. This may cause the program to hang or run out of memory, which can be used to mount a denial-of-service (DoS) attack.\n\n\n## Recommendation\nEither check that the object is indeed an array or limit the size of the `.length` property.\n\n\n## Example\nIn the example below, an HTTP request handler iterates over a user-controlled object `obj` using the `obj.length` property in order to copy the elements from `obj` to an array.\n\n\n```javascript\nvar express = require('express');\nvar app = express();\n\napp.post(\"/foo\", (req, res) => {\n var obj = req.body;\n\n var ret = [];\n\n // Potential DoS if obj.length is large.\n for (var i = 0; i < obj.length; i++) {\n ret.push(obj[i]);\n }\n});\n\n```\nThis is not secure since an attacker can control the value of `obj.length`, and thereby cause the loop to iterate indefinitely. Here the potential DoS is fixed by enforcing that the user-controlled object is an array.\n\n\n```javascript\nvar express = require('express');\nvar app = express();\n\napp.post(\"/foo\", (req, res) => {\n var obj = req.body;\n \n if (!(obj instanceof Array)) { // Prevents DoS.\n return [];\n }\n\n var ret = [];\n\n for (var i = 0; i < obj.length; i++) {\n ret.push(obj[i]);\n }\n});\n\n```\n\n## References\n* Common Weakness Enumeration: [CWE-834](https://cwe.mitre.org/data/definitions/834.html).\n* Common Weakness Enumeration: [CWE-730](https://cwe.mitre.org/data/definitions/730.html).\n", - "markdown" : "# Loop bound injection\nUsing the `.length` property of an untrusted object as a loop bound may cause indefinite looping since a malicious attacker can set the `.length` property to a very large number. For example, when a program that expects an array is passed a JSON object such as `{length: 1e100}`, the loop will be run for 10100 iterations. This may cause the program to hang or run out of memory, which can be used to mount a denial-of-service (DoS) attack.\n\n\n## Recommendation\nEither check that the object is indeed an array or limit the size of the `.length` property.\n\n\n## Example\nIn the example below, an HTTP request handler iterates over a user-controlled object `obj` using the `obj.length` property in order to copy the elements from `obj` to an array.\n\n\n```javascript\nvar express = require('express');\nvar app = express();\n\napp.post(\"/foo\", (req, res) => {\n var obj = req.body;\n\n var ret = [];\n\n // Potential DoS if obj.length is large.\n for (var i = 0; i < obj.length; i++) {\n ret.push(obj[i]);\n }\n});\n\n```\nThis is not secure since an attacker can control the value of `obj.length`, and thereby cause the loop to iterate indefinitely. Here the potential DoS is fixed by enforcing that the user-controlled object is an array.\n\n\n```javascript\nvar express = require('express');\nvar app = express();\n\napp.post(\"/foo\", (req, res) => {\n var obj = req.body;\n \n if (!(obj instanceof Array)) { // Prevents DoS.\n return [];\n }\n\n var ret = [];\n\n for (var i = 0; i < obj.length; i++) {\n ret.push(obj[i]);\n }\n});\n\n```\n\n## References\n* Common Weakness Enumeration: [CWE-834](https://cwe.mitre.org/data/definitions/834.html).\n* Common Weakness Enumeration: [CWE-730](https://cwe.mitre.org/data/definitions/730.html).\n" + "text" : "# Incomplete multi-character sanitization\nSanitizing untrusted input is a common technique for preventing injection attacks and other security vulnerabilities. Regular expressions are often used to perform this sanitization. However, when the regular expression matches multiple consecutive characters, replacing it just once can result in the unsafe text reappearing in the sanitized input.\n\nAttackers can exploit this issue by crafting inputs that, when sanitized with an ineffective regular expression, still contain malicious code or content. This can lead to code execution, data exposure, or other vulnerabilities.\n\n\n## Recommendation\nTo prevent this issue, it is highly recommended to use a well-tested sanitization library whenever possible. These libraries are more likely to handle corner cases and ensure effective sanitization.\n\nIf a library is not an option, you can consider alternative strategies to fix the issue. For example, applying the regular expression replacement repeatedly until no more replacements can be performed, or rewriting the regular expression to match single characters instead of the entire unsafe text.\n\n\n## Example\nConsider the following JavaScript code that aims to remove all HTML comment start and end tags:\n\n```javascript\n\nstr.replace(/`, and that HTML tag names can contain upper case characters.\n\n\n## References\n* Securitum: [The Curious Case of Copy & Paste](https://research.securitum.com/the-curious-case-of-copy-paste/).\n* stackoverflow.com: [You can't parse \\[X\\]HTML with regex](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags#answer-1732454).\n* HTML Standard: [Comment end bang state](https://html.spec.whatwg.org/multipage/parsing.html#comment-end-bang-state).\n* stackoverflow.com: [Why aren't browsers strict about HTML?](https://stackoverflow.com/questions/25559999/why-arent-browsers-strict-about-html).\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n* Common Weakness Enumeration: [CWE-80](https://cwe.mitre.org/data/definitions/80.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n* Common Weakness Enumeration: [CWE-184](https://cwe.mitre.org/data/definitions/184.html).\n* Common Weakness Enumeration: [CWE-185](https://cwe.mitre.org/data/definitions/185.html).\n* Common Weakness Enumeration: [CWE-186](https://cwe.mitre.org/data/definitions/186.html).\n", - "markdown" : "# Bad HTML filtering regexp\nIt is possible to match some single HTML tags using regular expressions (parsing general HTML using regular expressions is impossible). However, if the regular expression is not written well it might be possible to circumvent it, which can lead to cross-site scripting or other security issues.\n\nSome of these mistakes are caused by browsers having very forgiving HTML parsers, and will often render invalid HTML containing syntax errors. Regular expressions that attempt to match HTML should also recognize tags containing such syntax errors.\n\n\n## Recommendation\nUse a well-tested sanitization or parser library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation.\n\n\n## Example\nThe following example attempts to filters out all `` as script end tags, but also tags such as `` even though it is a parser error. This means that an attack string such as `` will not be filtered by the function, and `alert(1)` will be executed by a browser if the string is rendered as HTML.\n\nOther corner cases include that HTML comments can end with `--!>`, and that HTML tag names can contain upper case characters.\n\n\n## References\n* Securitum: [The Curious Case of Copy & Paste](https://research.securitum.com/the-curious-case-of-copy-paste/).\n* stackoverflow.com: [You can't parse \\[X\\]HTML with regex](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags#answer-1732454).\n* HTML Standard: [Comment end bang state](https://html.spec.whatwg.org/multipage/parsing.html#comment-end-bang-state).\n* stackoverflow.com: [Why aren't browsers strict about HTML?](https://stackoverflow.com/questions/25559999/why-arent-browsers-strict-about-html).\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n* Common Weakness Enumeration: [CWE-80](https://cwe.mitre.org/data/definitions/80.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n* Common Weakness Enumeration: [CWE-184](https://cwe.mitre.org/data/definitions/184.html).\n* Common Weakness Enumeration: [CWE-185](https://cwe.mitre.org/data/definitions/185.html).\n* Common Weakness Enumeration: [CWE-186](https://cwe.mitre.org/data/definitions/186.html).\n" + "text" : "# Creating biased random numbers from a cryptographically secure source\nGenerating secure random numbers can be an important part of creating a secure software system. This can be done using APIs that create cryptographically secure random numbers.\n\nHowever, using some mathematical operations on these cryptographically secure random numbers can create biased results, where some outcomes are more likely than others. Such biased results can make it easier for an attacker to guess the random numbers, and thereby break the security of the software system.\n\n\n## Recommendation\nBe very careful not to introduce bias when performing mathematical operations on cryptographically secure random numbers.\n\nIf possible, avoid performing mathematical operations on cryptographically secure random numbers at all, and use a preexisting library instead.\n\n\n## Example\nThe example below uses the modulo operator to create an array of 10 random digits using random bytes as the source for randomness.\n\n\n```javascript\nconst crypto = require('crypto');\n\nconst digits = [];\nfor (let i = 0; i < 10; i++) {\n digits.push(crypto.randomBytes(1)[0] % 10); // NOT OK\n}\n```\nThe random byte is a uniformly random value between 0 and 255, and thus the result from using the modulo operator is slightly more likely to be between 0 and 5 than between 6 and 9.\n\nThe issue has been fixed in the code below by using a library that correctly generates cryptographically secure random values.\n\n\n```javascript\nconst cryptoRandomString = require('crypto-random-string');\n\nconst digits = cryptoRandomString({length: 10, type: 'numeric'});\n```\nAlternatively, the issue can be fixed by fixing the math in the original code. In the code below the random byte is discarded if the value is greater than or equal to 250. Thus the modulo operator is used on a uniformly random number between 0 and 249, which results in a uniformly random digit between 0 and 9.\n\n\n```javascript\nconst crypto = require('crypto');\n\nconst digits = [];\nwhile (digits.length < 10) {\n const byte = crypto.randomBytes(1)[0];\n if (byte >= 250) {\n continue;\n }\n digits.push(byte % 10); // OK\n}\n```\n\n## References\n* Stack Overflow: [Understanding “randomness”](https://stackoverflow.com/questions/3956478/understanding-randomness).\n* OWASP: [Insecure Randomness](https://owasp.org/www-community/vulnerabilities/Insecure_Randomness).\n* OWASP: [Rule - Use strong approved cryptographic algorithms](https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#rule---use-strong-approved-authenticated-encryption).\n* Common Weakness Enumeration: [CWE-327](https://cwe.mitre.org/data/definitions/327.html).\n", + "markdown" : "# Creating biased random numbers from a cryptographically secure source\nGenerating secure random numbers can be an important part of creating a secure software system. This can be done using APIs that create cryptographically secure random numbers.\n\nHowever, using some mathematical operations on these cryptographically secure random numbers can create biased results, where some outcomes are more likely than others. Such biased results can make it easier for an attacker to guess the random numbers, and thereby break the security of the software system.\n\n\n## Recommendation\nBe very careful not to introduce bias when performing mathematical operations on cryptographically secure random numbers.\n\nIf possible, avoid performing mathematical operations on cryptographically secure random numbers at all, and use a preexisting library instead.\n\n\n## Example\nThe example below uses the modulo operator to create an array of 10 random digits using random bytes as the source for randomness.\n\n\n```javascript\nconst crypto = require('crypto');\n\nconst digits = [];\nfor (let i = 0; i < 10; i++) {\n digits.push(crypto.randomBytes(1)[0] % 10); // NOT OK\n}\n```\nThe random byte is a uniformly random value between 0 and 255, and thus the result from using the modulo operator is slightly more likely to be between 0 and 5 than between 6 and 9.\n\nThe issue has been fixed in the code below by using a library that correctly generates cryptographically secure random values.\n\n\n```javascript\nconst cryptoRandomString = require('crypto-random-string');\n\nconst digits = cryptoRandomString({length: 10, type: 'numeric'});\n```\nAlternatively, the issue can be fixed by fixing the math in the original code. In the code below the random byte is discarded if the value is greater than or equal to 250. Thus the modulo operator is used on a uniformly random number between 0 and 249, which results in a uniformly random digit between 0 and 9.\n\n\n```javascript\nconst crypto = require('crypto');\n\nconst digits = [];\nwhile (digits.length < 10) {\n const byte = crypto.randomBytes(1)[0];\n if (byte >= 250) {\n continue;\n }\n digits.push(byte % 10); // OK\n}\n```\n\n## References\n* Stack Overflow: [Understanding “randomness”](https://stackoverflow.com/questions/3956478/understanding-randomness).\n* OWASP: [Insecure Randomness](https://owasp.org/www-community/vulnerabilities/Insecure_Randomness).\n* OWASP: [Rule - Use strong approved cryptographic algorithms](https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#rule---use-strong-approved-authenticated-encryption).\n* Common Weakness Enumeration: [CWE-327](https://cwe.mitre.org/data/definitions/327.html).\n" }, "properties" : { - "tags" : [ "correctness", "security", "external/cwe/cwe-020", "external/cwe/cwe-080", "external/cwe/cwe-116", "external/cwe/cwe-184", "external/cwe/cwe-185", "external/cwe/cwe-186" ], - "description" : "Matching HTML tags using regular expressions is hard to do right, and can easily lead to security issues.", - "id" : "js/bad-tag-filter", + "tags" : [ "security", "external/cwe/cwe-327" ], + "description" : "Some mathematical operations on random numbers can cause bias in\n the results and compromise security.", + "id" : "js/biased-cryptographic-random", "kind" : "problem", - "name" : "Bad HTML filtering regexp", + "name" : "Creating biased random numbers from a cryptographically secure source", "precision" : "high", "problem.severity" : "warning", - "security-severity" : "7.8" + "security-severity" : "7.5" } }, { - "id" : "js/incomplete-sanitization", - "name" : "js/incomplete-sanitization", + "id" : "js/bad-code-sanitization", + "name" : "js/bad-code-sanitization", "shortDescription" : { - "text" : "Incomplete string escaping or encoding" + "text" : "Improper code sanitization" }, "fullDescription" : { - "text" : "A string transformer that does not replace or escape all occurrences of a meta-character may be ineffective." + "text" : "Escaping code as HTML does not provide protection against code injection." }, "defaultConfiguration" : { "enabled" : true, - "level" : "warning" + "level" : "error" }, "help" : { - "text" : "# Incomplete string escaping or encoding\nSanitizing untrusted input is a common technique for preventing injection attacks such as SQL injection or cross-site scripting. Usually, this is done by escaping meta-characters such as quotes in a domain-specific way so that they are treated as normal characters.\n\nHowever, directly using the string `replace` method to perform escaping is notoriously error-prone. Common mistakes include only replacing the first occurrence of a meta-character, or backslash-escaping various meta-characters but not the backslash itself.\n\nIn the former case, later meta-characters are left undisturbed and can be used to subvert the sanitization. In the latter case, preceding a meta-character with a backslash leads to the backslash being escaped, but the meta-character appearing un-escaped, which again makes the sanitization ineffective.\n\nEven if the escaped string is not used in a security-critical context, incomplete escaping may still have undesirable effects, such as badly rendered or confusing output.\n\n\n## Recommendation\nUse a (well-tested) sanitization library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation.\n\nAn even safer alternative is to design the application so that sanitization is not needed, for instance by using prepared statements for SQL queries.\n\nOtherwise, make sure to use a regular expression with the `g` flag to ensure that all occurrences are replaced, and remember to escape backslashes if applicable.\n\n\n## Example\nFor example, assume that we want to embed a user-controlled string `accountNumber` into a SQL query as part of a string literal. To avoid SQL injection, we need to ensure that the string does not contain un-escaped single-quote characters. The following function attempts to ensure this by doubling single quotes, and thereby escaping them:\n\n\n```javascript\nfunction escapeQuotes(s) {\n return s.replace(\"'\", \"''\");\n}\n\n```\nAs written, this sanitizer is ineffective: if the first argument to `replace` is a string literal (as in this case), only the *first* occurrence of that string is replaced.\n\nAs mentioned above, the function `escapeQuotes` should be replaced with a purpose-built sanitization library, such as the npm module `sqlstring`. Many other sanitization libraries are available from npm and other sources.\n\nIf this is not an option, `escapeQuotes` should be rewritten to use a regular expression with the `g` (\"global\") flag instead:\n\n\n```javascript\nfunction escapeQuotes(s) {\n return s.replace(/'/g, \"''\");\n}\n\n```\nNote that it is very important to include the global flag: `s.replace(/'/, \"''\")` *without* the global flag is equivalent to the first example above and only replaces the first quote.\n\n\n## References\n* OWASP Top 10: [A1 Injection](https://www.owasp.org/index.php/Top_10-2017_A1-Injection).\n* npm: [sqlstring](https://www.npmjs.com/package/sqlstring) package.\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n* Common Weakness Enumeration: [CWE-80](https://cwe.mitre.org/data/definitions/80.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n", - "markdown" : "# Incomplete string escaping or encoding\nSanitizing untrusted input is a common technique for preventing injection attacks such as SQL injection or cross-site scripting. Usually, this is done by escaping meta-characters such as quotes in a domain-specific way so that they are treated as normal characters.\n\nHowever, directly using the string `replace` method to perform escaping is notoriously error-prone. Common mistakes include only replacing the first occurrence of a meta-character, or backslash-escaping various meta-characters but not the backslash itself.\n\nIn the former case, later meta-characters are left undisturbed and can be used to subvert the sanitization. In the latter case, preceding a meta-character with a backslash leads to the backslash being escaped, but the meta-character appearing un-escaped, which again makes the sanitization ineffective.\n\nEven if the escaped string is not used in a security-critical context, incomplete escaping may still have undesirable effects, such as badly rendered or confusing output.\n\n\n## Recommendation\nUse a (well-tested) sanitization library if at all possible. These libraries are much more likely to handle corner cases correctly than a custom implementation.\n\nAn even safer alternative is to design the application so that sanitization is not needed, for instance by using prepared statements for SQL queries.\n\nOtherwise, make sure to use a regular expression with the `g` flag to ensure that all occurrences are replaced, and remember to escape backslashes if applicable.\n\n\n## Example\nFor example, assume that we want to embed a user-controlled string `accountNumber` into a SQL query as part of a string literal. To avoid SQL injection, we need to ensure that the string does not contain un-escaped single-quote characters. The following function attempts to ensure this by doubling single quotes, and thereby escaping them:\n\n\n```javascript\nfunction escapeQuotes(s) {\n return s.replace(\"'\", \"''\");\n}\n\n```\nAs written, this sanitizer is ineffective: if the first argument to `replace` is a string literal (as in this case), only the *first* occurrence of that string is replaced.\n\nAs mentioned above, the function `escapeQuotes` should be replaced with a purpose-built sanitization library, such as the npm module `sqlstring`. Many other sanitization libraries are available from npm and other sources.\n\nIf this is not an option, `escapeQuotes` should be rewritten to use a regular expression with the `g` (\"global\") flag instead:\n\n\n```javascript\nfunction escapeQuotes(s) {\n return s.replace(/'/g, \"''\");\n}\n\n```\nNote that it is very important to include the global flag: `s.replace(/'/, \"''\")` *without* the global flag is equivalent to the first example above and only replaces the first quote.\n\n\n## References\n* OWASP Top 10: [A1 Injection](https://www.owasp.org/index.php/Top_10-2017_A1-Injection).\n* npm: [sqlstring](https://www.npmjs.com/package/sqlstring) package.\n* Common Weakness Enumeration: [CWE-20](https://cwe.mitre.org/data/definitions/20.html).\n* Common Weakness Enumeration: [CWE-80](https://cwe.mitre.org/data/definitions/80.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n" + "text" : "# Improper code sanitization\nUsing string concatenation to construct JavaScript code can be error-prone, or in the worst case, enable code injection if an input is constructed by an attacker.\n\n\n## Recommendation\nIf using `JSON.stringify` or an HTML sanitizer to sanitize a string inserted into JavaScript code, then make sure to perform additional sanitization or remove potentially dangerous characters.\n\n\n## Example\nThe example below constructs a function that assigns the number 42 to the property `key` on an object `obj`. However, if `key` contains ``, then the generated code will break out of a `` if inserted into a `` tag.\n\n\n```javascript\nfunction createObjectWrite() {\n const assignment = `obj[${JSON.stringify(key)}]=42`;\n return `(function(){${assignment}})` // NOT OK\n}\n```\nThe issue has been fixed by escaping potentially dangerous characters, as shown below.\n\n\n```javascript\nconst charMap = {\n '<': '\\\\u003C',\n '>' : '\\\\u003E',\n '/': '\\\\u002F',\n '\\\\': '\\\\\\\\',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n '\\0': '\\\\0',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029'\n};\n\nfunction escapeUnsafeChars(str) {\n return str.replace(/[<>\\b\\f\\n\\r\\t\\0\\u2028\\u2029]/g, x => charMap[x])\n}\n\nfunction createObjectWrite() {\n const assignment = `obj[${escapeUnsafeChars(JSON.stringify(key))}]=42`;\n return `(function(){${assignment}})` // OK\n}\n```\n\n## References\n* OWASP: [Code Injection](https://www.owasp.org/index.php/Code_Injection).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n", + "markdown" : "# Improper code sanitization\nUsing string concatenation to construct JavaScript code can be error-prone, or in the worst case, enable code injection if an input is constructed by an attacker.\n\n\n## Recommendation\nIf using `JSON.stringify` or an HTML sanitizer to sanitize a string inserted into JavaScript code, then make sure to perform additional sanitization or remove potentially dangerous characters.\n\n\n## Example\nThe example below constructs a function that assigns the number 42 to the property `key` on an object `obj`. However, if `key` contains ``, then the generated code will break out of a `` if inserted into a `` tag.\n\n\n```javascript\nfunction createObjectWrite() {\n const assignment = `obj[${JSON.stringify(key)}]=42`;\n return `(function(){${assignment}})` // NOT OK\n}\n```\nThe issue has been fixed by escaping potentially dangerous characters, as shown below.\n\n\n```javascript\nconst charMap = {\n '<': '\\\\u003C',\n '>' : '\\\\u003E',\n '/': '\\\\u002F',\n '\\\\': '\\\\\\\\',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n '\\0': '\\\\0',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029'\n};\n\nfunction escapeUnsafeChars(str) {\n return str.replace(/[<>\\b\\f\\n\\r\\t\\0\\u2028\\u2029]/g, x => charMap[x])\n}\n\nfunction createObjectWrite() {\n const assignment = `obj[${escapeUnsafeChars(JSON.stringify(key))}]=42`;\n return `(function(){${assignment}})` // OK\n}\n```\n\n## References\n* OWASP: [Code Injection](https://www.owasp.org/index.php/Code_Injection).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n" }, "properties" : { - "tags" : [ "correctness", "security", "external/cwe/cwe-020", "external/cwe/cwe-080", "external/cwe/cwe-116" ], - "description" : "A string transformer that does not replace or escape all occurrences of a\n meta-character may be ineffective.", - "id" : "js/incomplete-sanitization", - "kind" : "problem", - "name" : "Incomplete string escaping or encoding", + "tags" : [ "security", "external/cwe/cwe-094", "external/cwe/cwe-079", "external/cwe/cwe-116" ], + "description" : "Escaping code as HTML does not provide protection against code injection.", + "id" : "js/bad-code-sanitization", + "kind" : "path-problem", + "name" : "Improper code sanitization", "precision" : "high", - "problem.severity" : "warning", - "security-severity" : "7.8" + "problem.severity" : "error", + "security-severity" : "6.1" } }, { - "id" : "js/unsafe-html-expansion", - "name" : "js/unsafe-html-expansion", + "id" : "js/actions/command-injection", + "name" : "js/actions/command-injection", "shortDescription" : { - "text" : "Unsafe expansion of self-closing HTML tag" + "text" : "Expression injection in Actions" }, "fullDescription" : { - "text" : "Using regular expressions to expand self-closing HTML tags may lead to cross-site scripting vulnerabilities." + "text" : "Using user-controlled GitHub Actions contexts like `run:` or `script:` may allow a malicious user to inject code into the GitHub action." }, "defaultConfiguration" : { "enabled" : true, "level" : "warning" }, "help" : { - "text" : "# Unsafe expansion of self-closing HTML tag\nSanitizing untrusted input for HTML meta-characters is a common technique for preventing cross-site scripting attacks. But even a sanitized input can be dangerous to use if it is modified further before a browser treats it as HTML. A seemingly innocent transformation that expands a self-closing HTML tag from `
` to `
` may in fact cause cross-site scripting vulnerabilities.\n\n\n## Recommendation\nUse a well-tested sanitization library if at all possible, and avoid modifying sanitized values further before treating them as HTML.\n\nAn even safer alternative is to design the application so that sanitization is not needed, for instance by using HTML templates that are explicit about the values they treat as HTML.\n\n\n## Example\nThe following function transforms a self-closing HTML tag to a pair of open/close tags. It does so for all non-`img` and non-`area` tags, by using a regular expression with two capture groups. The first capture group corresponds to the name of the tag, and the second capture group to the content of the tag.\n\n\n```javascript\nfunction expandSelfClosingTags(html) {\n\tvar rxhtmlTag = /<(?!img|area)(([a-z][^\\w\\/>]*)[^>]*)\\/>/gi;\n\treturn html.replace(rxhtmlTag, \"<$1>\"); // BAD\n}\n\n```\nWhile it is generally known regular expressions are ill-suited for parsing HTML, variants of this particular transformation pattern have long been considered safe.\n\nHowever, the function is not safe. As an example, consider the following string:\n\n\n```html\n
\n\"/>\n\n```\nWhen the above function transforms the string, it becomes a string that results in an alert when a browser treats it as HTML.\n\n\n```html\n
\n\"/>\n\n```\n\n## References\n* jQuery: [Security fixes in jQuery 3.5.0](https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/)\n* OWASP: [DOM based XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html).\n* OWASP: [XSS (Cross Site Scripting) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).\n* OWASP [Types of Cross-Site](https://owasp.org/www-community/Types_of_Cross-Site_Scripting).\n* Wikipedia: [Cross-site scripting](http://en.wikipedia.org/wiki/Cross-site_scripting).\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n", - "markdown" : "# Unsafe expansion of self-closing HTML tag\nSanitizing untrusted input for HTML meta-characters is a common technique for preventing cross-site scripting attacks. But even a sanitized input can be dangerous to use if it is modified further before a browser treats it as HTML. A seemingly innocent transformation that expands a self-closing HTML tag from `
` to `
` may in fact cause cross-site scripting vulnerabilities.\n\n\n## Recommendation\nUse a well-tested sanitization library if at all possible, and avoid modifying sanitized values further before treating them as HTML.\n\nAn even safer alternative is to design the application so that sanitization is not needed, for instance by using HTML templates that are explicit about the values they treat as HTML.\n\n\n## Example\nThe following function transforms a self-closing HTML tag to a pair of open/close tags. It does so for all non-`img` and non-`area` tags, by using a regular expression with two capture groups. The first capture group corresponds to the name of the tag, and the second capture group to the content of the tag.\n\n\n```javascript\nfunction expandSelfClosingTags(html) {\n\tvar rxhtmlTag = /<(?!img|area)(([a-z][^\\w\\/>]*)[^>]*)\\/>/gi;\n\treturn html.replace(rxhtmlTag, \"<$1>\"); // BAD\n}\n\n```\nWhile it is generally known regular expressions are ill-suited for parsing HTML, variants of this particular transformation pattern have long been considered safe.\n\nHowever, the function is not safe. As an example, consider the following string:\n\n\n```html\n
\n\"/>\n\n```\nWhen the above function transforms the string, it becomes a string that results in an alert when a browser treats it as HTML.\n\n\n```html\n
\n\"/>\n\n```\n\n## References\n* jQuery: [Security fixes in jQuery 3.5.0](https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/)\n* OWASP: [DOM based XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html).\n* OWASP: [XSS (Cross Site Scripting) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).\n* OWASP [Types of Cross-Site](https://owasp.org/www-community/Types_of_Cross-Site_Scripting).\n* Wikipedia: [Cross-site scripting](http://en.wikipedia.org/wiki/Cross-site_scripting).\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n* Common Weakness Enumeration: [CWE-116](https://cwe.mitre.org/data/definitions/116.html).\n" + "text" : "# Expression injection in Actions\nUsing user-controlled input in GitHub Actions may lead to code injection in contexts like *run:* or *script:*.\n\nCode injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token might have write access to the repository, allowing an attacker to use the token to make changes to the repository.\n\n\n## Recommendation\nThe best practice to avoid code injection vulnerabilities in GitHub workflows is to set the untrusted input value of the expression to an intermediate environment variable and then use the environment variable using the native syntax of the shell/script interpreter (that is, not *${{ env.VAR }}*).\n\nIt is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.\n\n\n## Example\nThe following example lets a user inject an arbitrary shell command:\n\n\n```yaml\non: issue_comment\n\njobs:\n echo-body:\n runs-on: ubuntu-latest\n steps:\n - run: |\n echo '${{ github.event.comment.body }}'\n```\nThe following example uses an environment variable, but **still allows the injection** because of the use of expression syntax:\n\n\n```yaml\non: issue_comment\n\njobs:\n echo-body:\n runs-on: ubuntu-latest\n steps:\n - env:\n BODY: ${{ github.event.issue.body }}\n run: |\n echo '${{ env.BODY }}'\n```\nThe following example uses shell syntax to read the environment variable and will prevent the attack:\n\n\n```yaml\non: issue_comment\n\njobs:\n echo-body:\n runs-on: ubuntu-latest\n steps:\n - env:\n BODY: ${{ github.event.issue.body }}\n run: |\n echo \"$BODY\"\n\n```\n\n## References\n* GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure: Untrusted input](https://securitylab.github.com/research/github-actions-untrusted-input).\n* GitHub Docs: [Security hardening for GitHub Actions](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions).\n* GitHub Docs: [Permissions for the GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n", + "markdown" : "# Expression injection in Actions\nUsing user-controlled input in GitHub Actions may lead to code injection in contexts like *run:* or *script:*.\n\nCode injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token might have write access to the repository, allowing an attacker to use the token to make changes to the repository.\n\n\n## Recommendation\nThe best practice to avoid code injection vulnerabilities in GitHub workflows is to set the untrusted input value of the expression to an intermediate environment variable and then use the environment variable using the native syntax of the shell/script interpreter (that is, not *${{ env.VAR }}*).\n\nIt is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.\n\n\n## Example\nThe following example lets a user inject an arbitrary shell command:\n\n\n```yaml\non: issue_comment\n\njobs:\n echo-body:\n runs-on: ubuntu-latest\n steps:\n - run: |\n echo '${{ github.event.comment.body }}'\n```\nThe following example uses an environment variable, but **still allows the injection** because of the use of expression syntax:\n\n\n```yaml\non: issue_comment\n\njobs:\n echo-body:\n runs-on: ubuntu-latest\n steps:\n - env:\n BODY: ${{ github.event.issue.body }}\n run: |\n echo '${{ env.BODY }}'\n```\nThe following example uses shell syntax to read the environment variable and will prevent the attack:\n\n\n```yaml\non: issue_comment\n\njobs:\n echo-body:\n runs-on: ubuntu-latest\n steps:\n - env:\n BODY: ${{ github.event.issue.body }}\n run: |\n echo \"$BODY\"\n\n```\n\n## References\n* GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure: Untrusted input](https://securitylab.github.com/research/github-actions-untrusted-input).\n* GitHub Docs: [Security hardening for GitHub Actions](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions).\n* GitHub Docs: [Permissions for the GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n" }, "properties" : { - "tags" : [ "correctness", "security", "external/cwe/cwe-079", "external/cwe/cwe-116" ], - "description" : "Using regular expressions to expand self-closing HTML\n tags may lead to cross-site scripting vulnerabilities.", - "id" : "js/unsafe-html-expansion", + "tags" : [ "actions", "security", "external/cwe/cwe-094" ], + "description" : "Using user-controlled GitHub Actions contexts like `run:` or `script:` may allow a malicious\n user to inject code into the GitHub action.", + "id" : "js/actions/command-injection", "kind" : "problem", - "name" : "Unsafe expansion of self-closing HTML tag", - "precision" : "very-high", + "name" : "Expression injection in Actions", + "precision" : "high", "problem.severity" : "warning", - "security-severity" : "6.1" + "security-severity" : "9.3" } }, { - "id" : "js/incomplete-multi-character-sanitization", - "name" : "js/incomplete-multi-character-sanitization", + "id" : "js/unsafe-dynamic-method-access", + "name" : "js/unsafe-dynamic-method-access", "shortDescription" : { - "text" : "Incomplete multi-character sanitization" + "text" : "Unsafe dynamic method access" }, "fullDescription" : { - "text" : "A sanitizer that removes a sequence of characters may reintroduce the dangerous sequence." + "text" : "Invoking user-controlled methods on certain objects can lead to remote code execution." }, "defaultConfiguration" : { "enabled" : true, - "level" : "warning" + "level" : "error" }, "help" : { - "text" : "# Incomplete multi-character sanitization\nSanitizing untrusted input is a common technique for preventing injection attacks and other security vulnerabilities. Regular expressions are often used to perform this sanitization. However, when the regular expression matches multiple consecutive characters, replacing it just once can result in the unsafe text reappearing in the sanitized input.\n\nAttackers can exploit this issue by crafting inputs that, when sanitized with an ineffective regular expression, still contain malicious code or content. This can lead to code execution, data exposure, or other vulnerabilities.\n\n\n## Recommendation\nTo prevent this issue, it is highly recommended to use a well-tested sanitization library whenever possible. These libraries are more likely to handle corner cases and ensure effective sanitization.\n\nIf a library is not an option, you can consider alternative strategies to fix the issue. For example, applying the regular expression replacement repeatedly until no more replacements can be performed, or rewriting the regular expression to match single characters instead of the entire unsafe text.\n\n\n## Example\nConsider the following JavaScript code that aims to remove all HTML comment start and end tags:\n\n```javascript\n\nstr.replace(/