From 032cfc134fccd1da23143c7f0b05d0f681e40bc6 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 25 Mar 2025 14:29:06 +0100 Subject: [PATCH 01/13] Added test cases for `hana` clients. --- .../Security/CWE-089/untyped/hana.js | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js new file mode 100644 index 000000000000..0d7fc05ec1a1 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js @@ -0,0 +1,86 @@ +const hana = require('@sap/hana-client'); +const express = require('express'); + +const app = express(); +const connectionParams = {}; +app.post('/documents/find', (req, res) => { + const conn = hana.createConnection(); + conn.connect(connectionParams, (err) => { + let maliciousInput = req.body.data; // $ MISSING: Source + const query = `SELECT * FROM Users WHERE username = '${maliciousInput}'`; + conn.exec(query, (err, rows) => {}); // $ MISSING: Alert + conn.disconnect(); + }); + + conn.connect(connectionParams, (err) => { + const maliciousInput = req.body.data; // $ MISSING: Source + const stmt = conn.prepare(`SELECT * FROM Test WHERE ID = ? AND username = ` + maliciousInput); // $ MISSING: Alert + stmt.exec([maliciousInput], (err, rows) => {}); // maliciousInput is treated as a parameter + conn.disconnect(); + }); + + conn.connect(connectionParams, (err) => { + const maliciousInput = req.body.data; // $ MISSING: Source + var stmt = conn.prepare(`INSERT INTO Customers(ID, NAME) VALUES(?, ?) ` + maliciousInput); // $ MISSING: Alert + stmt.execBatch([[1, maliciousInput], [2, maliciousInput]], function(err, rows) {}); // maliciousInput is treated as a parameter + conn.disconnect(); + }); + + conn.connect(connectionParams, (err) => { + const maliciousInput = req.body.data; // $ MISSING: Source + var stmt = conn.prepare("SELECT * FROM Customers WHERE ID >= ? AND ID < ?" + maliciousInput); // $ MISSING: Alert + stmt.execQuery([100, maliciousInput], function(err, rs) {}); // $ maliciousInput is treated as a parameter + conn.disconnect(); + }); +}); + +var hdbext = require('@sap/hdbext'); +var express = require('express'); +var dbStream = require('@sap/hana-client/extension/Stream'); + +var app1 = express(); +const hanaConfig = {}; +app1.use(hdbext.middleware(hanaConfig)); + +app1.get('/execute-query', function (req, res) { + var client = req.db; + let maliciousInput = req.body.data; // $ MISSING: Source + client.exec('SELECT * FROM DUMMY' + maliciousInput, function (err, rs) {}); // $ MISSING: Alert + + dbStream.createProcStatement(client, 'CALL PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function (err, stmt) { // $ MISSING: Alert + stmt.exec({ A: maliciousInput, B: 4 }, function (err, params, dummyRows, tablesRows) {}); // maliciousInput is treated as a parameter + }); + + hdbext.loadProcedure(client, null, 'PROC_DUMMY' + maliciousInput, function(err, sp) { // $ MISSING: Alert + sp(3, maliciousInput, function(err, parameters, dummyRows, tablesRows) {}); // maliciousInput is treated as a parameter + }); +}); + + +var hdb = require('hdb'); +const async = require('async'); + +const options = {}; +const app2 = express(); + +app2.post('/documents/find', (req, res) => { + var client = hdb.createClient(options); + let maliciousInput = req.body.data; // $ MISSING: Source + + client.connect(function onconnect(err) { + async.series([client.exec.bind(client, "INSERT INTO NUMBERS VALUES (1, 'one')" + maliciousInput)], function (err) {}); // $ MISSING: Alert + + client.exec('select * from DUMMY' + maliciousInput, function (err, rows) {}); // $ MISSING: Alert + client.exec('select * from DUMMY' + maliciousInput, options, function(err, rows) {}); // $ MISSING: Alert + + client.prepare('select * from DUMMY where DUMMY = ?' + maliciousInput, function (err, statement){ // $ MISSING: Alert + statement.exec([maliciousInput], function (err, rows) {}); // maliciousInput is treated as a parameter + }); + + client.prepare('call PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function(err, statement){ // $ MISSING: Alert + statement.exec({A: 3, B: maliciousInput}, function(err, parameters, dummyRows, tableRows) {}); + }); + + client.execute('select A, B from TEST.NUMBERS order by A' + maliciousInput, function(err, rs) {}); // $ MISSING: Alert + }); +}); From 9229962096f0dd0e3f0f866ed05c18ea5da8eaf2 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 25 Mar 2025 14:36:13 +0100 Subject: [PATCH 02/13] Add sink model for SQL injection detection in `exec` clients. --- .../ql/lib/ext/hana-db-client.model.yml | 8 ++++++ .../CWE-089/untyped/SqlInjection.expected | 28 +++++++++++++++++++ .../Security/CWE-089/untyped/hana.js | 12 ++++---- 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 javascript/ql/lib/ext/hana-db-client.model.yml diff --git a/javascript/ql/lib/ext/hana-db-client.model.yml b/javascript/ql/lib/ext/hana-db-client.model.yml new file mode 100644 index 000000000000..d8ef367b88d7 --- /dev/null +++ b/javascript/ql/lib/ext/hana-db-client.model.yml @@ -0,0 +1,8 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sinkModel + data: + - ["@sap/hana-client", "Member[createConnection].ReturnValue.Member[exec].Argument[0]", "sql-injection"] + + - ["hdb", "Member[createClient].ReturnValue.Member[exec].Argument[0]", "sql-injection"] diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected index 7ac39529dd60..a3c873912880 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected @@ -10,6 +10,10 @@ | graphql.js:74:46:74:64 | "{ foo" + id + " }" | graphql.js:73:14:73:25 | req.query.id | graphql.js:74:46:74:64 | "{ foo" + id + " }" | This query string depends on a $@. | graphql.js:73:14:73:25 | req.query.id | user-provided value | | graphql.js:82:14:88:8 | `{\\n ... }` | graphql.js:73:14:73:25 | req.query.id | graphql.js:82:14:88:8 | `{\\n ... }` | This query string depends on a $@. | graphql.js:73:14:73:25 | req.query.id | user-provided value | | graphql.js:118:38:118:48 | `foo ${id}` | graphql.js:117:16:117:28 | req.params.id | graphql.js:118:38:118:48 | `foo ${id}` | This query string depends on a $@. | graphql.js:117:16:117:28 | req.params.id | user-provided value | +| hana.js:11:19:11:23 | query | hana.js:9:30:9:37 | req.body | hana.js:11:19:11:23 | query | This query string depends on a $@. | hana.js:9:30:9:37 | req.body | user-provided value | +| hana.js:71:44:71:99 | "INSERT ... usInput | hana.js:68:24:68:31 | req.body | hana.js:71:44:71:99 | "INSERT ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | +| hana.js:73:17:73:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:73:17:73:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | +| hana.js:74:17:74:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:74:17:74:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | html-sanitizer.js:16:9:16:59 | `SELECT ... param1 | html-sanitizer.js:13:39:13:44 | param1 | html-sanitizer.js:16:9:16:59 | `SELECT ... param1 | This query string depends on a $@. | html-sanitizer.js:13:39:13:44 | param1 | user-provided value | | json-schema-validator.js:33:22:33:26 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:33:22:33:26 | query | This query object depends on a $@. | json-schema-validator.js:25:34:25:47 | req.query.data | user-provided value | | json-schema-validator.js:35:18:35:22 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:35:18:35:22 | query | This query object depends on a $@. | json-schema-validator.js:25:34:25:47 | req.query.data | user-provided value | @@ -152,6 +156,17 @@ edges | graphql.js:117:11:117:28 | id | graphql.js:118:45:118:46 | id | provenance | | | graphql.js:117:16:117:28 | req.params.id | graphql.js:117:11:117:28 | id | provenance | | | graphql.js:118:45:118:46 | id | graphql.js:118:38:118:48 | `foo ${id}` | provenance | | +| hana.js:9:13:9:42 | maliciousInput | hana.js:10:64:10:77 | maliciousInput | provenance | | +| hana.js:9:30:9:37 | req.body | hana.js:9:13:9:42 | maliciousInput | provenance | | +| hana.js:10:15:10:80 | query | hana.js:11:19:11:23 | query | provenance | | +| hana.js:10:64:10:77 | maliciousInput | hana.js:10:15:10:80 | query | provenance | | +| hana.js:68:7:68:36 | maliciousInput | hana.js:71:86:71:99 | maliciousInput | provenance | | +| hana.js:68:7:68:36 | maliciousInput | hana.js:73:41:73:54 | maliciousInput | provenance | | +| hana.js:68:7:68:36 | maliciousInput | hana.js:74:41:74:54 | maliciousInput | provenance | | +| hana.js:68:24:68:31 | req.body | hana.js:68:7:68:36 | maliciousInput | provenance | | +| hana.js:71:86:71:99 | maliciousInput | hana.js:71:44:71:99 | "INSERT ... usInput | provenance | | +| hana.js:73:41:73:54 | maliciousInput | hana.js:73:17:73:54 | 'select ... usInput | provenance | | +| hana.js:74:41:74:54 | maliciousInput | hana.js:74:17:74:54 | 'select ... usInput | provenance | | | html-sanitizer.js:13:39:13:44 | param1 | html-sanitizer.js:14:18:14:23 | param1 | provenance | | | html-sanitizer.js:14:5:14:24 | param1 | html-sanitizer.js:16:54:16:59 | param1 | provenance | | | html-sanitizer.js:14:14:14:24 | xss(param1) | html-sanitizer.js:14:5:14:24 | param1 | provenance | | @@ -504,6 +519,19 @@ nodes | graphql.js:117:16:117:28 | req.params.id | semmle.label | req.params.id | | graphql.js:118:38:118:48 | `foo ${id}` | semmle.label | `foo ${id}` | | graphql.js:118:45:118:46 | id | semmle.label | id | +| hana.js:9:13:9:42 | maliciousInput | semmle.label | maliciousInput | +| hana.js:9:30:9:37 | req.body | semmle.label | req.body | +| hana.js:10:15:10:80 | query | semmle.label | query | +| hana.js:10:64:10:77 | maliciousInput | semmle.label | maliciousInput | +| hana.js:11:19:11:23 | query | semmle.label | query | +| hana.js:68:7:68:36 | maliciousInput | semmle.label | maliciousInput | +| hana.js:68:24:68:31 | req.body | semmle.label | req.body | +| hana.js:71:44:71:99 | "INSERT ... usInput | semmle.label | "INSERT ... usInput | +| hana.js:71:86:71:99 | maliciousInput | semmle.label | maliciousInput | +| hana.js:73:17:73:54 | 'select ... usInput | semmle.label | 'select ... usInput | +| hana.js:73:41:73:54 | maliciousInput | semmle.label | maliciousInput | +| hana.js:74:17:74:54 | 'select ... usInput | semmle.label | 'select ... usInput | +| hana.js:74:41:74:54 | maliciousInput | semmle.label | maliciousInput | | html-sanitizer.js:13:39:13:44 | param1 | semmle.label | param1 | | html-sanitizer.js:14:5:14:24 | param1 | semmle.label | param1 | | html-sanitizer.js:14:14:14:24 | xss(param1) | semmle.label | xss(param1) | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js index 0d7fc05ec1a1..2b3aea9ae1d5 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js @@ -6,9 +6,9 @@ const connectionParams = {}; app.post('/documents/find', (req, res) => { const conn = hana.createConnection(); conn.connect(connectionParams, (err) => { - let maliciousInput = req.body.data; // $ MISSING: Source + let maliciousInput = req.body.data; // $ Source const query = `SELECT * FROM Users WHERE username = '${maliciousInput}'`; - conn.exec(query, (err, rows) => {}); // $ MISSING: Alert + conn.exec(query, (err, rows) => {}); // $ Alert conn.disconnect(); }); @@ -65,13 +65,13 @@ const app2 = express(); app2.post('/documents/find', (req, res) => { var client = hdb.createClient(options); - let maliciousInput = req.body.data; // $ MISSING: Source + let maliciousInput = req.body.data; // $ Source client.connect(function onconnect(err) { - async.series([client.exec.bind(client, "INSERT INTO NUMBERS VALUES (1, 'one')" + maliciousInput)], function (err) {}); // $ MISSING: Alert + async.series([client.exec.bind(client, "INSERT INTO NUMBERS VALUES (1, 'one')" + maliciousInput)], function (err) {}); // $ Alert - client.exec('select * from DUMMY' + maliciousInput, function (err, rows) {}); // $ MISSING: Alert - client.exec('select * from DUMMY' + maliciousInput, options, function(err, rows) {}); // $ MISSING: Alert + client.exec('select * from DUMMY' + maliciousInput, function (err, rows) {}); // $ Alert + client.exec('select * from DUMMY' + maliciousInput, options, function(err, rows) {}); // $ Alert client.prepare('select * from DUMMY where DUMMY = ?' + maliciousInput, function (err, statement){ // $ MISSING: Alert statement.exec([maliciousInput], function (err, rows) {}); // maliciousInput is treated as a parameter From d28af9508a267a0fea8e0700d05d6e639b4eb119 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 25 Mar 2025 14:40:10 +0100 Subject: [PATCH 03/13] Added sink models for `hana`'s client `prepare` function. --- .../ql/lib/ext/hana-db-client.model.yml | 4 +-- .../CWE-089/untyped/SqlInjection.expected | 34 +++++++++++++++++++ .../Security/CWE-089/untyped/hana.js | 16 ++++----- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/javascript/ql/lib/ext/hana-db-client.model.yml b/javascript/ql/lib/ext/hana-db-client.model.yml index d8ef367b88d7..1e52784e966c 100644 --- a/javascript/ql/lib/ext/hana-db-client.model.yml +++ b/javascript/ql/lib/ext/hana-db-client.model.yml @@ -3,6 +3,6 @@ extensions: pack: codeql/javascript-all extensible: sinkModel data: - - ["@sap/hana-client", "Member[createConnection].ReturnValue.Member[exec].Argument[0]", "sql-injection"] + - ["@sap/hana-client", "Member[createConnection].ReturnValue.Member[exec,prepare].Argument[0]", "sql-injection"] - - ["hdb", "Member[createClient].ReturnValue.Member[exec].Argument[0]", "sql-injection"] + - ["hdb", "Member[createClient].ReturnValue.Member[exec,prepare].Argument[0]", "sql-injection"] diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected index a3c873912880..63abc5d33477 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected @@ -11,9 +11,14 @@ | graphql.js:82:14:88:8 | `{\\n ... }` | graphql.js:73:14:73:25 | req.query.id | graphql.js:82:14:88:8 | `{\\n ... }` | This query string depends on a $@. | graphql.js:73:14:73:25 | req.query.id | user-provided value | | graphql.js:118:38:118:48 | `foo ${id}` | graphql.js:117:16:117:28 | req.params.id | graphql.js:118:38:118:48 | `foo ${id}` | This query string depends on a $@. | graphql.js:117:16:117:28 | req.params.id | user-provided value | | hana.js:11:19:11:23 | query | hana.js:9:30:9:37 | req.body | hana.js:11:19:11:23 | query | This query string depends on a $@. | hana.js:9:30:9:37 | req.body | user-provided value | +| hana.js:17:35:17:100 | `SELECT ... usInput | hana.js:16:32:16:39 | req.body | hana.js:17:35:17:100 | `SELECT ... usInput | This query string depends on a $@. | hana.js:16:32:16:39 | req.body | user-provided value | +| hana.js:24:33:24:96 | `INSERT ... usInput | hana.js:23:32:23:39 | req.body | hana.js:24:33:24:96 | `INSERT ... usInput | This query string depends on a $@. | hana.js:23:32:23:39 | req.body | user-provided value | +| hana.js:31:31:31:97 | "SELECT ... usInput | hana.js:30:30:30:37 | req.body | hana.js:31:31:31:97 | "SELECT ... usInput | This query string depends on a $@. | hana.js:30:30:30:37 | req.body | user-provided value | | hana.js:71:44:71:99 | "INSERT ... usInput | hana.js:68:24:68:31 | req.body | hana.js:71:44:71:99 | "INSERT ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | hana.js:73:17:73:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:73:17:73:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | hana.js:74:17:74:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:74:17:74:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | +| hana.js:76:20:76:73 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:76:20:76:73 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | +| hana.js:80:20:80:69 | 'call P ... usInput | hana.js:68:24:68:31 | req.body | hana.js:80:20:80:69 | 'call P ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | html-sanitizer.js:16:9:16:59 | `SELECT ... param1 | html-sanitizer.js:13:39:13:44 | param1 | html-sanitizer.js:16:9:16:59 | `SELECT ... param1 | This query string depends on a $@. | html-sanitizer.js:13:39:13:44 | param1 | user-provided value | | json-schema-validator.js:33:22:33:26 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:33:22:33:26 | query | This query object depends on a $@. | json-schema-validator.js:25:34:25:47 | req.query.data | user-provided value | | json-schema-validator.js:35:18:35:22 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:35:18:35:22 | query | This query object depends on a $@. | json-schema-validator.js:25:34:25:47 | req.query.data | user-provided value | @@ -160,13 +165,26 @@ edges | hana.js:9:30:9:37 | req.body | hana.js:9:13:9:42 | maliciousInput | provenance | | | hana.js:10:15:10:80 | query | hana.js:11:19:11:23 | query | provenance | | | hana.js:10:64:10:77 | maliciousInput | hana.js:10:15:10:80 | query | provenance | | +| hana.js:16:15:16:44 | maliciousInput | hana.js:17:87:17:100 | maliciousInput | provenance | | +| hana.js:16:32:16:39 | req.body | hana.js:16:15:16:44 | maliciousInput | provenance | | +| hana.js:17:87:17:100 | maliciousInput | hana.js:17:35:17:100 | `SELECT ... usInput | provenance | | +| hana.js:23:15:23:44 | maliciousInput | hana.js:24:83:24:96 | maliciousInput | provenance | | +| hana.js:23:32:23:39 | req.body | hana.js:23:15:23:44 | maliciousInput | provenance | | +| hana.js:24:83:24:96 | maliciousInput | hana.js:24:33:24:96 | `INSERT ... usInput | provenance | | +| hana.js:30:13:30:42 | maliciousInput | hana.js:31:84:31:97 | maliciousInput | provenance | | +| hana.js:30:30:30:37 | req.body | hana.js:30:13:30:42 | maliciousInput | provenance | | +| hana.js:31:84:31:97 | maliciousInput | hana.js:31:31:31:97 | "SELECT ... usInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:71:86:71:99 | maliciousInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:73:41:73:54 | maliciousInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:74:41:74:54 | maliciousInput | provenance | | +| hana.js:68:7:68:36 | maliciousInput | hana.js:76:60:76:73 | maliciousInput | provenance | | +| hana.js:68:7:68:36 | maliciousInput | hana.js:80:56:80:69 | maliciousInput | provenance | | | hana.js:68:24:68:31 | req.body | hana.js:68:7:68:36 | maliciousInput | provenance | | | hana.js:71:86:71:99 | maliciousInput | hana.js:71:44:71:99 | "INSERT ... usInput | provenance | | | hana.js:73:41:73:54 | maliciousInput | hana.js:73:17:73:54 | 'select ... usInput | provenance | | | hana.js:74:41:74:54 | maliciousInput | hana.js:74:17:74:54 | 'select ... usInput | provenance | | +| hana.js:76:60:76:73 | maliciousInput | hana.js:76:20:76:73 | 'select ... usInput | provenance | | +| hana.js:80:56:80:69 | maliciousInput | hana.js:80:20:80:69 | 'call P ... usInput | provenance | | | html-sanitizer.js:13:39:13:44 | param1 | html-sanitizer.js:14:18:14:23 | param1 | provenance | | | html-sanitizer.js:14:5:14:24 | param1 | html-sanitizer.js:16:54:16:59 | param1 | provenance | | | html-sanitizer.js:14:14:14:24 | xss(param1) | html-sanitizer.js:14:5:14:24 | param1 | provenance | | @@ -524,6 +542,18 @@ nodes | hana.js:10:15:10:80 | query | semmle.label | query | | hana.js:10:64:10:77 | maliciousInput | semmle.label | maliciousInput | | hana.js:11:19:11:23 | query | semmle.label | query | +| hana.js:16:15:16:44 | maliciousInput | semmle.label | maliciousInput | +| hana.js:16:32:16:39 | req.body | semmle.label | req.body | +| hana.js:17:35:17:100 | `SELECT ... usInput | semmle.label | `SELECT ... usInput | +| hana.js:17:87:17:100 | maliciousInput | semmle.label | maliciousInput | +| hana.js:23:15:23:44 | maliciousInput | semmle.label | maliciousInput | +| hana.js:23:32:23:39 | req.body | semmle.label | req.body | +| hana.js:24:33:24:96 | `INSERT ... usInput | semmle.label | `INSERT ... usInput | +| hana.js:24:83:24:96 | maliciousInput | semmle.label | maliciousInput | +| hana.js:30:13:30:42 | maliciousInput | semmle.label | maliciousInput | +| hana.js:30:30:30:37 | req.body | semmle.label | req.body | +| hana.js:31:31:31:97 | "SELECT ... usInput | semmle.label | "SELECT ... usInput | +| hana.js:31:84:31:97 | maliciousInput | semmle.label | maliciousInput | | hana.js:68:7:68:36 | maliciousInput | semmle.label | maliciousInput | | hana.js:68:24:68:31 | req.body | semmle.label | req.body | | hana.js:71:44:71:99 | "INSERT ... usInput | semmle.label | "INSERT ... usInput | @@ -532,6 +562,10 @@ nodes | hana.js:73:41:73:54 | maliciousInput | semmle.label | maliciousInput | | hana.js:74:17:74:54 | 'select ... usInput | semmle.label | 'select ... usInput | | hana.js:74:41:74:54 | maliciousInput | semmle.label | maliciousInput | +| hana.js:76:20:76:73 | 'select ... usInput | semmle.label | 'select ... usInput | +| hana.js:76:60:76:73 | maliciousInput | semmle.label | maliciousInput | +| hana.js:80:20:80:69 | 'call P ... usInput | semmle.label | 'call P ... usInput | +| hana.js:80:56:80:69 | maliciousInput | semmle.label | maliciousInput | | html-sanitizer.js:13:39:13:44 | param1 | semmle.label | param1 | | html-sanitizer.js:14:5:14:24 | param1 | semmle.label | param1 | | html-sanitizer.js:14:14:14:24 | xss(param1) | semmle.label | xss(param1) | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js index 2b3aea9ae1d5..1bbaf70b6709 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js @@ -13,22 +13,22 @@ app.post('/documents/find', (req, res) => { }); conn.connect(connectionParams, (err) => { - const maliciousInput = req.body.data; // $ MISSING: Source - const stmt = conn.prepare(`SELECT * FROM Test WHERE ID = ? AND username = ` + maliciousInput); // $ MISSING: Alert + const maliciousInput = req.body.data; // $ Source + const stmt = conn.prepare(`SELECT * FROM Test WHERE ID = ? AND username = ` + maliciousInput); // $ Alert stmt.exec([maliciousInput], (err, rows) => {}); // maliciousInput is treated as a parameter conn.disconnect(); }); conn.connect(connectionParams, (err) => { - const maliciousInput = req.body.data; // $ MISSING: Source - var stmt = conn.prepare(`INSERT INTO Customers(ID, NAME) VALUES(?, ?) ` + maliciousInput); // $ MISSING: Alert + const maliciousInput = req.body.data; // $ Source + var stmt = conn.prepare(`INSERT INTO Customers(ID, NAME) VALUES(?, ?) ` + maliciousInput); // $ Alert stmt.execBatch([[1, maliciousInput], [2, maliciousInput]], function(err, rows) {}); // maliciousInput is treated as a parameter conn.disconnect(); }); conn.connect(connectionParams, (err) => { - const maliciousInput = req.body.data; // $ MISSING: Source - var stmt = conn.prepare("SELECT * FROM Customers WHERE ID >= ? AND ID < ?" + maliciousInput); // $ MISSING: Alert + const maliciousInput = req.body.data; // $ Source + var stmt = conn.prepare("SELECT * FROM Customers WHERE ID >= ? AND ID < ?" + maliciousInput); // $ Alert stmt.execQuery([100, maliciousInput], function(err, rs) {}); // $ maliciousInput is treated as a parameter conn.disconnect(); }); @@ -73,11 +73,11 @@ app2.post('/documents/find', (req, res) => { client.exec('select * from DUMMY' + maliciousInput, function (err, rows) {}); // $ Alert client.exec('select * from DUMMY' + maliciousInput, options, function(err, rows) {}); // $ Alert - client.prepare('select * from DUMMY where DUMMY = ?' + maliciousInput, function (err, statement){ // $ MISSING: Alert + client.prepare('select * from DUMMY where DUMMY = ?' + maliciousInput, function (err, statement){ // $ Alert statement.exec([maliciousInput], function (err, rows) {}); // maliciousInput is treated as a parameter }); - client.prepare('call PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function(err, statement){ // $ MISSING: Alert + client.prepare('call PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function(err, statement){ // $ Alert statement.exec({A: 3, B: maliciousInput}, function(err, parameters, dummyRows, tableRows) {}); }); From e595def8b039062dd81cd30830b8fb0d144eb315 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 25 Mar 2025 14:44:37 +0100 Subject: [PATCH 04/13] Modeled `execute` as potential `hana`'s sink. --- javascript/ql/lib/ext/hana-db-client.model.yml | 2 +- .../Security/CWE-089/untyped/SqlInjection.expected | 5 +++++ .../ql/test/query-tests/Security/CWE-089/untyped/hana.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/ext/hana-db-client.model.yml b/javascript/ql/lib/ext/hana-db-client.model.yml index 1e52784e966c..bb2d10669f8c 100644 --- a/javascript/ql/lib/ext/hana-db-client.model.yml +++ b/javascript/ql/lib/ext/hana-db-client.model.yml @@ -5,4 +5,4 @@ extensions: data: - ["@sap/hana-client", "Member[createConnection].ReturnValue.Member[exec,prepare].Argument[0]", "sql-injection"] - - ["hdb", "Member[createClient].ReturnValue.Member[exec,prepare].Argument[0]", "sql-injection"] + - ["hdb", "Member[createClient].ReturnValue.Member[exec,prepare,execute].Argument[0]", "sql-injection"] diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected index 63abc5d33477..a1ce243a518c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected @@ -19,6 +19,7 @@ | hana.js:74:17:74:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:74:17:74:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | hana.js:76:20:76:73 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:76:20:76:73 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | hana.js:80:20:80:69 | 'call P ... usInput | hana.js:68:24:68:31 | req.body | hana.js:80:20:80:69 | 'call P ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | +| hana.js:84:20:84:78 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:84:20:84:78 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | html-sanitizer.js:16:9:16:59 | `SELECT ... param1 | html-sanitizer.js:13:39:13:44 | param1 | html-sanitizer.js:16:9:16:59 | `SELECT ... param1 | This query string depends on a $@. | html-sanitizer.js:13:39:13:44 | param1 | user-provided value | | json-schema-validator.js:33:22:33:26 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:33:22:33:26 | query | This query object depends on a $@. | json-schema-validator.js:25:34:25:47 | req.query.data | user-provided value | | json-schema-validator.js:35:18:35:22 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:35:18:35:22 | query | This query object depends on a $@. | json-schema-validator.js:25:34:25:47 | req.query.data | user-provided value | @@ -179,12 +180,14 @@ edges | hana.js:68:7:68:36 | maliciousInput | hana.js:74:41:74:54 | maliciousInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:76:60:76:73 | maliciousInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:80:56:80:69 | maliciousInput | provenance | | +| hana.js:68:7:68:36 | maliciousInput | hana.js:84:65:84:78 | maliciousInput | provenance | | | hana.js:68:24:68:31 | req.body | hana.js:68:7:68:36 | maliciousInput | provenance | | | hana.js:71:86:71:99 | maliciousInput | hana.js:71:44:71:99 | "INSERT ... usInput | provenance | | | hana.js:73:41:73:54 | maliciousInput | hana.js:73:17:73:54 | 'select ... usInput | provenance | | | hana.js:74:41:74:54 | maliciousInput | hana.js:74:17:74:54 | 'select ... usInput | provenance | | | hana.js:76:60:76:73 | maliciousInput | hana.js:76:20:76:73 | 'select ... usInput | provenance | | | hana.js:80:56:80:69 | maliciousInput | hana.js:80:20:80:69 | 'call P ... usInput | provenance | | +| hana.js:84:65:84:78 | maliciousInput | hana.js:84:20:84:78 | 'select ... usInput | provenance | | | html-sanitizer.js:13:39:13:44 | param1 | html-sanitizer.js:14:18:14:23 | param1 | provenance | | | html-sanitizer.js:14:5:14:24 | param1 | html-sanitizer.js:16:54:16:59 | param1 | provenance | | | html-sanitizer.js:14:14:14:24 | xss(param1) | html-sanitizer.js:14:5:14:24 | param1 | provenance | | @@ -566,6 +569,8 @@ nodes | hana.js:76:60:76:73 | maliciousInput | semmle.label | maliciousInput | | hana.js:80:20:80:69 | 'call P ... usInput | semmle.label | 'call P ... usInput | | hana.js:80:56:80:69 | maliciousInput | semmle.label | maliciousInput | +| hana.js:84:20:84:78 | 'select ... usInput | semmle.label | 'select ... usInput | +| hana.js:84:65:84:78 | maliciousInput | semmle.label | maliciousInput | | html-sanitizer.js:13:39:13:44 | param1 | semmle.label | param1 | | html-sanitizer.js:14:5:14:24 | param1 | semmle.label | param1 | | html-sanitizer.js:14:14:14:24 | xss(param1) | semmle.label | xss(param1) | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js index 1bbaf70b6709..b620e352607a 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js @@ -81,6 +81,6 @@ app2.post('/documents/find', (req, res) => { statement.exec({A: 3, B: maliciousInput}, function(err, parameters, dummyRows, tableRows) {}); }); - client.execute('select A, B from TEST.NUMBERS order by A' + maliciousInput, function(err, rs) {}); // $ MISSING: Alert + client.execute('select A, B from TEST.NUMBERS order by A' + maliciousInput, function(err, rs) {}); // $ Alert }); }); From 0285cb6c7a2c22e95b0bec074eb3113ae4fcfd0e Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 25 Mar 2025 14:48:40 +0100 Subject: [PATCH 05/13] Added `@sap/hdbext.loadProccedure` as sql sink. --- javascript/ql/lib/ext/hana-db-client.model.yml | 2 +- .../Security/CWE-089/untyped/SqlInjection.expected | 14 ++++++++++++++ .../query-tests/Security/CWE-089/untyped/hana.js | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/ext/hana-db-client.model.yml b/javascript/ql/lib/ext/hana-db-client.model.yml index bb2d10669f8c..08ccc41beab7 100644 --- a/javascript/ql/lib/ext/hana-db-client.model.yml +++ b/javascript/ql/lib/ext/hana-db-client.model.yml @@ -4,5 +4,5 @@ extensions: extensible: sinkModel data: - ["@sap/hana-client", "Member[createConnection].ReturnValue.Member[exec,prepare].Argument[0]", "sql-injection"] - - ["hdb", "Member[createClient].ReturnValue.Member[exec,prepare,execute].Argument[0]", "sql-injection"] + - ["@sap/hdbext", "Member[loadProcedure].Argument[2]", "sql-injection"] diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected index a1ce243a518c..62c0c51abce0 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected @@ -14,6 +14,7 @@ | hana.js:17:35:17:100 | `SELECT ... usInput | hana.js:16:32:16:39 | req.body | hana.js:17:35:17:100 | `SELECT ... usInput | This query string depends on a $@. | hana.js:16:32:16:39 | req.body | user-provided value | | hana.js:24:33:24:96 | `INSERT ... usInput | hana.js:23:32:23:39 | req.body | hana.js:24:33:24:96 | `INSERT ... usInput | This query string depends on a $@. | hana.js:23:32:23:39 | req.body | user-provided value | | hana.js:31:31:31:97 | "SELECT ... usInput | hana.js:30:30:30:37 | req.body | hana.js:31:31:31:97 | "SELECT ... usInput | This query string depends on a $@. | hana.js:30:30:30:37 | req.body | user-provided value | +| hana.js:54:38:54:66 | 'PROC_D ... usInput | hana.js:47:24:47:31 | req.body | hana.js:54:38:54:66 | 'PROC_D ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value | | hana.js:71:44:71:99 | "INSERT ... usInput | hana.js:68:24:68:31 | req.body | hana.js:71:44:71:99 | "INSERT ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | hana.js:73:17:73:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:73:17:73:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | hana.js:74:17:74:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:74:17:74:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | @@ -175,6 +176,13 @@ edges | hana.js:30:13:30:42 | maliciousInput | hana.js:31:84:31:97 | maliciousInput | provenance | | | hana.js:30:30:30:37 | req.body | hana.js:30:13:30:42 | maliciousInput | provenance | | | hana.js:31:84:31:97 | maliciousInput | hana.js:31:31:31:97 | "SELECT ... usInput | provenance | | +| hana.js:47:7:47:36 | maliciousInput | hana.js:48:39:48:52 | maliciousInput | provenance | | +| hana.js:47:7:47:36 | maliciousInput | hana.js:50:76:50:89 | maliciousInput | provenance | | +| hana.js:47:7:47:36 | maliciousInput | hana.js:54:53:54:66 | maliciousInput | provenance | | +| hana.js:47:24:47:31 | req.body | hana.js:47:7:47:36 | maliciousInput | provenance | | +| hana.js:48:39:48:52 | maliciousInput | hana.js:50:76:50:89 | maliciousInput | provenance | | +| hana.js:50:76:50:89 | maliciousInput | hana.js:54:53:54:66 | maliciousInput | provenance | | +| hana.js:54:53:54:66 | maliciousInput | hana.js:54:38:54:66 | 'PROC_D ... usInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:71:86:71:99 | maliciousInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:73:41:73:54 | maliciousInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:74:41:74:54 | maliciousInput | provenance | | @@ -557,6 +565,12 @@ nodes | hana.js:30:30:30:37 | req.body | semmle.label | req.body | | hana.js:31:31:31:97 | "SELECT ... usInput | semmle.label | "SELECT ... usInput | | hana.js:31:84:31:97 | maliciousInput | semmle.label | maliciousInput | +| hana.js:47:7:47:36 | maliciousInput | semmle.label | maliciousInput | +| hana.js:47:24:47:31 | req.body | semmle.label | req.body | +| hana.js:48:39:48:52 | maliciousInput | semmle.label | maliciousInput | +| hana.js:50:76:50:89 | maliciousInput | semmle.label | maliciousInput | +| hana.js:54:38:54:66 | 'PROC_D ... usInput | semmle.label | 'PROC_D ... usInput | +| hana.js:54:53:54:66 | maliciousInput | semmle.label | maliciousInput | | hana.js:68:7:68:36 | maliciousInput | semmle.label | maliciousInput | | hana.js:68:24:68:31 | req.body | semmle.label | req.body | | hana.js:71:44:71:99 | "INSERT ... usInput | semmle.label | "INSERT ... usInput | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js index b620e352607a..2d892a9864c1 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js @@ -44,14 +44,14 @@ app1.use(hdbext.middleware(hanaConfig)); app1.get('/execute-query', function (req, res) { var client = req.db; - let maliciousInput = req.body.data; // $ MISSING: Source + let maliciousInput = req.body.data; // $ Source client.exec('SELECT * FROM DUMMY' + maliciousInput, function (err, rs) {}); // $ MISSING: Alert dbStream.createProcStatement(client, 'CALL PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function (err, stmt) { // $ MISSING: Alert stmt.exec({ A: maliciousInput, B: 4 }, function (err, params, dummyRows, tablesRows) {}); // maliciousInput is treated as a parameter }); - hdbext.loadProcedure(client, null, 'PROC_DUMMY' + maliciousInput, function(err, sp) { // $ MISSING: Alert + hdbext.loadProcedure(client, null, 'PROC_DUMMY' + maliciousInput, function(err, sp) { // $ Alert sp(3, maliciousInput, function(err, parameters, dummyRows, tablesRows) {}); // maliciousInput is treated as a parameter }); }); From 7cc0634f573ed4015a1a9ae194283694babd341b Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 25 Mar 2025 14:50:38 +0100 Subject: [PATCH 06/13] Added `createProcStatement` as potential sql sink. --- javascript/ql/lib/ext/hana-db-client.model.yml | 1 + .../query-tests/Security/CWE-089/untyped/SqlInjection.expected | 3 +++ .../ql/test/query-tests/Security/CWE-089/untyped/hana.js | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/ext/hana-db-client.model.yml b/javascript/ql/lib/ext/hana-db-client.model.yml index 08ccc41beab7..f1a02189ef24 100644 --- a/javascript/ql/lib/ext/hana-db-client.model.yml +++ b/javascript/ql/lib/ext/hana-db-client.model.yml @@ -6,3 +6,4 @@ extensions: - ["@sap/hana-client", "Member[createConnection].ReturnValue.Member[exec,prepare].Argument[0]", "sql-injection"] - ["hdb", "Member[createClient].ReturnValue.Member[exec,prepare,execute].Argument[0]", "sql-injection"] - ["@sap/hdbext", "Member[loadProcedure].Argument[2]", "sql-injection"] + - ["@sap/hana-client/extension/Stream", "Member[createProcStatement].Argument[1]", "sql-injection"] diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected index 62c0c51abce0..df2f4ea5aed1 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected @@ -14,6 +14,7 @@ | hana.js:17:35:17:100 | `SELECT ... usInput | hana.js:16:32:16:39 | req.body | hana.js:17:35:17:100 | `SELECT ... usInput | This query string depends on a $@. | hana.js:16:32:16:39 | req.body | user-provided value | | hana.js:24:33:24:96 | `INSERT ... usInput | hana.js:23:32:23:39 | req.body | hana.js:24:33:24:96 | `INSERT ... usInput | This query string depends on a $@. | hana.js:23:32:23:39 | req.body | user-provided value | | hana.js:31:31:31:97 | "SELECT ... usInput | hana.js:30:30:30:37 | req.body | hana.js:31:31:31:97 | "SELECT ... usInput | This query string depends on a $@. | hana.js:30:30:30:37 | req.body | user-provided value | +| hana.js:50:40:50:89 | 'CALL P ... usInput | hana.js:47:24:47:31 | req.body | hana.js:50:40:50:89 | 'CALL P ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value | | hana.js:54:38:54:66 | 'PROC_D ... usInput | hana.js:47:24:47:31 | req.body | hana.js:54:38:54:66 | 'PROC_D ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value | | hana.js:71:44:71:99 | "INSERT ... usInput | hana.js:68:24:68:31 | req.body | hana.js:71:44:71:99 | "INSERT ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | | hana.js:73:17:73:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:73:17:73:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | @@ -181,6 +182,7 @@ edges | hana.js:47:7:47:36 | maliciousInput | hana.js:54:53:54:66 | maliciousInput | provenance | | | hana.js:47:24:47:31 | req.body | hana.js:47:7:47:36 | maliciousInput | provenance | | | hana.js:48:39:48:52 | maliciousInput | hana.js:50:76:50:89 | maliciousInput | provenance | | +| hana.js:50:76:50:89 | maliciousInput | hana.js:50:40:50:89 | 'CALL P ... usInput | provenance | | | hana.js:50:76:50:89 | maliciousInput | hana.js:54:53:54:66 | maliciousInput | provenance | | | hana.js:54:53:54:66 | maliciousInput | hana.js:54:38:54:66 | 'PROC_D ... usInput | provenance | | | hana.js:68:7:68:36 | maliciousInput | hana.js:71:86:71:99 | maliciousInput | provenance | | @@ -568,6 +570,7 @@ nodes | hana.js:47:7:47:36 | maliciousInput | semmle.label | maliciousInput | | hana.js:47:24:47:31 | req.body | semmle.label | req.body | | hana.js:48:39:48:52 | maliciousInput | semmle.label | maliciousInput | +| hana.js:50:40:50:89 | 'CALL P ... usInput | semmle.label | 'CALL P ... usInput | | hana.js:50:76:50:89 | maliciousInput | semmle.label | maliciousInput | | hana.js:54:38:54:66 | 'PROC_D ... usInput | semmle.label | 'PROC_D ... usInput | | hana.js:54:53:54:66 | maliciousInput | semmle.label | maliciousInput | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js index 2d892a9864c1..a8fad9008655 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js @@ -47,7 +47,7 @@ app1.get('/execute-query', function (req, res) { let maliciousInput = req.body.data; // $ Source client.exec('SELECT * FROM DUMMY' + maliciousInput, function (err, rs) {}); // $ MISSING: Alert - dbStream.createProcStatement(client, 'CALL PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function (err, stmt) { // $ MISSING: Alert + dbStream.createProcStatement(client, 'CALL PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function (err, stmt) { // $ Alert stmt.exec({ A: maliciousInput, B: 4 }, function (err, params, dummyRows, tablesRows) {}); // maliciousInput is treated as a parameter }); From 4cdc40d1150912deea084ee9124662bd1e913c7e Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 25 Mar 2025 18:39:54 +0100 Subject: [PATCH 07/13] Added SQL injection detection for `exec` method embeded Express client from `hdbext`. --- javascript/ql/lib/ext/hana-db-client.model.yml | 1 + .../query-tests/Security/CWE-089/untyped/SqlInjection.expected | 3 +++ .../ql/test/query-tests/Security/CWE-089/untyped/hana.js | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/ext/hana-db-client.model.yml b/javascript/ql/lib/ext/hana-db-client.model.yml index f1a02189ef24..f6e177d74aef 100644 --- a/javascript/ql/lib/ext/hana-db-client.model.yml +++ b/javascript/ql/lib/ext/hana-db-client.model.yml @@ -7,3 +7,4 @@ extensions: - ["hdb", "Member[createClient].ReturnValue.Member[exec,prepare,execute].Argument[0]", "sql-injection"] - ["@sap/hdbext", "Member[loadProcedure].Argument[2]", "sql-injection"] - ["@sap/hana-client/extension/Stream", "Member[createProcStatement].Argument[1]", "sql-injection"] + - ["express", "ReturnValue.Member[get].Argument[1].Parameter[0].Member[db].Member[exec].Argument[0]", "sql-injection"] diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected index df2f4ea5aed1..843d41eb9229 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected @@ -14,6 +14,7 @@ | hana.js:17:35:17:100 | `SELECT ... usInput | hana.js:16:32:16:39 | req.body | hana.js:17:35:17:100 | `SELECT ... usInput | This query string depends on a $@. | hana.js:16:32:16:39 | req.body | user-provided value | | hana.js:24:33:24:96 | `INSERT ... usInput | hana.js:23:32:23:39 | req.body | hana.js:24:33:24:96 | `INSERT ... usInput | This query string depends on a $@. | hana.js:23:32:23:39 | req.body | user-provided value | | hana.js:31:31:31:97 | "SELECT ... usInput | hana.js:30:30:30:37 | req.body | hana.js:31:31:31:97 | "SELECT ... usInput | This query string depends on a $@. | hana.js:30:30:30:37 | req.body | user-provided value | +| hana.js:48:15:48:52 | 'SELECT ... usInput | hana.js:47:24:47:31 | req.body | hana.js:48:15:48:52 | 'SELECT ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value | | hana.js:50:40:50:89 | 'CALL P ... usInput | hana.js:47:24:47:31 | req.body | hana.js:50:40:50:89 | 'CALL P ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value | | hana.js:54:38:54:66 | 'PROC_D ... usInput | hana.js:47:24:47:31 | req.body | hana.js:54:38:54:66 | 'PROC_D ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value | | hana.js:71:44:71:99 | "INSERT ... usInput | hana.js:68:24:68:31 | req.body | hana.js:71:44:71:99 | "INSERT ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value | @@ -181,6 +182,7 @@ edges | hana.js:47:7:47:36 | maliciousInput | hana.js:50:76:50:89 | maliciousInput | provenance | | | hana.js:47:7:47:36 | maliciousInput | hana.js:54:53:54:66 | maliciousInput | provenance | | | hana.js:47:24:47:31 | req.body | hana.js:47:7:47:36 | maliciousInput | provenance | | +| hana.js:48:39:48:52 | maliciousInput | hana.js:48:15:48:52 | 'SELECT ... usInput | provenance | | | hana.js:48:39:48:52 | maliciousInput | hana.js:50:76:50:89 | maliciousInput | provenance | | | hana.js:50:76:50:89 | maliciousInput | hana.js:50:40:50:89 | 'CALL P ... usInput | provenance | | | hana.js:50:76:50:89 | maliciousInput | hana.js:54:53:54:66 | maliciousInput | provenance | | @@ -569,6 +571,7 @@ nodes | hana.js:31:84:31:97 | maliciousInput | semmle.label | maliciousInput | | hana.js:47:7:47:36 | maliciousInput | semmle.label | maliciousInput | | hana.js:47:24:47:31 | req.body | semmle.label | req.body | +| hana.js:48:15:48:52 | 'SELECT ... usInput | semmle.label | 'SELECT ... usInput | | hana.js:48:39:48:52 | maliciousInput | semmle.label | maliciousInput | | hana.js:50:40:50:89 | 'CALL P ... usInput | semmle.label | 'CALL P ... usInput | | hana.js:50:76:50:89 | maliciousInput | semmle.label | maliciousInput | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js index a8fad9008655..693e1e428ef7 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js @@ -45,7 +45,7 @@ app1.use(hdbext.middleware(hanaConfig)); app1.get('/execute-query', function (req, res) { var client = req.db; let maliciousInput = req.body.data; // $ Source - client.exec('SELECT * FROM DUMMY' + maliciousInput, function (err, rs) {}); // $ MISSING: Alert + client.exec('SELECT * FROM DUMMY' + maliciousInput, function (err, rs) {}); // $ Alert dbStream.createProcStatement(client, 'CALL PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function (err, stmt) { // $ Alert stmt.exec({ A: maliciousInput, B: 4 }, function (err, params, dummyRows, tablesRows) {}); // maliciousInput is treated as a parameter From 62ab7f50d611ac8978ed333112d67d58a2e1479e Mon Sep 17 00:00:00 2001 From: Napalys Date: Wed, 26 Mar 2025 09:33:59 +0100 Subject: [PATCH 08/13] Added change note. --- javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md diff --git a/javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md b/javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md new file mode 100644 index 000000000000..d7b0d09d7128 --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added support for `@sap/hana-client`, `@sap/hdbext` and `hdb` packages. From e69929ebc6b3e66b180349271744f9652671e62e Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 27 Mar 2025 13:01:09 +0100 Subject: [PATCH 09/13] Update javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md Co-authored-by: Erik Krogh Kristensen --- javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md b/javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md index d7b0d09d7128..170707e0e787 100644 --- a/javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md +++ b/javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* Added support for `@sap/hana-client`, `@sap/hdbext` and `hdb` packages. +* Added support for the `@sap/hana-client`, `@sap/hdbext` and `hdb` packages. From f3af23e855dbd296350aebce1d01887d9590f121 Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 28 Mar 2025 13:29:18 +0100 Subject: [PATCH 10/13] Refactored hana's DB client to use `GuardedRouteHandler`, improving precision. --- javascript/ql/lib/ext/hana-db-client.model.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/ext/hana-db-client.model.yml b/javascript/ql/lib/ext/hana-db-client.model.yml index f6e177d74aef..c30d38a751b7 100644 --- a/javascript/ql/lib/ext/hana-db-client.model.yml +++ b/javascript/ql/lib/ext/hana-db-client.model.yml @@ -4,7 +4,13 @@ extensions: extensible: sinkModel data: - ["@sap/hana-client", "Member[createConnection].ReturnValue.Member[exec,prepare].Argument[0]", "sql-injection"] - - ["hdb", "Member[createClient].ReturnValue.Member[exec,prepare,execute].Argument[0]", "sql-injection"] + - ["hdb.Client", "Member[exec,prepare,execute].Argument[0]", "sql-injection"] - ["@sap/hdbext", "Member[loadProcedure].Argument[2]", "sql-injection"] - ["@sap/hana-client/extension/Stream", "Member[createProcStatement].Argument[1]", "sql-injection"] - - ["express", "ReturnValue.Member[get].Argument[1].Parameter[0].Member[db].Member[exec].Argument[0]", "sql-injection"] + + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["hdb.Client", "hdb", "Member[createClient].ReturnValue"] + - ["hdb.Client", "@sap/hdbext", "Member[middleware].ReturnValue.GuardedRouteHandler.Parameter[0].Member[db]"] From d0e2aa8192347b6aa98bb89875e7f1a7bd866fa1 Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 28 Mar 2025 14:07:10 +0100 Subject: [PATCH 11/13] Added sources from `hana` db as `MaD`. --- javascript/ql/lib/ext/hana-db-client.model.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/javascript/ql/lib/ext/hana-db-client.model.yml b/javascript/ql/lib/ext/hana-db-client.model.yml index c30d38a751b7..1a6b1e8425a6 100644 --- a/javascript/ql/lib/ext/hana-db-client.model.yml +++ b/javascript/ql/lib/ext/hana-db-client.model.yml @@ -14,3 +14,14 @@ extensions: data: - ["hdb.Client", "hdb", "Member[createClient].ReturnValue"] - ["hdb.Client", "@sap/hdbext", "Member[middleware].ReturnValue.GuardedRouteHandler.Parameter[0].Member[db]"] + + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ['@sap/hana-client', 'Member[createConnection].ReturnValue.Member[exec].Argument[1].Parameter[1]', 'database-access-result'] + - ['@sap/hana-client', 'Member[createConnection].ReturnValue.Member[prepare].ReturnValue.Member[execBatch,exec,execQuery].Argument[1].Parameter[1]', 'database-access-result'] + - ['hdb.Client', 'Member[exec,execute].Argument[1..2].Parameter[1]', 'database-access-result'] + - ['hdb.Client', 'Member[prepare].Argument[1].Parameter[1].Member[exec].Argument[1].Parameter[2..]', 'database-access-result'] + - ["@sap/hana-client/extension/Stream", "Member[createProcStatement].Argument[2].Parameter[1].Member[exec].Argument[1].Parameter[2..]", "database-access-result"] + - ['@sap/hdbext', 'Member[loadProcedure].Argument[3].Parameter[1].Argument[2].Parameter[2..]', 'database-access-result'] From 45c8ec96df5b16e7d33a7f87d0b034efe3a88916 Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 28 Mar 2025 15:02:03 +0100 Subject: [PATCH 12/13] Added test cases for `hana` db additional sources. --- .../XssWithAdditionalSources.expected | 56 +++++++++++ .../Security/CWE-079/DomBasedXss/hana.js | 93 +++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/hana.js diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected index eb961fc83dbf..ed2611a11e37 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected @@ -153,6 +153,34 @@ nodes | event-handler-receiver.js:2:31:2:83 | '

' | semmle.label | '

' | | event-handler-receiver.js:2:49:2:61 | location.href | semmle.label | location.href | | express.js:6:15:6:33 | req.param("wobble") | semmle.label | req.param("wobble") | +| hana.js:11:37:11:40 | rows | semmle.label | rows | +| hana.js:11:37:11:51 | rows[0].comment | semmle.label | rows[0].comment | +| hana.js:16:37:16:40 | rows | semmle.label | rows | +| hana.js:16:37:16:51 | rows[0].comment | semmle.label | rows[0].comment | +| hana.js:19:37:19:40 | rows | semmle.label | rows | +| hana.js:19:37:19:51 | rows[0].comment | semmle.label | rows[0].comment | +| hana.js:22:37:22:38 | rs | semmle.label | rs | +| hana.js:22:37:22:49 | rs[0].comment | semmle.label | rs[0].comment | +| hana.js:38:31:38:32 | rs | semmle.label | rs | +| hana.js:38:31:38:43 | rs[0].comment | semmle.label | rs[0].comment | +| hana.js:43:33:43:41 | dummyRows | semmle.label | dummyRows | +| hana.js:43:33:43:52 | dummyRows[0].comment | semmle.label | dummyRows[0].comment | +| hana.js:44:33:44:42 | tablesRows | semmle.label | tablesRows | +| hana.js:44:33:44:53 | tablesR ... comment | semmle.label | tablesR ... comment | +| hana.js:50:33:50:41 | dummyRows | semmle.label | dummyRows | +| hana.js:50:33:50:52 | dummyRows[0].comment | semmle.label | dummyRows[0].comment | +| hana.js:51:33:51:42 | tablesRows | semmle.label | tablesRows | +| hana.js:51:33:51:53 | tablesR ... comment | semmle.label | tablesR ... comment | +| hana.js:70:33:70:36 | rows | semmle.label | rows | +| hana.js:70:33:70:47 | rows[0].comment | semmle.label | rows[0].comment | +| hana.js:73:33:73:36 | rows | semmle.label | rows | +| hana.js:73:33:73:47 | rows[0].comment | semmle.label | rows[0].comment | +| hana.js:84:35:84:43 | dummyRows | semmle.label | dummyRows | +| hana.js:84:35:84:54 | dummyRows[0].comment | semmle.label | dummyRows[0].comment | +| hana.js:85:35:85:43 | tableRows | semmle.label | tableRows | +| hana.js:85:35:85:54 | tableRows[0].comment | semmle.label | tableRows[0].comment | +| hana.js:90:33:90:34 | rs | semmle.label | rs | +| hana.js:90:33:90:45 | rs[0].comment | semmle.label | rs[0].comment | | jquery.js:2:7:2:40 | tainted | semmle.label | tainted | | jquery.js:2:17:2:40 | documen ... .search | semmle.label | documen ... .search | | jquery.js:4:5:4:11 | tainted | semmle.label | tainted | @@ -791,6 +819,20 @@ edges | dragAndDrop.ts:71:27:71:61 | e.dataT ... /html') | dragAndDrop.ts:71:13:71:61 | droppedHtml | provenance | | | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '

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

' | provenance | Config | +| hana.js:11:37:11:40 | rows | hana.js:11:37:11:51 | rows[0].comment | provenance | | +| hana.js:16:37:16:40 | rows | hana.js:16:37:16:51 | rows[0].comment | provenance | | +| hana.js:19:37:19:40 | rows | hana.js:19:37:19:51 | rows[0].comment | provenance | | +| hana.js:22:37:22:38 | rs | hana.js:22:37:22:49 | rs[0].comment | provenance | | +| hana.js:38:31:38:32 | rs | hana.js:38:31:38:43 | rs[0].comment | provenance | | +| hana.js:43:33:43:41 | dummyRows | hana.js:43:33:43:52 | dummyRows[0].comment | provenance | | +| hana.js:44:33:44:42 | tablesRows | hana.js:44:33:44:53 | tablesR ... comment | provenance | | +| hana.js:50:33:50:41 | dummyRows | hana.js:50:33:50:52 | dummyRows[0].comment | provenance | | +| hana.js:51:33:51:42 | tablesRows | hana.js:51:33:51:53 | tablesR ... comment | provenance | | +| hana.js:70:33:70:36 | rows | hana.js:70:33:70:47 | rows[0].comment | provenance | | +| hana.js:73:33:73:36 | rows | hana.js:73:33:73:47 | rows[0].comment | provenance | | +| hana.js:84:35:84:43 | dummyRows | hana.js:84:35:84:54 | dummyRows[0].comment | provenance | | +| hana.js:85:35:85:43 | tableRows | hana.js:85:35:85:54 | tableRows[0].comment | provenance | | +| hana.js:90:33:90:34 | rs | hana.js:90:33:90:45 | rs[0].comment | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:4:5:4:11 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:5:13:5:19 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:6:11:6:17 | tainted | provenance | | @@ -1274,6 +1316,20 @@ subpaths | various-concat-obfuscations.js:21:17:21:46 | documen ... h.attrs | various-concat-obfuscations.js:17:24:17:28 | attrs | various-concat-obfuscations.js:18:10:18:105 | '
') | various-concat-obfuscations.js:21:4:21:47 | indirec ... .attrs) | | various-concat-obfuscations.js:21:17:21:46 | documen ... h.attrs | various-concat-obfuscations.js:17:24:17:28 | attrs | various-concat-obfuscations.js:18:10:18:105 | '
') [ArrayElement] | various-concat-obfuscations.js:21:4:21:47 | indirec ... .attrs) | #select +| hana.js:11:37:11:51 | rows[0].comment | hana.js:11:37:11:40 | rows | hana.js:11:37:11:51 | rows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:11:37:11:40 | rows | user-provided value | +| hana.js:16:37:16:51 | rows[0].comment | hana.js:16:37:16:40 | rows | hana.js:16:37:16:51 | rows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:16:37:16:40 | rows | user-provided value | +| hana.js:19:37:19:51 | rows[0].comment | hana.js:19:37:19:40 | rows | hana.js:19:37:19:51 | rows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:19:37:19:40 | rows | user-provided value | +| hana.js:22:37:22:49 | rs[0].comment | hana.js:22:37:22:38 | rs | hana.js:22:37:22:49 | rs[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:22:37:22:38 | rs | user-provided value | +| hana.js:38:31:38:43 | rs[0].comment | hana.js:38:31:38:32 | rs | hana.js:38:31:38:43 | rs[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:38:31:38:32 | rs | user-provided value | +| hana.js:43:33:43:52 | dummyRows[0].comment | hana.js:43:33:43:41 | dummyRows | hana.js:43:33:43:52 | dummyRows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:43:33:43:41 | dummyRows | user-provided value | +| hana.js:44:33:44:53 | tablesR ... comment | hana.js:44:33:44:42 | tablesRows | hana.js:44:33:44:53 | tablesR ... comment | Cross-site scripting vulnerability due to $@. | hana.js:44:33:44:42 | tablesRows | user-provided value | +| hana.js:50:33:50:52 | dummyRows[0].comment | hana.js:50:33:50:41 | dummyRows | hana.js:50:33:50:52 | dummyRows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:50:33:50:41 | dummyRows | user-provided value | +| hana.js:51:33:51:53 | tablesR ... comment | hana.js:51:33:51:42 | tablesRows | hana.js:51:33:51:53 | tablesR ... comment | Cross-site scripting vulnerability due to $@. | hana.js:51:33:51:42 | tablesRows | user-provided value | +| hana.js:70:33:70:47 | rows[0].comment | hana.js:70:33:70:36 | rows | hana.js:70:33:70:47 | rows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:70:33:70:36 | rows | user-provided value | +| hana.js:73:33:73:47 | rows[0].comment | hana.js:73:33:73:36 | rows | hana.js:73:33:73:47 | rows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:73:33:73:36 | rows | user-provided value | +| hana.js:84:35:84:54 | dummyRows[0].comment | hana.js:84:35:84:43 | dummyRows | hana.js:84:35:84:54 | dummyRows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:84:35:84:43 | dummyRows | user-provided value | +| hana.js:85:35:85:54 | tableRows[0].comment | hana.js:85:35:85:43 | tableRows | hana.js:85:35:85:54 | tableRows[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:85:35:85:43 | tableRows | user-provided value | +| hana.js:90:33:90:45 | rs[0].comment | hana.js:90:33:90:34 | rs | hana.js:90:33:90:45 | rs[0].comment | Cross-site scripting vulnerability due to $@. | hana.js:90:33:90:34 | rs | user-provided value | | jwt.js:6:14:6:20 | decoded | jwt.js:4:36:4:39 | data | jwt.js:6:14:6:20 | decoded | Cross-site scripting vulnerability due to $@. | jwt.js:4:36:4:39 | data | user-provided value | | typeahead.js:10:16:10:18 | loc | typeahead.js:9:28:9:30 | loc | typeahead.js:10:16:10:18 | loc | Cross-site scripting vulnerability due to $@. | typeahead.js:9:28:9:30 | loc | user-provided value | | xmlRequest.js:9:28:9:39 | json.message | xmlRequest.js:8:31:8:46 | xhr.responseText | xmlRequest.js:9:28:9:39 | json.message | Cross-site scripting vulnerability due to $@. | xmlRequest.js:8:31:8:46 | xhr.responseText | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/hana.js b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/hana.js new file mode 100644 index 000000000000..ef7c9cd71eb1 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/hana.js @@ -0,0 +1,93 @@ +const hana = require('@sap/hana-client'); +const express = require('express'); + +const app = express(); +const connectionParams = {}; +const query = ``; +app.post('/documents/find', (req, res) => { + const conn = hana.createConnection(); + conn.connect(connectionParams, (err) => { + conn.exec(query, (err, rows) => { + document.body.innerHTML = rows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const stmt = conn.prepare(query); + stmt.exec([0], (err, rows) => { + document.body.innerHTML = rows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + stmt.execBatch([[1, "a"], [2, "b"]], function(err, rows) { + document.body.innerHTML = rows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + stmt.execQuery([100, "a"], function(err, rs) { + document.body.innerHTML = rs[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + }); +}); + +var hdbext = require('@sap/hdbext'); +var express = require('express'); +var dbStream = require('@sap/hana-client/extension/Stream'); + +var app1 = express(); +const hanaConfig = {}; +app1.use(hdbext.middleware(hanaConfig)); + +app1.get('/execute-query', function (req, res) { + var client = req.db; + client.exec(query, function (err, rs) { + document.body.innerHTML = rs[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + dbStream.createProcStatement(client, query, function (err, stmt) { + stmt.exec({ A: 1, B: 4 }, function (err, params, dummyRows, tablesRows) { + document.body.innerHTML = dummyRows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + document.body.innerHTML = tablesRows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + }); + + hdbext.loadProcedure(client, null, query, function(err, sp) { + sp(3, maliciousInput, function(err, parameters, dummyRows, tablesRows) { + document.body.innerHTML = dummyRows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + document.body.innerHTML = tablesRows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + }); +}); + + +var hdb = require('hdb'); +const async = require('async'); +const { q } = require('underscore.string'); + +const options = {}; +const app2 = express(); + +app2.post('/documents/find', (req, res) => { + var client = hdb.createClient(options); + + client.connect(function onconnect(err) { + + client.exec(query, function (err, rows) { + document.body.innerHTML = rows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + client.exec(query, options, function(err, rows) { + document.body.innerHTML = rows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + client.prepare(query, function (err, statement){ + statement.exec([1], function (err, rows) { + document.body.innerHTML = rows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + }); + + client.prepare(query, function(err, statement){ + statement.exec({A: 3, B: 1}, function(err, parameters, dummyRows, tableRows) { + document.body.innerHTML = dummyRows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + document.body.innerHTML = tableRows[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + }); + + client.execute(query, function(err, rs) { + document.body.innerHTML = rs[0].comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + }); +}); From 32d6ac8da705adc9609f449f6c5388d5886e02b9 Mon Sep 17 00:00:00 2001 From: Napalys Date: Sun, 30 Mar 2025 13:59:36 +0200 Subject: [PATCH 13/13] Add test case to ensure `exec` calls without middleware injection into `Express` are not flagged. --- .../ql/test/query-tests/Security/CWE-089/untyped/hana.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js index 693e1e428ef7..259ecbbc4d6c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js @@ -84,3 +84,12 @@ app2.post('/documents/find', (req, res) => { client.execute('select A, B from TEST.NUMBERS order by A' + maliciousInput, function(err, rs) {}); // $ Alert }); }); + +var app3 = express(); + +app3.get('/execute-query', function (req, res) { + var client = req.db; + let maliciousInput = req.body.data; + client.exec('SELECT * FROM DUMMY' + maliciousInput, function (err, rs) {}); + req.db.exec('SELECT * FROM DUMMY' + maliciousInput, function (err, rs) {}); +});