From 0a3343a07d78bb1c3e5d1cbf2ee01b633247da92 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 28 Jul 2025 17:35:29 +0200 Subject: [PATCH 01/20] Added test cases for v2 and v3 sql injection of dynamodb --- .../Security/CWE-089/untyped/dynamodb.js | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js new file mode 100644 index 000000000000..0f2d4bbd5fc5 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js @@ -0,0 +1,73 @@ +import {DynamoDBClient, ExecuteStatementCommand, BatchExecuteStatementCommand, DynamoDB} from "@aws-sdk/client-dynamodb"; +const express = require('express'); + +const app = express(); +const region = 'us-east-1'; + +app.post('/partiql/v3/execute', async (req, res) => { + const client = new DynamoDBClient({}); + let maliciousInput = req.body.data; // $ MISSING: Source + + const statement = `SELECT * FROM Users WHERE username = '${maliciousInput}'`; + const command = new ExecuteStatementCommand({ + Statement: statement + }); + await client.send(command); // $ MISSING: Alert + + const updateStatement = "UPDATE Users SET status = 'active' WHERE id = " + maliciousInput; + const updateCommand = new ExecuteStatementCommand({ + Statement: updateStatement + }); + await client.send(updateCommand); // $ MISSING: Alert + + + const batchInput = { + Statements: [{ + Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` + }, + { + Statement: "UPDATE Users SET role = 'user' WHERE username = bob" + } + ] + }; + + const batchCommand = new BatchExecuteStatementCommand(batchInput); + await client.send(batchCommand); // $ MISSING: Alert + + const batchInput2 = { + Statements: maliciousInput.map(input => ({ + Statement: `SELECT * FROM SensitiveData WHERE username = '${input}'` + })) + }; + + const batchCommand2 = new BatchExecuteStatementCommand(batchInput2); + await client.send(batchCommand2); // $ MISSING: Alert + + const client2 = new DynamoDB({}); + await client2.send(command); // $ MISSING: Alert + await client2.send(batchCommand); // $ MISSING: Alert +}); + +app.post('/partiql/v2/execute', async (req, res) => { + const AWS = require('aws-sdk'); + const dynamodb = new AWS.DynamoDB({ + region: 'us-east-1' + }); + let maliciousInput = req.body.data; // $ MISSING: Source + const params = { + Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` + }; + + dynamodb.executeStatement(params, function(err, data) {}); // $ MISSING: Alert + const params2 = { + Statements: [{ + Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` + }, + { + Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` + } + ] + }; + + dynamodb.batchExecuteStatement(params2, function(err, data) {}); // $ MISSING: Alert +}); From ae2e8b129205b19d76fd560f07ac1ee5a75c825b Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 28 Jul 2025 17:41:34 +0200 Subject: [PATCH 02/20] Added modeling of dynamodb v3 for sql injections --- javascript/ql/lib/ext/dynamodb.model.yml | 19 ++++++++++ .../CWE-089/untyped/SqlInjection.expected | 36 +++++++++++++++++++ .../Security/CWE-089/untyped/dynamodb.js | 8 ++--- 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 javascript/ql/lib/ext/dynamodb.model.yml diff --git a/javascript/ql/lib/ext/dynamodb.model.yml b/javascript/ql/lib/ext/dynamodb.model.yml new file mode 100644 index 000000000000..218ae7f217e3 --- /dev/null +++ b/javascript/ql/lib/ext/dynamodb.model.yml @@ -0,0 +1,19 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sinkModel + data: + - ["DynamoDBClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] + + - addsTo: + pack: codeql/javascript-all + extensible: summaryModel + data: + - ["@aws-sdk/client-dynamodb", "Member[ExecuteStatementCommand]", "Argument[0].Member[Statement]", "ReturnValue", "taint"] + - ["@aws-sdk/client-dynamodb", "Member[BatchExecuteStatementCommand]", "Argument[0].Member[Statements].ArrayElement.Member[Statement]", "ReturnValue", "taint"] + + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["DynamoDBClientV3", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] 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 9405e075e332..880be7d4383a 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 @@ -1,4 +1,7 @@ #select +| dynamodb.js:15:23:15:29 | command | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:15:23:15:29 | command | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | +| dynamodb.js:21:23:21:35 | updateCommand | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:21:23:21:35 | updateCommand | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | +| dynamodb.js:47:24:47:30 | command | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:47:24:47:30 | command | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | | graphql.js:9:34:19:5 | `\\n ... }\\n ` | graphql.js:8:16:8:28 | req.params.id | graphql.js:9:34:19:5 | `\\n ... }\\n ` | This query string depends on a $@. | graphql.js:8:16:8:28 | req.params.id | user-provided value | | graphql.js:26:30:26:40 | `foo ${id}` | graphql.js:25:16:25:28 | req.params.id | graphql.js:26:30:26:40 | `foo ${id}` | This query string depends on a $@. | graphql.js:25:16:25:28 | req.params.id | user-provided value | | graphql.js:29:32:29:42 | `foo ${id}` | graphql.js:25:16:25:28 | req.params.id | graphql.js:29:32:29:42 | `foo ${id}` | This query string depends on a $@. | graphql.js:25:16:25:28 | req.params.id | user-provided value | @@ -137,6 +140,22 @@ | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | tst4.js:8:46:8:60 | $routeParams.id | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | This query string depends on a $@. | tst4.js:8:46:8:60 | $routeParams.id | user-provided value | | tst.js:10:10:10:64 | 'SELECT ... d + '"' | tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' | This query string depends on a $@. | tst.js:10:46:10:58 | req.params.id | user-provided value | edges +| dynamodb.js:9:9:9:22 | maliciousInput | dynamodb.js:11:64:11:77 | maliciousInput | provenance | | +| dynamodb.js:9:9:9:22 | maliciousInput | dynamodb.js:17:80:17:93 | maliciousInput | provenance | | +| dynamodb.js:9:26:9:33 | req.body | dynamodb.js:9:9:9:22 | maliciousInput | provenance | | +| dynamodb.js:11:11:11:19 | statement | dynamodb.js:13:20:13:28 | statement | provenance | | +| dynamodb.js:11:64:11:77 | maliciousInput | dynamodb.js:11:11:11:19 | statement | provenance | | +| dynamodb.js:12:11:12:17 | command | dynamodb.js:15:23:15:29 | command | provenance | | +| dynamodb.js:12:11:12:17 | command | dynamodb.js:47:24:47:30 | command | provenance | | +| dynamodb.js:12:21:14:6 | new Exe ... \\n }) | dynamodb.js:12:11:12:17 | command | provenance | | +| dynamodb.js:12:49:14:5 | {\\n ... t\\n } [Statement] | dynamodb.js:12:21:14:6 | new Exe ... \\n }) | provenance | | +| dynamodb.js:13:20:13:28 | statement | dynamodb.js:12:49:14:5 | {\\n ... t\\n } [Statement] | provenance | | +| dynamodb.js:17:11:17:25 | updateStatement | dynamodb.js:19:20:19:34 | updateStatement | provenance | | +| dynamodb.js:17:80:17:93 | maliciousInput | dynamodb.js:17:11:17:25 | updateStatement | provenance | | +| dynamodb.js:18:11:18:23 | updateCommand | dynamodb.js:21:23:21:35 | updateCommand | provenance | | +| dynamodb.js:18:27:20:6 | new Exe ... \\n }) | dynamodb.js:18:11:18:23 | updateCommand | provenance | | +| dynamodb.js:18:55:20:5 | {\\n ... t\\n } [Statement] | dynamodb.js:18:27:20:6 | new Exe ... \\n }) | provenance | | +| dynamodb.js:19:20:19:34 | updateStatement | dynamodb.js:18:55:20:5 | {\\n ... t\\n } [Statement] | provenance | | | graphql.js:8:11:8:12 | id | graphql.js:11:46:11:47 | id | provenance | | | graphql.js:8:16:8:28 | req.params.id | graphql.js:8:11:8:12 | id | provenance | | | graphql.js:11:46:11:47 | id | graphql.js:9:34:19:5 | `\\n ... }\\n ` | provenance | | @@ -518,6 +537,23 @@ edges | tst4.js:8:46:8:60 | $routeParams.id | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | provenance | | | tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' | provenance | | nodes +| dynamodb.js:9:9:9:22 | maliciousInput | semmle.label | maliciousInput | +| dynamodb.js:9:26:9:33 | req.body | semmle.label | req.body | +| dynamodb.js:11:11:11:19 | statement | semmle.label | statement | +| dynamodb.js:11:64:11:77 | maliciousInput | semmle.label | maliciousInput | +| dynamodb.js:12:11:12:17 | command | semmle.label | command | +| dynamodb.js:12:21:14:6 | new Exe ... \\n }) | semmle.label | new Exe ... \\n }) | +| dynamodb.js:12:49:14:5 | {\\n ... t\\n } [Statement] | semmle.label | {\\n ... t\\n } [Statement] | +| dynamodb.js:13:20:13:28 | statement | semmle.label | statement | +| dynamodb.js:15:23:15:29 | command | semmle.label | command | +| dynamodb.js:17:11:17:25 | updateStatement | semmle.label | updateStatement | +| dynamodb.js:17:80:17:93 | maliciousInput | semmle.label | maliciousInput | +| dynamodb.js:18:11:18:23 | updateCommand | semmle.label | updateCommand | +| dynamodb.js:18:27:20:6 | new Exe ... \\n }) | semmle.label | new Exe ... \\n }) | +| dynamodb.js:18:55:20:5 | {\\n ... t\\n } [Statement] | semmle.label | {\\n ... t\\n } [Statement] | +| dynamodb.js:19:20:19:34 | updateStatement | semmle.label | updateStatement | +| dynamodb.js:21:23:21:35 | updateCommand | semmle.label | updateCommand | +| dynamodb.js:47:24:47:30 | command | semmle.label | command | | graphql.js:8:11:8:12 | id | semmle.label | id | | graphql.js:8:16:8:28 | req.params.id | semmle.label | req.params.id | | graphql.js:9:34:19:5 | `\\n ... }\\n ` | semmle.label | `\\n ... }\\n ` | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js index 0f2d4bbd5fc5..8b4eddc4bd76 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js @@ -6,19 +6,19 @@ const region = 'us-east-1'; app.post('/partiql/v3/execute', async (req, res) => { const client = new DynamoDBClient({}); - let maliciousInput = req.body.data; // $ MISSING: Source + let maliciousInput = req.body.data; // $ Source const statement = `SELECT * FROM Users WHERE username = '${maliciousInput}'`; const command = new ExecuteStatementCommand({ Statement: statement }); - await client.send(command); // $ MISSING: Alert + await client.send(command); // $ Alert const updateStatement = "UPDATE Users SET status = 'active' WHERE id = " + maliciousInput; const updateCommand = new ExecuteStatementCommand({ Statement: updateStatement }); - await client.send(updateCommand); // $ MISSING: Alert + await client.send(updateCommand); // $ Alert const batchInput = { @@ -44,7 +44,7 @@ app.post('/partiql/v3/execute', async (req, res) => { await client.send(batchCommand2); // $ MISSING: Alert const client2 = new DynamoDB({}); - await client2.send(command); // $ MISSING: Alert + await client2.send(command); // $ Alert await client2.send(batchCommand); // $ MISSING: Alert }); From 06ab918985399b154fc0ad40ab4834d4c8ae742f Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 28 Jul 2025 17:53:16 +0200 Subject: [PATCH 03/20] Added modeling for V2 of dynamoDB --- javascript/ql/lib/ext/dynamodb.model.yml | 3 +++ .../CWE-089/untyped/SqlInjection.expected | 18 ++++++++++++++++++ .../Security/CWE-089/untyped/dynamodb.js | 12 ++++++------ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/javascript/ql/lib/ext/dynamodb.model.yml b/javascript/ql/lib/ext/dynamodb.model.yml index 218ae7f217e3..ddbf6cc3eee1 100644 --- a/javascript/ql/lib/ext/dynamodb.model.yml +++ b/javascript/ql/lib/ext/dynamodb.model.yml @@ -4,6 +4,8 @@ extensions: extensible: sinkModel data: - ["DynamoDBClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] + - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement].Argument[0].Member[Statement]", "sql-injection"] + - ["DynamoDBClientV2", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[Statements].ArrayElement.Member[Statement]", "sql-injection"] - addsTo: pack: codeql/javascript-all @@ -17,3 +19,4 @@ extensions: extensible: typeModel data: - ["DynamoDBClientV3", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] + - ["DynamoDBClientV2", "aws-sdk", "Member[DynamoDB]"] 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 880be7d4383a..87445c661626 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 @@ -2,6 +2,9 @@ | dynamodb.js:15:23:15:29 | command | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:15:23:15:29 | command | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | | dynamodb.js:21:23:21:35 | updateCommand | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:21:23:21:35 | updateCommand | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | | dynamodb.js:47:24:47:30 | command | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:47:24:47:30 | command | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | +| dynamodb.js:58:20:58:77 | `SELECT ... nput}'` | dynamodb.js:56:26:56:33 | req.body | dynamodb.js:58:20:58:77 | `SELECT ... nput}'` | This query string depends on a $@. | dynamodb.js:56:26:56:33 | req.body | user-provided value | +| dynamodb.js:64:28:64:85 | `SELECT ... nput}'` | dynamodb.js:56:26:56:33 | req.body | dynamodb.js:64:28:64:85 | `SELECT ... nput}'` | This query string depends on a $@. | dynamodb.js:56:26:56:33 | req.body | user-provided value | +| dynamodb.js:67:28:67:85 | `SELECT ... nput}'` | dynamodb.js:56:26:56:33 | req.body | dynamodb.js:67:28:67:85 | `SELECT ... nput}'` | This query string depends on a $@. | dynamodb.js:56:26:56:33 | req.body | user-provided value | | graphql.js:9:34:19:5 | `\\n ... }\\n ` | graphql.js:8:16:8:28 | req.params.id | graphql.js:9:34:19:5 | `\\n ... }\\n ` | This query string depends on a $@. | graphql.js:8:16:8:28 | req.params.id | user-provided value | | graphql.js:26:30:26:40 | `foo ${id}` | graphql.js:25:16:25:28 | req.params.id | graphql.js:26:30:26:40 | `foo ${id}` | This query string depends on a $@. | graphql.js:25:16:25:28 | req.params.id | user-provided value | | graphql.js:29:32:29:42 | `foo ${id}` | graphql.js:25:16:25:28 | req.params.id | graphql.js:29:32:29:42 | `foo ${id}` | This query string depends on a $@. | graphql.js:25:16:25:28 | req.params.id | user-provided value | @@ -156,6 +159,13 @@ edges | dynamodb.js:18:27:20:6 | new Exe ... \\n }) | dynamodb.js:18:11:18:23 | updateCommand | provenance | | | dynamodb.js:18:55:20:5 | {\\n ... t\\n } [Statement] | dynamodb.js:18:27:20:6 | new Exe ... \\n }) | provenance | | | dynamodb.js:19:20:19:34 | updateStatement | dynamodb.js:18:55:20:5 | {\\n ... t\\n } [Statement] | provenance | | +| dynamodb.js:56:9:56:22 | maliciousInput | dynamodb.js:58:61:58:74 | maliciousInput | provenance | | +| dynamodb.js:56:9:56:22 | maliciousInput | dynamodb.js:64:69:64:82 | maliciousInput | provenance | | +| dynamodb.js:56:9:56:22 | maliciousInput | dynamodb.js:67:69:67:82 | maliciousInput | provenance | | +| dynamodb.js:56:26:56:33 | req.body | dynamodb.js:56:9:56:22 | maliciousInput | provenance | | +| dynamodb.js:58:61:58:74 | maliciousInput | dynamodb.js:58:20:58:77 | `SELECT ... nput}'` | provenance | | +| dynamodb.js:64:69:64:82 | maliciousInput | dynamodb.js:64:28:64:85 | `SELECT ... nput}'` | provenance | | +| dynamodb.js:67:69:67:82 | maliciousInput | dynamodb.js:67:28:67:85 | `SELECT ... nput}'` | provenance | | | graphql.js:8:11:8:12 | id | graphql.js:11:46:11:47 | id | provenance | | | graphql.js:8:16:8:28 | req.params.id | graphql.js:8:11:8:12 | id | provenance | | | graphql.js:11:46:11:47 | id | graphql.js:9:34:19:5 | `\\n ... }\\n ` | provenance | | @@ -554,6 +564,14 @@ nodes | dynamodb.js:19:20:19:34 | updateStatement | semmle.label | updateStatement | | dynamodb.js:21:23:21:35 | updateCommand | semmle.label | updateCommand | | dynamodb.js:47:24:47:30 | command | semmle.label | command | +| dynamodb.js:56:9:56:22 | maliciousInput | semmle.label | maliciousInput | +| dynamodb.js:56:26:56:33 | req.body | semmle.label | req.body | +| dynamodb.js:58:20:58:77 | `SELECT ... nput}'` | semmle.label | `SELECT ... nput}'` | +| dynamodb.js:58:61:58:74 | maliciousInput | semmle.label | maliciousInput | +| dynamodb.js:64:28:64:85 | `SELECT ... nput}'` | semmle.label | `SELECT ... nput}'` | +| dynamodb.js:64:69:64:82 | maliciousInput | semmle.label | maliciousInput | +| dynamodb.js:67:28:67:85 | `SELECT ... nput}'` | semmle.label | `SELECT ... nput}'` | +| dynamodb.js:67:69:67:82 | maliciousInput | semmle.label | maliciousInput | | graphql.js:8:11:8:12 | id | semmle.label | id | | graphql.js:8:16:8:28 | req.params.id | semmle.label | req.params.id | | graphql.js:9:34:19:5 | `\\n ... }\\n ` | semmle.label | `\\n ... }\\n ` | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js index 8b4eddc4bd76..ef9e3c3005ce 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/dynamodb.js @@ -53,21 +53,21 @@ app.post('/partiql/v2/execute', async (req, res) => { const dynamodb = new AWS.DynamoDB({ region: 'us-east-1' }); - let maliciousInput = req.body.data; // $ MISSING: Source + let maliciousInput = req.body.data; // $ Source const params = { - Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` + Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` // $ Alert }; - dynamodb.executeStatement(params, function(err, data) {}); // $ MISSING: Alert + dynamodb.executeStatement(params, function(err, data) {}); const params2 = { Statements: [{ - Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` + Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` // $ Alert }, { - Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` + Statement: `SELECT * FROM Users WHERE username = '${maliciousInput}'` // $ Alert } ] }; - dynamodb.batchExecuteStatement(params2, function(err, data) {}); // $ MISSING: Alert + dynamodb.batchExecuteStatement(params2, function(err, data) {}); }); From 1149617f7b3f0760ceab4c41d8425695206cd6c9 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 28 Jul 2025 17:58:05 +0200 Subject: [PATCH 04/20] Added change note --- javascript/ql/lib/change-notes/2025-07-28-dynamodb.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2025-07-28-dynamodb.md diff --git a/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md b/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md new file mode 100644 index 000000000000..8cef0a4ec0f6 --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added support for the `aws-sdk` and `@aws-sdk/client-dynamodb` packages for DynamoDB PartiQL operations. From 5e6118ef3f95c13a86c43cfe5cb9a6093e76d2be Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 09:50:46 +0200 Subject: [PATCH 05/20] Added test cases for client-s v2 and v3 sql injection --- .../Security/CWE-089/untyped/clients3.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/untyped/clients3.js diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/clients3.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/clients3.js new file mode 100644 index 000000000000..58cada55e721 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/clients3.js @@ -0,0 +1,44 @@ +const { S3Client, SelectObjectContentCommand } = require("@aws-sdk/client-s3"); +const AWS = require('aws-sdk'); +const express = require('express'); +const bodyParser = require('body-parser'); + +const app = express(); +app.use(bodyParser.json()); + +app.post('/client/v3/execute', async (req, res) => { + let maliciousInput = req.body.filter; // $ MISSING: Source + const client = new S3Client({ region: "us-east-1" }); + const params = { + Bucket: "my-bucket", + Key: "data.csv", + ExpressionType: "SQL", + Expression: "SELECT * FROM S3Object WHERE " + maliciousInput, + }; + await client.send(new SelectObjectContentCommand(params)); // $ MISSING: Alert + res.end(); +}); + +app.post('/client/v2/execute', async (req, res) => { + let maliciousInput = req.body.filter; // $ MISSING: Source + const s3 = new AWS.S3({ region: "us-east-1" }); + const params = { + Bucket: "my-bucket", + Key: "data.csv", + ExpressionType: "SQL", + Expression: "SELECT * FROM S3Object WHERE " + maliciousInput, // $ MISSING: Alert + }; + await s3.selectObjectContent(params).promise(); + res.end(); + + const params1 = { + Bucket: "my-bucket", + Key: "data.csv", + ExpressionType: "SQL", + Expression: "SELECT * FROM S3Object WHERE " + maliciousInput, // $ MISSING: Alert + }; + + s3.selectObjectContent(params1, (err, data) => { + res.end(); + }); +}); From ee1af432fe2a62a99f91918faa95a2c28836023b Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 09:52:49 +0200 Subject: [PATCH 06/20] Added modeling of client-s3 v2 and v3 --- javascript/ql/lib/ext/client-s3.model.yml | 20 +++++++++ .../CWE-089/untyped/SqlInjection.expected | 44 +++++++++++++++++++ .../Security/CWE-089/untyped/clients3.js | 10 ++--- 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 javascript/ql/lib/ext/client-s3.model.yml diff --git a/javascript/ql/lib/ext/client-s3.model.yml b/javascript/ql/lib/ext/client-s3.model.yml new file mode 100644 index 000000000000..2d81b3040e97 --- /dev/null +++ b/javascript/ql/lib/ext/client-s3.model.yml @@ -0,0 +1,20 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sinkModel + data: + - ["S3ClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] + - ["S3ClientV2", "ReturnValue.Member[selectObjectContent].Argument[0].Member[Expression]", "sql-injection"] + + - addsTo: + pack: codeql/javascript-all + extensible: summaryModel + data: + - ["@aws-sdk/client-s3", "Member[SelectObjectContentCommand]", "Argument[0].Member[Expression]", "ReturnValue", "taint"] + + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["S3ClientV3", "@aws-sdk/client-s3", "Member[S3Client]"] + - ["S3ClientV2", "aws-sdk", "Member[S3]"] 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 87445c661626..7f357c671149 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 @@ -1,4 +1,7 @@ #select +| clients3.js:18:23:18:60 | new Sel ... params) | clients3.js:10:26:10:33 | req.body | clients3.js:18:23:18:60 | new Sel ... params) | This query string depends on a $@. | clients3.js:10:26:10:33 | req.body | user-provided value | +| clients3.js:29:21:29:68 | "SELECT ... usInput | clients3.js:23:26:23:33 | req.body | clients3.js:29:21:29:68 | "SELECT ... usInput | This query string depends on a $@. | clients3.js:23:26:23:33 | req.body | user-provided value | +| clients3.js:38:21:38:68 | "SELECT ... usInput | clients3.js:23:26:23:33 | req.body | clients3.js:38:21:38:68 | "SELECT ... usInput | This query string depends on a $@. | clients3.js:23:26:23:33 | req.body | user-provided value | | dynamodb.js:15:23:15:29 | command | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:15:23:15:29 | command | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | | dynamodb.js:21:23:21:35 | updateCommand | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:21:23:21:35 | updateCommand | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | | dynamodb.js:47:24:47:30 | command | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:47:24:47:30 | command | This query string depends on a $@. | dynamodb.js:9:26:9:33 | req.body | user-provided value | @@ -143,6 +146,7 @@ | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | tst4.js:8:46:8:60 | $routeParams.id | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | This query string depends on a $@. | tst4.js:8:46:8:60 | $routeParams.id | user-provided value | | tst.js:10:10:10:64 | 'SELECT ... d + '"' | tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' | This query string depends on a $@. | tst.js:10:46:10:58 | req.params.id | user-provided value | edges +<<<<<<< HEAD | dynamodb.js:9:9:9:22 | maliciousInput | dynamodb.js:11:64:11:77 | maliciousInput | provenance | | | dynamodb.js:9:9:9:22 | maliciousInput | dynamodb.js:17:80:17:93 | maliciousInput | provenance | | | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:9:9:9:22 | maliciousInput | provenance | | @@ -151,6 +155,28 @@ edges | dynamodb.js:12:11:12:17 | command | dynamodb.js:15:23:15:29 | command | provenance | | | dynamodb.js:12:11:12:17 | command | dynamodb.js:47:24:47:30 | command | provenance | | | dynamodb.js:12:21:14:6 | new Exe ... \\n }) | dynamodb.js:12:11:12:17 | command | provenance | | +======= +| clients3.js:10:9:10:40 | maliciousInput | clients3.js:16:55:16:68 | maliciousInput | provenance | | +| clients3.js:10:26:10:33 | req.body | clients3.js:10:9:10:40 | maliciousInput | provenance | | +| clients3.js:12:11:17:5 | params [Expression] | clients3.js:18:54:18:59 | params [Expression] | provenance | | +| clients3.js:12:20:17:5 | {\\n ... ,\\n } [Expression] | clients3.js:12:11:17:5 | params [Expression] | provenance | | +| clients3.js:16:21:16:68 | "SELECT ... usInput | clients3.js:12:20:17:5 | {\\n ... ,\\n } [Expression] | provenance | | +| clients3.js:16:55:16:68 | maliciousInput | clients3.js:16:21:16:68 | "SELECT ... usInput | provenance | | +| clients3.js:18:54:18:59 | params [Expression] | clients3.js:18:23:18:60 | new Sel ... params) | provenance | | +| clients3.js:23:9:23:40 | maliciousInput | clients3.js:29:55:29:68 | maliciousInput | provenance | | +| clients3.js:23:9:23:40 | maliciousInput | clients3.js:38:55:38:68 | maliciousInput | provenance | | +| clients3.js:23:26:23:33 | req.body | clients3.js:23:9:23:40 | maliciousInput | provenance | | +| clients3.js:29:55:29:68 | maliciousInput | clients3.js:29:21:29:68 | "SELECT ... usInput | provenance | | +| clients3.js:38:55:38:68 | maliciousInput | clients3.js:38:21:38:68 | "SELECT ... usInput | provenance | | +| dynamodb.js:9:9:9:38 | maliciousInput | dynamodb.js:11:64:11:77 | maliciousInput | provenance | | +| dynamodb.js:9:9:9:38 | maliciousInput | dynamodb.js:17:80:17:93 | maliciousInput | provenance | | +| dynamodb.js:9:26:9:33 | req.body | dynamodb.js:9:9:9:38 | maliciousInput | provenance | | +| dynamodb.js:11:11:11:80 | statement | dynamodb.js:13:20:13:28 | statement | provenance | | +| dynamodb.js:11:64:11:77 | maliciousInput | dynamodb.js:11:11:11:80 | statement | provenance | | +| dynamodb.js:12:11:14:6 | command | dynamodb.js:15:23:15:29 | command | provenance | | +| dynamodb.js:12:11:14:6 | command | dynamodb.js:47:24:47:30 | command | provenance | | +| dynamodb.js:12:21:14:6 | new Exe ... \\n }) | dynamodb.js:12:11:14:6 | command | provenance | | +>>>>>>> 1af289cd7d4 (Added modeling of client-s3 v2 and v3) | dynamodb.js:12:49:14:5 | {\\n ... t\\n } [Statement] | dynamodb.js:12:21:14:6 | new Exe ... \\n }) | provenance | | | dynamodb.js:13:20:13:28 | statement | dynamodb.js:12:49:14:5 | {\\n ... t\\n } [Statement] | provenance | | | dynamodb.js:17:11:17:25 | updateStatement | dynamodb.js:19:20:19:34 | updateStatement | provenance | | @@ -547,7 +573,25 @@ edges | tst4.js:8:46:8:60 | $routeParams.id | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | provenance | | | tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' | provenance | | nodes +<<<<<<< HEAD | dynamodb.js:9:9:9:22 | maliciousInput | semmle.label | maliciousInput | +======= +| clients3.js:10:9:10:40 | maliciousInput | semmle.label | maliciousInput | +| clients3.js:10:26:10:33 | req.body | semmle.label | req.body | +| clients3.js:12:11:17:5 | params [Expression] | semmle.label | params [Expression] | +| clients3.js:12:20:17:5 | {\\n ... ,\\n } [Expression] | semmle.label | {\\n ... ,\\n } [Expression] | +| clients3.js:16:21:16:68 | "SELECT ... usInput | semmle.label | "SELECT ... usInput | +| clients3.js:16:55:16:68 | maliciousInput | semmle.label | maliciousInput | +| clients3.js:18:23:18:60 | new Sel ... params) | semmle.label | new Sel ... params) | +| clients3.js:18:54:18:59 | params [Expression] | semmle.label | params [Expression] | +| clients3.js:23:9:23:40 | maliciousInput | semmle.label | maliciousInput | +| clients3.js:23:26:23:33 | req.body | semmle.label | req.body | +| clients3.js:29:21:29:68 | "SELECT ... usInput | semmle.label | "SELECT ... usInput | +| clients3.js:29:55:29:68 | maliciousInput | semmle.label | maliciousInput | +| clients3.js:38:21:38:68 | "SELECT ... usInput | semmle.label | "SELECT ... usInput | +| clients3.js:38:55:38:68 | maliciousInput | semmle.label | maliciousInput | +| dynamodb.js:9:9:9:38 | maliciousInput | semmle.label | maliciousInput | +>>>>>>> 1af289cd7d4 (Added modeling of client-s3 v2 and v3) | dynamodb.js:9:26:9:33 | req.body | semmle.label | req.body | | dynamodb.js:11:11:11:19 | statement | semmle.label | statement | | dynamodb.js:11:64:11:77 | maliciousInput | semmle.label | maliciousInput | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/clients3.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/clients3.js index 58cada55e721..9c6bce426575 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/clients3.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/clients3.js @@ -7,7 +7,7 @@ const app = express(); app.use(bodyParser.json()); app.post('/client/v3/execute', async (req, res) => { - let maliciousInput = req.body.filter; // $ MISSING: Source + let maliciousInput = req.body.filter; // $ Source const client = new S3Client({ region: "us-east-1" }); const params = { Bucket: "my-bucket", @@ -15,18 +15,18 @@ app.post('/client/v3/execute', async (req, res) => { ExpressionType: "SQL", Expression: "SELECT * FROM S3Object WHERE " + maliciousInput, }; - await client.send(new SelectObjectContentCommand(params)); // $ MISSING: Alert + await client.send(new SelectObjectContentCommand(params)); // $ Alert res.end(); }); app.post('/client/v2/execute', async (req, res) => { - let maliciousInput = req.body.filter; // $ MISSING: Source + let maliciousInput = req.body.filter; // $ Source const s3 = new AWS.S3({ region: "us-east-1" }); const params = { Bucket: "my-bucket", Key: "data.csv", ExpressionType: "SQL", - Expression: "SELECT * FROM S3Object WHERE " + maliciousInput, // $ MISSING: Alert + Expression: "SELECT * FROM S3Object WHERE " + maliciousInput, // $ Alert }; await s3.selectObjectContent(params).promise(); res.end(); @@ -35,7 +35,7 @@ app.post('/client/v2/execute', async (req, res) => { Bucket: "my-bucket", Key: "data.csv", ExpressionType: "SQL", - Expression: "SELECT * FROM S3Object WHERE " + maliciousInput, // $ MISSING: Alert + Expression: "SELECT * FROM S3Object WHERE " + maliciousInput, // $ Alert }; s3.selectObjectContent(params1, (err, data) => { From af97b0edc2274a7c86bce65775de29fdb0c0952e Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 13:13:45 +0200 Subject: [PATCH 07/20] Added test cases for athena v2 and v3 for sql injections --- .../Security/CWE-089/untyped/athena.js | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js new file mode 100644 index 000000000000..00ef4a9f86fa --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js @@ -0,0 +1,72 @@ +const { AthenaClient, StartQueryExecutionCommand, CreateNamedQueryCommand, UpdateNamedQueryCommand } = require("@aws-sdk/client-athena"); +const AWS = require('aws-sdk'); +const express = require('express'); +const bodyParser = require('body-parser'); +const app = express(); +app.use(bodyParser.json()); + +app.post('/v3/athena/all', async (req, res) => { + const userQuery = req.body.query; // $ MISSING: Source + + const client = new AthenaClient({ region: "us-east-1" }); + + const params1 = { + QueryString: "SQL" + userQuery, + QueryExecutionContext: { Database: "default" }, + ResultConfiguration: { OutputLocation: "s3://my-results/" } + }; + const p = new StartQueryExecutionCommand(params1); + await client.send(p); // $ MISSING: Alert + + const params2 = { + Name: "user_query", + Database: "default", + QueryString: userQuery, + Description: "User-provided query" + }; + await client.send(new CreateNamedQueryCommand(params2)); // $ MISSING: Alert -- This only stores query to database, not executed + + const params3 = { + NamedQueryId: "namedQueryId", + Name: "user_query_updated", + Database: "default", + QueryString: userQuery, + Description: "Updated user-provided query" + }; + await client.send(new UpdateNamedQueryCommand(params3)); // $ MISSING: Alert -- This only stores query to database, not executed + + res.end(); +}); + + +app.post('/v2/athena/all', async (req, res) => { + const userQuery = req.body.query; // $ MISSING: Source + + const athena = new AWS.Athena({ region: "us-east-1" }); + + const params1 = { + QueryString: userQuery, // $ MISSING: Alert + QueryExecutionContext: { Database: "default" }, + ResultConfiguration: { OutputLocation: "s3://my-results/" } + }; + await athena.startQueryExecution(params1).promise(); + + const params2 = { + Name: "user_query", + Database: "default", + QueryString: userQuery, // $ MISSING: Alert -- This only stores query to database, not executed + Description: "User-provided query" + }; + await athena.createNamedQuery(params2).promise(); + + const params3 = { + NamedQueryId: "namedQueryId", + Name: "user_query_updated", + Database: "default", + QueryString: userQuery, // $ MISSING: Alert -- This only stores query to database, not executed + Description: "Updated user-provided query" + }; + await athena.updateNamedQuery(params3).promise(); + + res.end(); +}); From 0e6bac73a70d9fd0423cbf72621d9091085b394c Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 13:45:11 +0200 Subject: [PATCH 08/20] Added modeling of athena v2 and v3 for sql injections --- javascript/ql/lib/ext/athena.model.yml | 21 ++++ .../CWE-089/untyped/SqlInjection.expected | 101 ++++++++++++------ .../Security/CWE-089/untyped/athena.js | 16 +-- 3 files changed, 99 insertions(+), 39 deletions(-) create mode 100644 javascript/ql/lib/ext/athena.model.yml diff --git a/javascript/ql/lib/ext/athena.model.yml b/javascript/ql/lib/ext/athena.model.yml new file mode 100644 index 000000000000..5101d7047e3e --- /dev/null +++ b/javascript/ql/lib/ext/athena.model.yml @@ -0,0 +1,21 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sinkModel + data: + - ["AthenaClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] + - ["AthenaClientV2", "ReturnValue.Member[startQueryExecution,createNamedQuery,updateNamedQuery].Argument[0].Member[QueryString]", "sql-injection"] + + + - addsTo: + pack: codeql/javascript-all + extensible: summaryModel + data: + - ["@aws-sdk/client-athena", "Member[StartQueryExecutionCommand,CreateNamedQueryCommand,UpdateNamedQueryCommand]", "Argument[0].Member[QueryString]", "ReturnValue", "taint"] + + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["AthenaClientV3", "@aws-sdk/client-athena", "Member[AthenaClient]"] + - ["AthenaClientV2", "aws-sdk", "Member[Athena]"] 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 7f357c671149..edbb27bc84bd 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 @@ -1,4 +1,10 @@ #select +| athena.js:19:23:19:23 | p | athena.js:9:23:9:30 | req.body | athena.js:19:23:19:23 | p | This query string depends on a $@. | athena.js:9:23:9:30 | req.body | user-provided value | +| athena.js:27:23:27:58 | new Cre ... arams2) | athena.js:9:23:9:30 | req.body | athena.js:27:23:27:58 | new Cre ... arams2) | This query string depends on a $@. | athena.js:9:23:9:30 | req.body | user-provided value | +| athena.js:36:23:36:58 | new Upd ... arams3) | athena.js:9:23:9:30 | req.body | athena.js:36:23:36:58 | new Upd ... arams3) | This query string depends on a $@. | athena.js:9:23:9:30 | req.body | user-provided value | +| athena.js:48:22:48:30 | userQuery | athena.js:43:23:43:30 | req.body | athena.js:48:22:48:30 | userQuery | This query string depends on a $@. | athena.js:43:23:43:30 | req.body | user-provided value | +| athena.js:57:22:57:30 | userQuery | athena.js:43:23:43:30 | req.body | athena.js:57:22:57:30 | userQuery | This query string depends on a $@. | athena.js:43:23:43:30 | req.body | user-provided value | +| athena.js:66:22:66:30 | userQuery | athena.js:43:23:43:30 | req.body | athena.js:66:22:66:30 | userQuery | This query string depends on a $@. | athena.js:43:23:43:30 | req.body | user-provided value | | clients3.js:18:23:18:60 | new Sel ... params) | clients3.js:10:26:10:33 | req.body | clients3.js:18:23:18:60 | new Sel ... params) | This query string depends on a $@. | clients3.js:10:26:10:33 | req.body | user-provided value | | clients3.js:29:21:29:68 | "SELECT ... usInput | clients3.js:23:26:23:33 | req.body | clients3.js:29:21:29:68 | "SELECT ... usInput | This query string depends on a $@. | clients3.js:23:26:23:33 | req.body | user-provided value | | clients3.js:38:21:38:68 | "SELECT ... usInput | clients3.js:23:26:23:33 | req.body | clients3.js:38:21:38:68 | "SELECT ... usInput | This query string depends on a $@. | clients3.js:23:26:23:33 | req.body | user-provided value | @@ -146,7 +152,41 @@ | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | tst4.js:8:46:8:60 | $routeParams.id | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | This query string depends on a $@. | tst4.js:8:46:8:60 | $routeParams.id | user-provided value | | tst.js:10:10:10:64 | 'SELECT ... d + '"' | tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' | This query string depends on a $@. | tst.js:10:46:10:58 | req.params.id | user-provided value | edges -<<<<<<< HEAD +| athena.js:9:11:9:19 | userQuery | athena.js:14:30:14:38 | userQuery | provenance | | +| athena.js:9:11:9:19 | userQuery | athena.js:24:22:24:30 | userQuery | provenance | | +| athena.js:9:11:9:19 | userQuery | athena.js:33:22:33:30 | userQuery | provenance | | +| athena.js:9:23:9:30 | req.body | athena.js:9:11:9:19 | userQuery | provenance | | +| athena.js:13:11:13:17 | params1 [QueryString] | athena.js:18:46:18:52 | params1 [QueryString] | provenance | | +| athena.js:13:21:17:5 | {\\n ... }\\n } [QueryString] | athena.js:13:11:13:17 | params1 [QueryString] | provenance | | +| athena.js:14:22:14:38 | "SQL" + userQuery | athena.js:13:21:17:5 | {\\n ... }\\n } [QueryString] | provenance | | +| athena.js:14:30:14:38 | userQuery | athena.js:14:22:14:38 | "SQL" + userQuery | provenance | | +| athena.js:18:11:18:11 | p | athena.js:19:23:19:23 | p | provenance | | +| athena.js:18:15:18:53 | new Sta ... arams1) | athena.js:18:11:18:11 | p | provenance | | +| athena.js:18:46:18:52 | params1 [QueryString] | athena.js:18:15:18:53 | new Sta ... arams1) | provenance | | +| athena.js:21:11:21:17 | params2 [QueryString] | athena.js:27:51:27:57 | params2 [QueryString] | provenance | | +| athena.js:21:21:26:5 | {\\n ... "\\n } [QueryString] | athena.js:21:11:21:17 | params2 [QueryString] | provenance | | +| athena.js:24:22:24:30 | userQuery | athena.js:21:21:26:5 | {\\n ... "\\n } [QueryString] | provenance | | +| athena.js:27:51:27:57 | params2 [QueryString] | athena.js:27:23:27:58 | new Cre ... arams2) | provenance | | +| athena.js:29:11:29:17 | params3 [QueryString] | athena.js:36:51:36:57 | params3 [QueryString] | provenance | | +| athena.js:29:21:35:5 | {\\n ... "\\n } [QueryString] | athena.js:29:11:29:17 | params3 [QueryString] | provenance | | +| athena.js:33:22:33:30 | userQuery | athena.js:29:21:35:5 | {\\n ... "\\n } [QueryString] | provenance | | +| athena.js:36:51:36:57 | params3 [QueryString] | athena.js:36:23:36:58 | new Upd ... arams3) | provenance | | +| athena.js:43:11:43:19 | userQuery | athena.js:48:22:48:30 | userQuery | provenance | | +| athena.js:43:11:43:19 | userQuery | athena.js:57:22:57:30 | userQuery | provenance | | +| athena.js:43:11:43:19 | userQuery | athena.js:66:22:66:30 | userQuery | provenance | | +| athena.js:43:23:43:30 | req.body | athena.js:43:11:43:19 | userQuery | provenance | | +| clients3.js:10:9:10:22 | maliciousInput | clients3.js:16:55:16:68 | maliciousInput | provenance | | +| clients3.js:10:26:10:33 | req.body | clients3.js:10:9:10:22 | maliciousInput | provenance | | +| clients3.js:12:11:12:16 | params [Expression] | clients3.js:18:54:18:59 | params [Expression] | provenance | | +| clients3.js:12:20:17:5 | {\\n ... ,\\n } [Expression] | clients3.js:12:11:12:16 | params [Expression] | provenance | | +| clients3.js:16:21:16:68 | "SELECT ... usInput | clients3.js:12:20:17:5 | {\\n ... ,\\n } [Expression] | provenance | | +| clients3.js:16:55:16:68 | maliciousInput | clients3.js:16:21:16:68 | "SELECT ... usInput | provenance | | +| clients3.js:18:54:18:59 | params [Expression] | clients3.js:18:23:18:60 | new Sel ... params) | provenance | | +| clients3.js:23:9:23:22 | maliciousInput | clients3.js:29:55:29:68 | maliciousInput | provenance | | +| clients3.js:23:9:23:22 | maliciousInput | clients3.js:38:55:38:68 | maliciousInput | provenance | | +| clients3.js:23:26:23:33 | req.body | clients3.js:23:9:23:22 | maliciousInput | provenance | | +| clients3.js:29:55:29:68 | maliciousInput | clients3.js:29:21:29:68 | "SELECT ... usInput | provenance | | +| clients3.js:38:55:38:68 | maliciousInput | clients3.js:38:21:38:68 | "SELECT ... usInput | provenance | | | dynamodb.js:9:9:9:22 | maliciousInput | dynamodb.js:11:64:11:77 | maliciousInput | provenance | | | dynamodb.js:9:9:9:22 | maliciousInput | dynamodb.js:17:80:17:93 | maliciousInput | provenance | | | dynamodb.js:9:26:9:33 | req.body | dynamodb.js:9:9:9:22 | maliciousInput | provenance | | @@ -155,28 +195,6 @@ edges | dynamodb.js:12:11:12:17 | command | dynamodb.js:15:23:15:29 | command | provenance | | | dynamodb.js:12:11:12:17 | command | dynamodb.js:47:24:47:30 | command | provenance | | | dynamodb.js:12:21:14:6 | new Exe ... \\n }) | dynamodb.js:12:11:12:17 | command | provenance | | -======= -| clients3.js:10:9:10:40 | maliciousInput | clients3.js:16:55:16:68 | maliciousInput | provenance | | -| clients3.js:10:26:10:33 | req.body | clients3.js:10:9:10:40 | maliciousInput | provenance | | -| clients3.js:12:11:17:5 | params [Expression] | clients3.js:18:54:18:59 | params [Expression] | provenance | | -| clients3.js:12:20:17:5 | {\\n ... ,\\n } [Expression] | clients3.js:12:11:17:5 | params [Expression] | provenance | | -| clients3.js:16:21:16:68 | "SELECT ... usInput | clients3.js:12:20:17:5 | {\\n ... ,\\n } [Expression] | provenance | | -| clients3.js:16:55:16:68 | maliciousInput | clients3.js:16:21:16:68 | "SELECT ... usInput | provenance | | -| clients3.js:18:54:18:59 | params [Expression] | clients3.js:18:23:18:60 | new Sel ... params) | provenance | | -| clients3.js:23:9:23:40 | maliciousInput | clients3.js:29:55:29:68 | maliciousInput | provenance | | -| clients3.js:23:9:23:40 | maliciousInput | clients3.js:38:55:38:68 | maliciousInput | provenance | | -| clients3.js:23:26:23:33 | req.body | clients3.js:23:9:23:40 | maliciousInput | provenance | | -| clients3.js:29:55:29:68 | maliciousInput | clients3.js:29:21:29:68 | "SELECT ... usInput | provenance | | -| clients3.js:38:55:38:68 | maliciousInput | clients3.js:38:21:38:68 | "SELECT ... usInput | provenance | | -| dynamodb.js:9:9:9:38 | maliciousInput | dynamodb.js:11:64:11:77 | maliciousInput | provenance | | -| dynamodb.js:9:9:9:38 | maliciousInput | dynamodb.js:17:80:17:93 | maliciousInput | provenance | | -| dynamodb.js:9:26:9:33 | req.body | dynamodb.js:9:9:9:38 | maliciousInput | provenance | | -| dynamodb.js:11:11:11:80 | statement | dynamodb.js:13:20:13:28 | statement | provenance | | -| dynamodb.js:11:64:11:77 | maliciousInput | dynamodb.js:11:11:11:80 | statement | provenance | | -| dynamodb.js:12:11:14:6 | command | dynamodb.js:15:23:15:29 | command | provenance | | -| dynamodb.js:12:11:14:6 | command | dynamodb.js:47:24:47:30 | command | provenance | | -| dynamodb.js:12:21:14:6 | new Exe ... \\n }) | dynamodb.js:12:11:14:6 | command | provenance | | ->>>>>>> 1af289cd7d4 (Added modeling of client-s3 v2 and v3) | dynamodb.js:12:49:14:5 | {\\n ... t\\n } [Statement] | dynamodb.js:12:21:14:6 | new Exe ... \\n }) | provenance | | | dynamodb.js:13:20:13:28 | statement | dynamodb.js:12:49:14:5 | {\\n ... t\\n } [Statement] | provenance | | | dynamodb.js:17:11:17:25 | updateStatement | dynamodb.js:19:20:19:34 | updateStatement | provenance | | @@ -573,25 +591,46 @@ edges | tst4.js:8:46:8:60 | $routeParams.id | tst4.js:8:10:8:66 | 'SELECT ... d + '"' | provenance | | | tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' | provenance | | nodes -<<<<<<< HEAD -| dynamodb.js:9:9:9:22 | maliciousInput | semmle.label | maliciousInput | -======= -| clients3.js:10:9:10:40 | maliciousInput | semmle.label | maliciousInput | +| athena.js:9:11:9:19 | userQuery | semmle.label | userQuery | +| athena.js:9:23:9:30 | req.body | semmle.label | req.body | +| athena.js:13:11:13:17 | params1 [QueryString] | semmle.label | params1 [QueryString] | +| athena.js:13:21:17:5 | {\\n ... }\\n } [QueryString] | semmle.label | {\\n ... }\\n } [QueryString] | +| athena.js:14:22:14:38 | "SQL" + userQuery | semmle.label | "SQL" + userQuery | +| athena.js:14:30:14:38 | userQuery | semmle.label | userQuery | +| athena.js:18:11:18:11 | p | semmle.label | p | +| athena.js:18:15:18:53 | new Sta ... arams1) | semmle.label | new Sta ... arams1) | +| athena.js:18:46:18:52 | params1 [QueryString] | semmle.label | params1 [QueryString] | +| athena.js:19:23:19:23 | p | semmle.label | p | +| athena.js:21:11:21:17 | params2 [QueryString] | semmle.label | params2 [QueryString] | +| athena.js:21:21:26:5 | {\\n ... "\\n } [QueryString] | semmle.label | {\\n ... "\\n } [QueryString] | +| athena.js:24:22:24:30 | userQuery | semmle.label | userQuery | +| athena.js:27:23:27:58 | new Cre ... arams2) | semmle.label | new Cre ... arams2) | +| athena.js:27:51:27:57 | params2 [QueryString] | semmle.label | params2 [QueryString] | +| athena.js:29:11:29:17 | params3 [QueryString] | semmle.label | params3 [QueryString] | +| athena.js:29:21:35:5 | {\\n ... "\\n } [QueryString] | semmle.label | {\\n ... "\\n } [QueryString] | +| athena.js:33:22:33:30 | userQuery | semmle.label | userQuery | +| athena.js:36:23:36:58 | new Upd ... arams3) | semmle.label | new Upd ... arams3) | +| athena.js:36:51:36:57 | params3 [QueryString] | semmle.label | params3 [QueryString] | +| athena.js:43:11:43:19 | userQuery | semmle.label | userQuery | +| athena.js:43:23:43:30 | req.body | semmle.label | req.body | +| athena.js:48:22:48:30 | userQuery | semmle.label | userQuery | +| athena.js:57:22:57:30 | userQuery | semmle.label | userQuery | +| athena.js:66:22:66:30 | userQuery | semmle.label | userQuery | +| clients3.js:10:9:10:22 | maliciousInput | semmle.label | maliciousInput | | clients3.js:10:26:10:33 | req.body | semmle.label | req.body | -| clients3.js:12:11:17:5 | params [Expression] | semmle.label | params [Expression] | +| clients3.js:12:11:12:16 | params [Expression] | semmle.label | params [Expression] | | clients3.js:12:20:17:5 | {\\n ... ,\\n } [Expression] | semmle.label | {\\n ... ,\\n } [Expression] | | clients3.js:16:21:16:68 | "SELECT ... usInput | semmle.label | "SELECT ... usInput | | clients3.js:16:55:16:68 | maliciousInput | semmle.label | maliciousInput | | clients3.js:18:23:18:60 | new Sel ... params) | semmle.label | new Sel ... params) | | clients3.js:18:54:18:59 | params [Expression] | semmle.label | params [Expression] | -| clients3.js:23:9:23:40 | maliciousInput | semmle.label | maliciousInput | +| clients3.js:23:9:23:22 | maliciousInput | semmle.label | maliciousInput | | clients3.js:23:26:23:33 | req.body | semmle.label | req.body | | clients3.js:29:21:29:68 | "SELECT ... usInput | semmle.label | "SELECT ... usInput | | clients3.js:29:55:29:68 | maliciousInput | semmle.label | maliciousInput | | clients3.js:38:21:38:68 | "SELECT ... usInput | semmle.label | "SELECT ... usInput | | clients3.js:38:55:38:68 | maliciousInput | semmle.label | maliciousInput | -| dynamodb.js:9:9:9:38 | maliciousInput | semmle.label | maliciousInput | ->>>>>>> 1af289cd7d4 (Added modeling of client-s3 v2 and v3) +| dynamodb.js:9:9:9:22 | maliciousInput | semmle.label | maliciousInput | | dynamodb.js:9:26:9:33 | req.body | semmle.label | req.body | | dynamodb.js:11:11:11:19 | statement | semmle.label | statement | | dynamodb.js:11:64:11:77 | maliciousInput | semmle.label | maliciousInput | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js index 00ef4a9f86fa..2b661d5d4aea 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js @@ -6,7 +6,7 @@ const app = express(); app.use(bodyParser.json()); app.post('/v3/athena/all', async (req, res) => { - const userQuery = req.body.query; // $ MISSING: Source + const userQuery = req.body.query; // $ Source const client = new AthenaClient({ region: "us-east-1" }); @@ -16,7 +16,7 @@ app.post('/v3/athena/all', async (req, res) => { ResultConfiguration: { OutputLocation: "s3://my-results/" } }; const p = new StartQueryExecutionCommand(params1); - await client.send(p); // $ MISSING: Alert + await client.send(p); // $ Alert const params2 = { Name: "user_query", @@ -24,7 +24,7 @@ app.post('/v3/athena/all', async (req, res) => { QueryString: userQuery, Description: "User-provided query" }; - await client.send(new CreateNamedQueryCommand(params2)); // $ MISSING: Alert -- This only stores query to database, not executed + await client.send(new CreateNamedQueryCommand(params2)); // $ Alert -- This only stores query to database, not executed const params3 = { NamedQueryId: "namedQueryId", @@ -33,19 +33,19 @@ app.post('/v3/athena/all', async (req, res) => { QueryString: userQuery, Description: "Updated user-provided query" }; - await client.send(new UpdateNamedQueryCommand(params3)); // $ MISSING: Alert -- This only stores query to database, not executed + await client.send(new UpdateNamedQueryCommand(params3)); // $ Alert -- This only stores query to database, not executed res.end(); }); app.post('/v2/athena/all', async (req, res) => { - const userQuery = req.body.query; // $ MISSING: Source + const userQuery = req.body.query; // $ Source const athena = new AWS.Athena({ region: "us-east-1" }); const params1 = { - QueryString: userQuery, // $ MISSING: Alert + QueryString: userQuery, // $ Alert QueryExecutionContext: { Database: "default" }, ResultConfiguration: { OutputLocation: "s3://my-results/" } }; @@ -54,7 +54,7 @@ app.post('/v2/athena/all', async (req, res) => { const params2 = { Name: "user_query", Database: "default", - QueryString: userQuery, // $ MISSING: Alert -- This only stores query to database, not executed + QueryString: userQuery, // $ Alert -- This only stores query to database, not executed Description: "User-provided query" }; await athena.createNamedQuery(params2).promise(); @@ -63,7 +63,7 @@ app.post('/v2/athena/all', async (req, res) => { NamedQueryId: "namedQueryId", Name: "user_query_updated", Database: "default", - QueryString: userQuery, // $ MISSING: Alert -- This only stores query to database, not executed + QueryString: userQuery, // $ Alert -- This only stores query to database, not executed Description: "Updated user-provided query" }; await athena.updateNamedQuery(params3).promise(); From 5b5c17100ce26df72bbed0568083402439353c90 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 14:34:51 +0200 Subject: [PATCH 09/20] Added test cases for client-rds-data for sql injections --- .../Security/CWE-089/untyped/rds-client.js | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/untyped/rds-client.js diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/rds-client.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/rds-client.js new file mode 100644 index 000000000000..6899ed0338f3 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/rds-client.js @@ -0,0 +1,68 @@ +const { RDSDataClient, BatchExecuteStatementCommand, ExecuteStatementCommand, ExecuteSqlCommand } = require("@aws-sdk/client-rds-data"); +const express = require('express'); +const bodyParser = require('body-parser'); +const app = express(); +app.use(bodyParser.json()); + +app.post('/v3/rds/all', async (req, res) => { + const userQuery = req.body.query; // $ MISSING: Source + const userQueries = req.body.queries; // $ MISSING: Source + + const client = new RDSDataClient({ region: "us-east-1" }); + + const params1 = { + resourceArn: "arn:aws:rds:us-east-1:123456789012:cluster:my-aurora-cluster", + secretArn: "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret", + database: "userDatabase", + sql: userQuery + }; + await client.send(new ExecuteStatementCommand(params1)); // $ MISSING: Alert + + const params2 = { + resourceArn: "arn:aws:rds:us-east-1:123456789012:cluster:my-aurora-cluster", + secretArn: "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret", + database: "userDatabase", + parameterSets: userQueries.map(sql => ({ sql })) + }; + await client.send(new BatchExecuteStatementCommand(params2)); // $ MISSING: Alert + + const params = { + resourceArn: "...", + secretArn: "...", + database: "userDatabase", + sqlStatements: userQuery + }; + + await client.send(new ExecuteSqlCommand(params)); // $ MISSING: Alert + + res.end(); +}); + +const AWS = require('aws-sdk'); + +app.post('/v2/rds/all', async (req, res) => { + const userQuery = req.body.query; // $ MISSING: Source + const userQueries = req.body.queries; // $ MISSING: Source + + const rdsData = new AWS.RDSDataService({ region: "us-east-1" }); + + const params1 = { + resourceArn: "arn:aws:rds:us-east-1:123456789012:cluster:my-aurora-cluster", + secretArn: "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret", + database: "userDatabase", + sql: userQuery // $ MISSING: Alert + }; + await rdsData.executeStatement(params1).promise(); + + const params2 = { + resourceArn: "arn:aws:rds:us-east-1:123456789012:cluster:my-aurora-cluster", + secretArn: "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret", + database: "userDatabase", + parameterSets: userQueries.map(sql => ({ sql })) // $ MISSING: Alert + }; + await rdsData.batchExecuteStatement(params2).promise(); + + res.end(); +}); + +app.listen(3000); From e5f02852e15923892937b4073a54370d3d43a021 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 14:50:19 +0200 Subject: [PATCH 10/20] Added modeling of rds v2 and v3 for sql injections --- javascript/ql/lib/ext/rds-client.model.yml | 23 +++++++++++ .../CWE-089/untyped/SqlInjection.expected | 41 +++++++++++++++++++ .../Security/CWE-089/untyped/rds-client.js | 14 +++---- 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 javascript/ql/lib/ext/rds-client.model.yml diff --git a/javascript/ql/lib/ext/rds-client.model.yml b/javascript/ql/lib/ext/rds-client.model.yml new file mode 100644 index 000000000000..7f10fc92c9f1 --- /dev/null +++ b/javascript/ql/lib/ext/rds-client.model.yml @@ -0,0 +1,23 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sinkModel + data: + - ["RDSDataClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] + - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[0].Member[sql]", "sql-injection"] + - ["RDSDataClientV2", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "sql-injection"] + + - addsTo: + pack: codeql/javascript-all + extensible: summaryModel + data: + - ["@aws-sdk/client-rds-data", "Member[ExecuteStatementCommand,BatchExecuteStatementCommand]", "Argument[0].Member[sql]", "ReturnValue", "taint"] + - ["@aws-sdk/client-rds-data", "Member[BatchExecuteStatementCommand]", "Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "ReturnValue", "taint"] + - ["@aws-sdk/client-rds-data", "Member[ExecuteSqlCommand]", "Argument[0].Member[sqlStatements]", "ReturnValue", "taint"] + + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["RDSDataClientV3", "@aws-sdk/client-rds-data", "Member[RDSDataClient]"] + - ["RDSDataClientV2", "aws-sdk", "Member[RDSDataService]"] 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 edbb27bc84bd..623ea18de74c 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 @@ -137,6 +137,10 @@ | pg-promise.js:60:20:60:24 | query | pg-promise.js:7:16:7:34 | req.params.category | pg-promise.js:60:20:60:24 | query | This query string depends on a $@. | pg-promise.js:7:16:7:34 | req.params.category | user-provided value | | pg-promise.js:63:23:63:27 | query | pg-promise.js:7:16:7:34 | req.params.category | pg-promise.js:63:23:63:27 | query | This query string depends on a $@. | pg-promise.js:7:16:7:34 | req.params.category | user-provided value | | pg-promise.js:64:16:64:20 | query | pg-promise.js:7:16:7:34 | req.params.category | pg-promise.js:64:16:64:20 | query | This query string depends on a $@. | pg-promise.js:7:16:7:34 | req.params.category | user-provided value | +| rds-client.js:19:23:19:58 | new Exe ... arams1) | rds-client.js:8:23:8:30 | req.body | rds-client.js:19:23:19:58 | new Exe ... arams1) | This query string depends on a $@. | rds-client.js:8:23:8:30 | req.body | user-provided value | +| rds-client.js:36:23:36:51 | new Exe ... params) | rds-client.js:8:23:8:30 | req.body | rds-client.js:36:23:36:51 | new Exe ... params) | This query string depends on a $@. | rds-client.js:8:23:8:30 | req.body | user-provided value | +| rds-client.js:53:14:53:22 | userQuery | rds-client.js:44:23:44:30 | req.body | rds-client.js:53:14:53:22 | userQuery | This query string depends on a $@. | rds-client.js:44:23:44:30 | req.body | user-provided value | +| rds-client.js:61:50:61:52 | sql | rds-client.js:45:25:45:32 | req.body | rds-client.js:61:50:61:52 | sql | This query string depends on a $@. | rds-client.js:45:25:45:32 | req.body | user-provided value | | redis.js:10:16:10:27 | req.body.key | redis.js:10:16:10:23 | req.body | redis.js:10:16:10:27 | req.body.key | This query object depends on a $@. | redis.js:10:16:10:23 | req.body | user-provided value | | redis.js:18:16:18:18 | key | redis.js:12:15:12:22 | req.body | redis.js:18:16:18:18 | key | This query object depends on a $@. | redis.js:12:15:12:22 | req.body | user-provided value | | redis.js:19:43:19:45 | key | redis.js:12:15:12:22 | req.body | redis.js:19:43:19:45 | key | This query object depends on a $@. | redis.js:12:15:12:22 | req.body | user-provided value | @@ -563,6 +567,23 @@ edges | pg-promise.js:22:11:22:15 | query | pg-promise.js:60:20:60:24 | query | provenance | | | pg-promise.js:22:11:22:15 | query | pg-promise.js:63:23:63:27 | query | provenance | | | pg-promise.js:22:11:22:15 | query | pg-promise.js:64:16:64:20 | query | provenance | | +| rds-client.js:8:11:8:36 | userQuery | rds-client.js:17:14:17:22 | userQuery | provenance | | +| rds-client.js:8:11:8:36 | userQuery | rds-client.js:33:24:33:32 | userQuery | provenance | | +| rds-client.js:8:23:8:30 | req.body | rds-client.js:8:11:8:36 | userQuery | provenance | | +| rds-client.js:13:11:18:5 | params1 [sql] | rds-client.js:19:51:19:57 | params1 [sql] | provenance | | +| rds-client.js:13:21:18:5 | {\\n ... y\\n } [sql] | rds-client.js:13:11:18:5 | params1 [sql] | provenance | | +| rds-client.js:17:14:17:22 | userQuery | rds-client.js:13:21:18:5 | {\\n ... y\\n } [sql] | provenance | | +| rds-client.js:19:51:19:57 | params1 [sql] | rds-client.js:19:23:19:58 | new Exe ... arams1) | provenance | | +| rds-client.js:29:11:34:5 | params [sqlStatements] | rds-client.js:36:45:36:50 | params [sqlStatements] | provenance | | +| rds-client.js:29:20:34:5 | {\\n ... y\\n } [sqlStatements] | rds-client.js:29:11:34:5 | params [sqlStatements] | provenance | | +| rds-client.js:33:24:33:32 | userQuery | rds-client.js:29:20:34:5 | {\\n ... y\\n } [sqlStatements] | provenance | | +| rds-client.js:36:45:36:50 | params [sqlStatements] | rds-client.js:36:23:36:51 | new Exe ... params) | provenance | | +| rds-client.js:44:11:44:36 | userQuery | rds-client.js:53:14:53:22 | userQuery | provenance | | +| rds-client.js:44:23:44:30 | req.body | rds-client.js:44:11:44:36 | userQuery | provenance | | +| rds-client.js:45:11:45:40 | userQueries | rds-client.js:61:24:61:34 | userQueries | provenance | | +| rds-client.js:45:25:45:32 | req.body | rds-client.js:45:11:45:40 | userQueries | provenance | | +| rds-client.js:61:24:61:34 | userQueries | rds-client.js:61:40:61:42 | sql | provenance | | +| rds-client.js:61:40:61:42 | sql | rds-client.js:61:50:61:52 | sql | provenance | | | redis.js:10:16:10:23 | req.body | redis.js:10:16:10:27 | req.body.key | provenance | Config | | redis.js:12:9:12:11 | key | redis.js:13:16:13:18 | key | provenance | | | redis.js:12:9:12:11 | key | redis.js:18:16:18:18 | key | provenance | | @@ -940,6 +961,26 @@ nodes | pg-promise.js:60:20:60:24 | query | semmle.label | query | | pg-promise.js:63:23:63:27 | query | semmle.label | query | | pg-promise.js:64:16:64:20 | query | semmle.label | query | +| rds-client.js:8:11:8:36 | userQuery | semmle.label | userQuery | +| rds-client.js:8:23:8:30 | req.body | semmle.label | req.body | +| rds-client.js:13:11:18:5 | params1 [sql] | semmle.label | params1 [sql] | +| rds-client.js:13:21:18:5 | {\\n ... y\\n } [sql] | semmle.label | {\\n ... y\\n } [sql] | +| rds-client.js:17:14:17:22 | userQuery | semmle.label | userQuery | +| rds-client.js:19:23:19:58 | new Exe ... arams1) | semmle.label | new Exe ... arams1) | +| rds-client.js:19:51:19:57 | params1 [sql] | semmle.label | params1 [sql] | +| rds-client.js:29:11:34:5 | params [sqlStatements] | semmle.label | params [sqlStatements] | +| rds-client.js:29:20:34:5 | {\\n ... y\\n } [sqlStatements] | semmle.label | {\\n ... y\\n } [sqlStatements] | +| rds-client.js:33:24:33:32 | userQuery | semmle.label | userQuery | +| rds-client.js:36:23:36:51 | new Exe ... params) | semmle.label | new Exe ... params) | +| rds-client.js:36:45:36:50 | params [sqlStatements] | semmle.label | params [sqlStatements] | +| rds-client.js:44:11:44:36 | userQuery | semmle.label | userQuery | +| rds-client.js:44:23:44:30 | req.body | semmle.label | req.body | +| rds-client.js:45:11:45:40 | userQueries | semmle.label | userQueries | +| rds-client.js:45:25:45:32 | req.body | semmle.label | req.body | +| rds-client.js:53:14:53:22 | userQuery | semmle.label | userQuery | +| rds-client.js:61:24:61:34 | userQueries | semmle.label | userQueries | +| rds-client.js:61:40:61:42 | sql | semmle.label | sql | +| rds-client.js:61:50:61:52 | sql | semmle.label | sql | | redis.js:10:16:10:23 | req.body | semmle.label | req.body | | redis.js:10:16:10:27 | req.body.key | semmle.label | req.body.key | | redis.js:12:9:12:11 | key | semmle.label | key | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/rds-client.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/rds-client.js index 6899ed0338f3..35d41e145f98 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/rds-client.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/rds-client.js @@ -5,7 +5,7 @@ const app = express(); app.use(bodyParser.json()); app.post('/v3/rds/all', async (req, res) => { - const userQuery = req.body.query; // $ MISSING: Source + const userQuery = req.body.query; // $ Source const userQueries = req.body.queries; // $ MISSING: Source const client = new RDSDataClient({ region: "us-east-1" }); @@ -16,7 +16,7 @@ app.post('/v3/rds/all', async (req, res) => { database: "userDatabase", sql: userQuery }; - await client.send(new ExecuteStatementCommand(params1)); // $ MISSING: Alert + await client.send(new ExecuteStatementCommand(params1)); // $ Alert const params2 = { resourceArn: "arn:aws:rds:us-east-1:123456789012:cluster:my-aurora-cluster", @@ -33,7 +33,7 @@ app.post('/v3/rds/all', async (req, res) => { sqlStatements: userQuery }; - await client.send(new ExecuteSqlCommand(params)); // $ MISSING: Alert + await client.send(new ExecuteSqlCommand(params)); // $ Alert res.end(); }); @@ -41,8 +41,8 @@ app.post('/v3/rds/all', async (req, res) => { const AWS = require('aws-sdk'); app.post('/v2/rds/all', async (req, res) => { - const userQuery = req.body.query; // $ MISSING: Source - const userQueries = req.body.queries; // $ MISSING: Source + const userQuery = req.body.query; // $ Source + const userQueries = req.body.queries; // $ Source const rdsData = new AWS.RDSDataService({ region: "us-east-1" }); @@ -50,7 +50,7 @@ app.post('/v2/rds/all', async (req, res) => { resourceArn: "arn:aws:rds:us-east-1:123456789012:cluster:my-aurora-cluster", secretArn: "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret", database: "userDatabase", - sql: userQuery // $ MISSING: Alert + sql: userQuery // $ Alert }; await rdsData.executeStatement(params1).promise(); @@ -58,7 +58,7 @@ app.post('/v2/rds/all', async (req, res) => { resourceArn: "arn:aws:rds:us-east-1:123456789012:cluster:my-aurora-cluster", secretArn: "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret", database: "userDatabase", - parameterSets: userQueries.map(sql => ({ sql })) // $ MISSING: Alert + parameterSets: userQueries.map(sql => ({ sql })) // $ Alert }; await rdsData.batchExecuteStatement(params2).promise(); From 93d9ae73b71b7530c99a8c3d723b257ca08c5b24 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 14:56:28 +0200 Subject: [PATCH 11/20] Updated change note --- javascript/ql/lib/change-notes/2025-07-28-dynamodb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md b/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md index 8cef0a4ec0f6..6d247a4fa5cb 100644 --- a/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md +++ b/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* Added support for the `aws-sdk` and `@aws-sdk/client-dynamodb` packages for DynamoDB PartiQL operations. +* Added support for the `aws-sdk` and `@aws-sdk/client-dynamodb`, `@aws-sdk/client-athena`, `@aws-sdk/client-s3`, and `@aws-sdk/client-rds-data` packages. This enables detection of SQL injection vulnerabilities in DynamoDB PartiQL operations, Athena queries, S3 select expressions, and RDS Data API calls. From 5b31350e8321e8c273383deb6a41a6080c6e609b Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 16:28:35 +0200 Subject: [PATCH 12/20] Added tests and modeling of database-access-result --- .../lib/change-notes/2025-07-28-dynamodb.md | 2 +- javascript/ql/lib/ext/athena.model.yml | 8 ++ javascript/ql/lib/ext/client-s3.model.yml | 8 ++ javascript/ql/lib/ext/dynamodb.model.yml | 8 ++ javascript/ql/lib/ext/rds-client.model.yml | 8 ++ .../XssWithAdditionalSources.expected | 56 +++++++++++++ .../Security/CWE-079/DomBasedXss/aws-db.js | 79 +++++++++++++++++++ 7 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/aws-db.js diff --git a/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md b/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md index 6d247a4fa5cb..bbf5d57163ae 100644 --- a/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md +++ b/javascript/ql/lib/change-notes/2025-07-28-dynamodb.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* Added support for the `aws-sdk` and `@aws-sdk/client-dynamodb`, `@aws-sdk/client-athena`, `@aws-sdk/client-s3`, and `@aws-sdk/client-rds-data` packages. This enables detection of SQL injection vulnerabilities in DynamoDB PartiQL operations, Athena queries, S3 select expressions, and RDS Data API calls. +* Added support for the `aws-sdk` and `@aws-sdk/client-dynamodb`, `@aws-sdk/client-athena`, `@aws-sdk/client-s3`, and `@aws-sdk/client-rds-data` packages. diff --git a/javascript/ql/lib/ext/athena.model.yml b/javascript/ql/lib/ext/athena.model.yml index 5101d7047e3e..225d0926988a 100644 --- a/javascript/ql/lib/ext/athena.model.yml +++ b/javascript/ql/lib/ext/athena.model.yml @@ -19,3 +19,11 @@ extensions: data: - ["AthenaClientV3", "@aws-sdk/client-athena", "Member[AthenaClient]"] - ["AthenaClientV2", "aws-sdk", "Member[Athena]"] + + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ["AthenaClientV3", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] + - ["AthenaClientV2", "ReturnValue.Member[getQueryResults].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["AthenaClientV2", "ReturnValue.Member[getQueryResults].Argument[1].Parameter[1]", "database-access-result"] diff --git a/javascript/ql/lib/ext/client-s3.model.yml b/javascript/ql/lib/ext/client-s3.model.yml index 2d81b3040e97..4deb2d6b7054 100644 --- a/javascript/ql/lib/ext/client-s3.model.yml +++ b/javascript/ql/lib/ext/client-s3.model.yml @@ -18,3 +18,11 @@ extensions: data: - ["S3ClientV3", "@aws-sdk/client-s3", "Member[S3Client]"] - ["S3ClientV2", "aws-sdk", "Member[S3]"] + + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ["S3ClientV3", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] + - ["S3ClientV2", "ReturnValue.Member[getObject].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["S3ClientV2", "ReturnValue.Member[getObject].Argument[1].Parameter[1]", "database-access-result"] diff --git a/javascript/ql/lib/ext/dynamodb.model.yml b/javascript/ql/lib/ext/dynamodb.model.yml index ddbf6cc3eee1..0efba4d4bc45 100644 --- a/javascript/ql/lib/ext/dynamodb.model.yml +++ b/javascript/ql/lib/ext/dynamodb.model.yml @@ -20,3 +20,11 @@ extensions: data: - ["DynamoDBClientV3", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] - ["DynamoDBClientV2", "aws-sdk", "Member[DynamoDB]"] + + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ["DynamoDBClientV3", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] + - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[1].Parameter[1]", "database-access-result"] diff --git a/javascript/ql/lib/ext/rds-client.model.yml b/javascript/ql/lib/ext/rds-client.model.yml index 7f10fc92c9f1..2535c88a02c8 100644 --- a/javascript/ql/lib/ext/rds-client.model.yml +++ b/javascript/ql/lib/ext/rds-client.model.yml @@ -21,3 +21,11 @@ extensions: data: - ["RDSDataClientV3", "@aws-sdk/client-rds-data", "Member[RDSDataClient]"] - ["RDSDataClientV2", "aws-sdk", "Member[RDSDataService]"] + + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ["RDSDataClientV3", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] + - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[1].Parameter[1]", "database-access-result"] 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 4f27cd94835c..3393efc34499 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 @@ -32,6 +32,34 @@ nodes | angular-tempate-url.js:13:30:13:31 | ev | semmle.label | ev | | angular-tempate-url.js:14:26:14:27 | ev | semmle.label | ev | | angular-tempate-url.js:14:26:14:32 | ev.data | semmle.label | ev.data | +| aws-db.js:15:31:15:37 | results | semmle.label | results | +| aws-db.js:15:31:15:76 | results ... arValue | semmle.label | results ... arValue | +| aws-db.js:20:31:20:38 | response | semmle.label | response | +| aws-db.js:20:31:20:54 | respons ... tring() | semmle.label | respons ... tring() | +| aws-db.js:24:31:24:39 | response2 | semmle.label | response2 | +| aws-db.js:24:31:24:47 | response2.records | semmle.label | response2.records | +| aws-db.js:28:31:28:39 | response3 | semmle.label | response3 | +| aws-db.js:28:31:28:44 | response3.Item | semmle.label | response3.Item | +| aws-db.js:43:31:43:37 | results | semmle.label | results | +| aws-db.js:43:31:43:76 | results ... arValue | semmle.label | results ... arValue | +| aws-db.js:46:35:46:38 | data | semmle.label | data | +| aws-db.js:46:35:46:77 | data.Re ... arValue | semmle.label | data.Re ... arValue | +| aws-db.js:51:31:51:38 | response | semmle.label | response | +| aws-db.js:51:31:51:54 | respons ... tring() | semmle.label | respons ... tring() | +| aws-db.js:54:35:54:38 | data | semmle.label | data | +| aws-db.js:54:35:54:54 | data.Body.toString() | semmle.label | data.Body.toString() | +| aws-db.js:59:31:59:39 | response1 | semmle.label | response1 | +| aws-db.js:59:31:59:47 | response1.records | semmle.label | response1.records | +| aws-db.js:62:35:62:38 | data | semmle.label | data | +| aws-db.js:62:35:62:46 | data.records | semmle.label | data.records | +| aws-db.js:66:31:66:39 | response2 | semmle.label | response2 | +| aws-db.js:66:31:66:53 | respons ... Results | semmle.label | respons ... Results | +| aws-db.js:69:35:69:38 | data | semmle.label | data | +| aws-db.js:69:35:69:52 | data.updateResults | semmle.label | data.updateResults | +| aws-db.js:74:35:74:38 | data | semmle.label | data | +| aws-db.js:74:35:74:43 | data.Item | semmle.label | data.Item | +| aws-db.js:77:35:77:38 | data | semmle.label | data | +| aws-db.js:77:35:77:43 | data.Item | semmle.label | data.Item | | classnames.js:7:31:7:84 | `` | semmle.label | `` | | classnames.js:7:47:7:69 | classNa ... w.name) | semmle.label | classNa ... w.name) | | classnames.js:7:58:7:68 | window.name | semmle.label | window.name | @@ -724,6 +752,20 @@ edges | angular-tempate-url.js:13:30:13:31 | ev | angular-tempate-url.js:14:26:14:27 | ev | provenance | | | angular-tempate-url.js:14:26:14:27 | ev | angular-tempate-url.js:14:26:14:32 | ev.data | provenance | | | angular-tempate-url.js:14:26:14:32 | ev.data | angular-tempate-url.js:9:26:9:45 | Cookie.get("unsafe") | provenance | | +| aws-db.js:15:31:15:37 | results | aws-db.js:15:31:15:76 | results ... arValue | provenance | | +| aws-db.js:20:31:20:38 | response | aws-db.js:20:31:20:54 | respons ... tring() | provenance | | +| aws-db.js:24:31:24:39 | response2 | aws-db.js:24:31:24:47 | response2.records | provenance | | +| aws-db.js:28:31:28:39 | response3 | aws-db.js:28:31:28:44 | response3.Item | provenance | | +| aws-db.js:43:31:43:37 | results | aws-db.js:43:31:43:76 | results ... arValue | provenance | | +| aws-db.js:46:35:46:38 | data | aws-db.js:46:35:46:77 | data.Re ... arValue | provenance | | +| aws-db.js:51:31:51:38 | response | aws-db.js:51:31:51:54 | respons ... tring() | provenance | | +| aws-db.js:54:35:54:38 | data | aws-db.js:54:35:54:54 | data.Body.toString() | provenance | | +| aws-db.js:59:31:59:39 | response1 | aws-db.js:59:31:59:47 | response1.records | provenance | | +| aws-db.js:62:35:62:38 | data | aws-db.js:62:35:62:46 | data.records | provenance | | +| aws-db.js:66:31:66:39 | response2 | aws-db.js:66:31:66:53 | respons ... Results | provenance | | +| aws-db.js:69:35:69:38 | data | aws-db.js:69:35:69:52 | data.updateResults | provenance | | +| aws-db.js:74:35:74:38 | data | aws-db.js:74:35:74:43 | data.Item | provenance | | +| aws-db.js:77:35:77:38 | data | aws-db.js:77:35:77:43 | data.Item | provenance | | | classnames.js:7:47:7:69 | classNa ... w.name) | classnames.js:7:31:7:84 | `` | provenance | | | classnames.js:7:58:7:68 | window.name | classnames.js:7:47:7:69 | classNa ... w.name) | provenance | | | classnames.js:8:47:8:70 | classNa ... w.name) | classnames.js:8:31:8:85 | `` | provenance | | @@ -1319,6 +1361,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 +| aws-db.js:15:31:15:76 | results ... arValue | aws-db.js:15:31:15:37 | results | aws-db.js:15:31:15:76 | results ... arValue | Cross-site scripting vulnerability due to $@. | aws-db.js:15:31:15:37 | results | user-provided value | +| aws-db.js:20:31:20:54 | respons ... tring() | aws-db.js:20:31:20:38 | response | aws-db.js:20:31:20:54 | respons ... tring() | Cross-site scripting vulnerability due to $@. | aws-db.js:20:31:20:38 | response | user-provided value | +| aws-db.js:24:31:24:47 | response2.records | aws-db.js:24:31:24:39 | response2 | aws-db.js:24:31:24:47 | response2.records | Cross-site scripting vulnerability due to $@. | aws-db.js:24:31:24:39 | response2 | user-provided value | +| aws-db.js:28:31:28:44 | response3.Item | aws-db.js:28:31:28:39 | response3 | aws-db.js:28:31:28:44 | response3.Item | Cross-site scripting vulnerability due to $@. | aws-db.js:28:31:28:39 | response3 | user-provided value | +| aws-db.js:43:31:43:76 | results ... arValue | aws-db.js:43:31:43:37 | results | aws-db.js:43:31:43:76 | results ... arValue | Cross-site scripting vulnerability due to $@. | aws-db.js:43:31:43:37 | results | user-provided value | +| aws-db.js:46:35:46:77 | data.Re ... arValue | aws-db.js:46:35:46:38 | data | aws-db.js:46:35:46:77 | data.Re ... arValue | Cross-site scripting vulnerability due to $@. | aws-db.js:46:35:46:38 | data | user-provided value | +| aws-db.js:51:31:51:54 | respons ... tring() | aws-db.js:51:31:51:38 | response | aws-db.js:51:31:51:54 | respons ... tring() | Cross-site scripting vulnerability due to $@. | aws-db.js:51:31:51:38 | response | user-provided value | +| aws-db.js:54:35:54:54 | data.Body.toString() | aws-db.js:54:35:54:38 | data | aws-db.js:54:35:54:54 | data.Body.toString() | Cross-site scripting vulnerability due to $@. | aws-db.js:54:35:54:38 | data | user-provided value | +| aws-db.js:59:31:59:47 | response1.records | aws-db.js:59:31:59:39 | response1 | aws-db.js:59:31:59:47 | response1.records | Cross-site scripting vulnerability due to $@. | aws-db.js:59:31:59:39 | response1 | user-provided value | +| aws-db.js:62:35:62:46 | data.records | aws-db.js:62:35:62:38 | data | aws-db.js:62:35:62:46 | data.records | Cross-site scripting vulnerability due to $@. | aws-db.js:62:35:62:38 | data | user-provided value | +| aws-db.js:66:31:66:53 | respons ... Results | aws-db.js:66:31:66:39 | response2 | aws-db.js:66:31:66:53 | respons ... Results | Cross-site scripting vulnerability due to $@. | aws-db.js:66:31:66:39 | response2 | user-provided value | +| aws-db.js:69:35:69:52 | data.updateResults | aws-db.js:69:35:69:38 | data | aws-db.js:69:35:69:52 | data.updateResults | Cross-site scripting vulnerability due to $@. | aws-db.js:69:35:69:38 | data | user-provided value | +| aws-db.js:74:35:74:43 | data.Item | aws-db.js:74:35:74:38 | data | aws-db.js:74:35:74:43 | data.Item | Cross-site scripting vulnerability due to $@. | aws-db.js:74:35:74:38 | data | user-provided value | +| aws-db.js:77:35:77:43 | data.Item | aws-db.js:77:35:77:38 | data | aws-db.js:77:35:77:43 | data.Item | Cross-site scripting vulnerability due to $@. | aws-db.js:77:35:77:38 | data | user-provided value | | 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 | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/aws-db.js b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/aws-db.js new file mode 100644 index 000000000000..0f345f7172e0 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/aws-db.js @@ -0,0 +1,79 @@ +const { AthenaClient, GetQueryResultsCommand } = require('@aws-sdk/client-athena'); +const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3"); +const { RDSDataClient, ExecuteStatementCommand } = require("@aws-sdk/client-rds-data"); +const { DynamoDBClient, GetItemCommand } = require("@aws-sdk/client-dynamodb"); + + +const express = require('express'); +const bodyParser = require('body-parser'); +const app = express(); +app.use(bodyParser.json()); + +app.post('/v3/all', async (req, res) => { + const client = new AthenaClient({ region: "us-east-1" }); + const results = await client.send(new GetQueryResultsCommand({ QueryExecutionId })); + document.body.innerHTML = results.ResultSet.Rows[0].Data[0].VarCharValue; // $ Alert[js/xss-additional-sources-dom-test] + + const s3 = new S3Client({ region: "us-east-1" }); + const command = new GetObjectCommand({ Bucket: bucket, Key: key }); + const response = await s3.send(command); + document.body.innerHTML = response.Body.toString(); // $ Alert[js/xss-additional-sources-dom-test] + + const clientRDS = new RDSDataClient({ region: "us-east-1" }); + const response2 = await clientRDS.send(new ExecuteStatementCommand(command)); + document.body.innerHTML = response2.records; // $ Alert[js/xss-additional-sources-dom-test] + + const clientDyamo = new DynamoDBClient({ region: "us-east-1" }); + const response3 = await clientDyamo.send(new GetItemCommand(command)); + document.body.innerHTML = response3.Item; // $ Alert[js/xss-additional-sources-dom-test] + +}); + + +app.post('/v2/all', async (req, res) => { + const AWS = require('aws-sdk'); + const athena = new AWS.Athena(); + const params = { + QueryString: 'SELECT * FROM my_table', + ResultConfiguration: { OutputLocation: 's3://bucket/prefix/' } + }; + const { QueryExecutionId } = await athena.startQueryExecution(params).promise(); + + const results = await athena.getQueryResults({ QueryExecutionId }).promise(); + document.body.innerHTML = results.ResultSet.Rows[0].Data[0].VarCharValue; // $ Alert[js/xss-additional-sources-dom-test] + + athena.getQueryResults({ QueryExecutionId }, (err, data) => { + document.body.innerHTML = data.ResultSet.Rows[0].Data[0].VarCharValue; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const s3 = new AWS.S3({ region: "us-east-1" }); + const response = await s3.getObject({ Bucket: "bucket", Key: "key" }).promise(); + document.body.innerHTML = response.Body.toString(); // $ Alert[js/xss-additional-sources-dom-test] + + s3.getObject({ Bucket: "bucket", Key: "key" }, (err, data) => { + document.body.innerHTML = data.Body.toString(); // $ Alert[js/xss-additional-sources-dom-test] + }); + + const rdsData = new AWS.RDSDataService({ region: "us-east-1" }); + const response1 = await rdsData.executeStatement(params).promise(); + document.body.innerHTML = response1.records; // $ Alert[js/xss-additional-sources-dom-test] + + rdsData.executeStatement(params, function(err, data) { + document.body.innerHTML = data.records; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const response2 = await rdsData.batchExecuteStatement(params).promise(); + document.body.innerHTML = response2.updateResults; // $ Alert[js/xss-additional-sources-dom-test] + + rdsData.batchExecuteStatement(params, function(err, data) { + document.body.innerHTML = data.updateResults; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const dynamodb = new AWS.DynamoDB({ region: "us-east-1" }); + dynamodb.executeStatement(params, (err, data) => { + document.body.innerHTML = data.Item; // $ Alert[js/xss-additional-sources-dom-test] + }); + dynamodb.executeStatement(params).promise().then(data => { + document.body.innerHTML = data.Item; // $ Alert[js/xss-additional-sources-dom-test] + }); +}); From 9beac51586c1f8253b969d0be75905fa2fde19ef Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 29 Jul 2025 16:50:42 +0200 Subject: [PATCH 13/20] Unified aws-db modeling into singular file --- javascript/ql/lib/ext/athena.model.yml | 29 ------------- javascript/ql/lib/ext/aws-sdk.model.yml | 49 ++++++++++++++++++++-- javascript/ql/lib/ext/client-s3.model.yml | 28 ------------- javascript/ql/lib/ext/dynamodb.model.yml | 30 ------------- javascript/ql/lib/ext/rds-client.model.yml | 31 -------------- 5 files changed, 46 insertions(+), 121 deletions(-) delete mode 100644 javascript/ql/lib/ext/athena.model.yml delete mode 100644 javascript/ql/lib/ext/client-s3.model.yml delete mode 100644 javascript/ql/lib/ext/dynamodb.model.yml delete mode 100644 javascript/ql/lib/ext/rds-client.model.yml diff --git a/javascript/ql/lib/ext/athena.model.yml b/javascript/ql/lib/ext/athena.model.yml deleted file mode 100644 index 225d0926988a..000000000000 --- a/javascript/ql/lib/ext/athena.model.yml +++ /dev/null @@ -1,29 +0,0 @@ -extensions: - - addsTo: - pack: codeql/javascript-all - extensible: sinkModel - data: - - ["AthenaClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] - - ["AthenaClientV2", "ReturnValue.Member[startQueryExecution,createNamedQuery,updateNamedQuery].Argument[0].Member[QueryString]", "sql-injection"] - - - - addsTo: - pack: codeql/javascript-all - extensible: summaryModel - data: - - ["@aws-sdk/client-athena", "Member[StartQueryExecutionCommand,CreateNamedQueryCommand,UpdateNamedQueryCommand]", "Argument[0].Member[QueryString]", "ReturnValue", "taint"] - - - addsTo: - pack: codeql/javascript-all - extensible: typeModel - data: - - ["AthenaClientV3", "@aws-sdk/client-athena", "Member[AthenaClient]"] - - ["AthenaClientV2", "aws-sdk", "Member[Athena]"] - - - addsTo: - pack: codeql/javascript-all - extensible: sourceModel - data: - - ["AthenaClientV3", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] - - ["AthenaClientV2", "ReturnValue.Member[getQueryResults].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - - ["AthenaClientV2", "ReturnValue.Member[getQueryResults].Argument[1].Parameter[1]", "database-access-result"] diff --git a/javascript/ql/lib/ext/aws-sdk.model.yml b/javascript/ql/lib/ext/aws-sdk.model.yml index eefa87fbe613..d850fec7371c 100644 --- a/javascript/ql/lib/ext/aws-sdk.model.yml +++ b/javascript/ql/lib/ext/aws-sdk.model.yml @@ -3,6 +3,49 @@ extensions: pack: codeql/javascript-all extensible: sinkModel data: - - ["aws-sdk", "AnyMember.Argument[0].Member[secretAccessKey,accessKeyId]", "credentials-key"] - - ["aws-sdk", "AnyMember.Member[secretAccessKey,accessKeyId]", "credentials-key"] - - ["aws-sdk", "Member[Credentials].Argument[0,1]", "credentials-key"] + - ["aws-sdk", "AnyMember.Argument[0].Member[secretAccessKey,accessKeyId]", "credentials-key"] + - ["aws-sdk", "AnyMember.Member[secretAccessKey,accessKeyId]", "credentials-key"] + - ["aws-sdk", "Member[Credentials].Argument[0,1]", "credentials-key"] + - ["AWS-V3-Common", "ReturnValue.Member[send].Argument[0]", "sql-injection"] + - ["AthenaClientV2", "ReturnValue.Member[startQueryExecution,createNamedQuery,updateNamedQuery].Argument[0].Member[QueryString]", "sql-injection"] + - ["S3ClientV2", "ReturnValue.Member[selectObjectContent].Argument[0].Member[Expression]", "sql-injection"] + - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[0].Member[sql]", "sql-injection"] + - ["RDSDataClientV2", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "sql-injection"] + - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement].Argument[0].Member[Statement]", "sql-injection"] + - ["DynamoDBClientV2", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[Statements].ArrayElement.Member[Statement]", "sql-injection"] + - addsTo: + pack: codeql/javascript-all + extensible: summaryModel + data: + - ["@aws-sdk/client-athena", "Member[StartQueryExecutionCommand,CreateNamedQueryCommand,UpdateNamedQueryCommand]", "Argument[0].Member[QueryString]", "ReturnValue", "taint"] + - ["@aws-sdk/client-s3", "Member[SelectObjectContentCommand]", "Argument[0].Member[Expression]", "ReturnValue", "taint"] + - ["@aws-sdk/client-rds-data", "Member[ExecuteStatementCommand,BatchExecuteStatementCommand]", "Argument[0].Member[sql]", "ReturnValue", "taint"] + - ["@aws-sdk/client-rds-data", "Member[BatchExecuteStatementCommand]", "Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "ReturnValue", "taint"] + - ["@aws-sdk/client-rds-data", "Member[ExecuteSqlCommand]", "Argument[0].Member[sqlStatements]", "ReturnValue", "taint"] + - ["@aws-sdk/client-dynamodb", "Member[ExecuteStatementCommand]", "Argument[0].Member[Statement]", "ReturnValue", "taint"] + - ["@aws-sdk/client-dynamodb", "Member[BatchExecuteStatementCommand]", "Argument[0].Member[Statements].ArrayElement.Member[Statement]", "ReturnValue", "taint"] + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["AthenaClientV2", "aws-sdk", "Member[Athena]"] + - ["S3ClientV2", "aws-sdk", "Member[S3]"] + - ["RDSDataClientV2", "aws-sdk", "Member[RDSDataService]"] + - ["DynamoDBClientV2", "aws-sdk", "Member[DynamoDB]"] + - ["AWS-V3-Common", "@aws-sdk/client-athena", "Member[AthenaClient]"] + - ["AWS-V3-Common", "@aws-sdk/client-s3", "Member[S3Client]"] + - ["AWS-V3-Common", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] + - ["AWS-V3-Common", "@aws-sdk/client-rds-data", "Member[RDSDataClient]"] + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ["AWS-V3-Common", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] + - ["AthenaClientV2", "ReturnValue.Member[getQueryResults].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["AthenaClientV2", "ReturnValue.Member[getQueryResults].Argument[1].Parameter[1]", "database-access-result"] + - ["S3ClientV2", "ReturnValue.Member[getObject].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["S3ClientV2", "ReturnValue.Member[getObject].Argument[1].Parameter[1]", "database-access-result"] + - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[1].Parameter[1]", "database-access-result"] + - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement,query,scan,getItem,batchGetItem].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement,query,scan,getItem,batchGetItem].Argument[1].Parameter[1]", "database-access-result"] diff --git a/javascript/ql/lib/ext/client-s3.model.yml b/javascript/ql/lib/ext/client-s3.model.yml deleted file mode 100644 index 4deb2d6b7054..000000000000 --- a/javascript/ql/lib/ext/client-s3.model.yml +++ /dev/null @@ -1,28 +0,0 @@ -extensions: - - addsTo: - pack: codeql/javascript-all - extensible: sinkModel - data: - - ["S3ClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] - - ["S3ClientV2", "ReturnValue.Member[selectObjectContent].Argument[0].Member[Expression]", "sql-injection"] - - - addsTo: - pack: codeql/javascript-all - extensible: summaryModel - data: - - ["@aws-sdk/client-s3", "Member[SelectObjectContentCommand]", "Argument[0].Member[Expression]", "ReturnValue", "taint"] - - - addsTo: - pack: codeql/javascript-all - extensible: typeModel - data: - - ["S3ClientV3", "@aws-sdk/client-s3", "Member[S3Client]"] - - ["S3ClientV2", "aws-sdk", "Member[S3]"] - - - addsTo: - pack: codeql/javascript-all - extensible: sourceModel - data: - - ["S3ClientV3", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] - - ["S3ClientV2", "ReturnValue.Member[getObject].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - - ["S3ClientV2", "ReturnValue.Member[getObject].Argument[1].Parameter[1]", "database-access-result"] diff --git a/javascript/ql/lib/ext/dynamodb.model.yml b/javascript/ql/lib/ext/dynamodb.model.yml deleted file mode 100644 index 0efba4d4bc45..000000000000 --- a/javascript/ql/lib/ext/dynamodb.model.yml +++ /dev/null @@ -1,30 +0,0 @@ -extensions: - - addsTo: - pack: codeql/javascript-all - extensible: sinkModel - data: - - ["DynamoDBClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] - - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement].Argument[0].Member[Statement]", "sql-injection"] - - ["DynamoDBClientV2", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[Statements].ArrayElement.Member[Statement]", "sql-injection"] - - - addsTo: - pack: codeql/javascript-all - extensible: summaryModel - data: - - ["@aws-sdk/client-dynamodb", "Member[ExecuteStatementCommand]", "Argument[0].Member[Statement]", "ReturnValue", "taint"] - - ["@aws-sdk/client-dynamodb", "Member[BatchExecuteStatementCommand]", "Argument[0].Member[Statements].ArrayElement.Member[Statement]", "ReturnValue", "taint"] - - - addsTo: - pack: codeql/javascript-all - extensible: typeModel - data: - - ["DynamoDBClientV3", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] - - ["DynamoDBClientV2", "aws-sdk", "Member[DynamoDB]"] - - - addsTo: - pack: codeql/javascript-all - extensible: sourceModel - data: - - ["DynamoDBClientV3", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] - - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[1].Parameter[1]", "database-access-result"] diff --git a/javascript/ql/lib/ext/rds-client.model.yml b/javascript/ql/lib/ext/rds-client.model.yml deleted file mode 100644 index 2535c88a02c8..000000000000 --- a/javascript/ql/lib/ext/rds-client.model.yml +++ /dev/null @@ -1,31 +0,0 @@ -extensions: - - addsTo: - pack: codeql/javascript-all - extensible: sinkModel - data: - - ["RDSDataClientV3", "ReturnValue.Member[send].Argument[0]", "sql-injection"] - - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[0].Member[sql]", "sql-injection"] - - ["RDSDataClientV2", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "sql-injection"] - - - addsTo: - pack: codeql/javascript-all - extensible: summaryModel - data: - - ["@aws-sdk/client-rds-data", "Member[ExecuteStatementCommand,BatchExecuteStatementCommand]", "Argument[0].Member[sql]", "ReturnValue", "taint"] - - ["@aws-sdk/client-rds-data", "Member[BatchExecuteStatementCommand]", "Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "ReturnValue", "taint"] - - ["@aws-sdk/client-rds-data", "Member[ExecuteSqlCommand]", "Argument[0].Member[sqlStatements]", "ReturnValue", "taint"] - - - addsTo: - pack: codeql/javascript-all - extensible: typeModel - data: - - ["RDSDataClientV3", "@aws-sdk/client-rds-data", "Member[RDSDataClient]"] - - ["RDSDataClientV2", "aws-sdk", "Member[RDSDataService]"] - - - addsTo: - pack: codeql/javascript-all - extensible: sourceModel - data: - - ["RDSDataClientV3", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] - - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[1].Parameter[1]", "database-access-result"] From 801a34f6a1d3a34cdc512502675fbd3716a6b019 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 4 Sep 2025 11:05:42 +0200 Subject: [PATCH 14/20] Moved typeModel at the start of the file --- javascript/ql/lib/ext/aws-sdk.model.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/javascript/ql/lib/ext/aws-sdk.model.yml b/javascript/ql/lib/ext/aws-sdk.model.yml index d850fec7371c..17164c18298d 100644 --- a/javascript/ql/lib/ext/aws-sdk.model.yml +++ b/javascript/ql/lib/ext/aws-sdk.model.yml @@ -1,4 +1,16 @@ extensions: + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - ["AthenaClientV2", "aws-sdk", "Member[Athena]"] + - ["S3ClientV2", "aws-sdk", "Member[S3]"] + - ["RDSDataClientV2", "aws-sdk", "Member[RDSDataService]"] + - ["DynamoDBClientV2", "aws-sdk", "Member[DynamoDB]"] + - ["AWS-V3-Common", "@aws-sdk/client-athena", "Member[AthenaClient]"] + - ["AWS-V3-Common", "@aws-sdk/client-s3", "Member[S3Client]"] + - ["AWS-V3-Common", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] + - ["AWS-V3-Common", "@aws-sdk/client-rds-data", "Member[RDSDataClient]"] - addsTo: pack: codeql/javascript-all extensible: sinkModel @@ -24,18 +36,6 @@ extensions: - ["@aws-sdk/client-rds-data", "Member[ExecuteSqlCommand]", "Argument[0].Member[sqlStatements]", "ReturnValue", "taint"] - ["@aws-sdk/client-dynamodb", "Member[ExecuteStatementCommand]", "Argument[0].Member[Statement]", "ReturnValue", "taint"] - ["@aws-sdk/client-dynamodb", "Member[BatchExecuteStatementCommand]", "Argument[0].Member[Statements].ArrayElement.Member[Statement]", "ReturnValue", "taint"] - - addsTo: - pack: codeql/javascript-all - extensible: typeModel - data: - - ["AthenaClientV2", "aws-sdk", "Member[Athena]"] - - ["S3ClientV2", "aws-sdk", "Member[S3]"] - - ["RDSDataClientV2", "aws-sdk", "Member[RDSDataService]"] - - ["DynamoDBClientV2", "aws-sdk", "Member[DynamoDB]"] - - ["AWS-V3-Common", "@aws-sdk/client-athena", "Member[AthenaClient]"] - - ["AWS-V3-Common", "@aws-sdk/client-s3", "Member[S3Client]"] - - ["AWS-V3-Common", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] - - ["AWS-V3-Common", "@aws-sdk/client-rds-data", "Member[RDSDataClient]"] - addsTo: pack: codeql/javascript-all extensible: sourceModel From b89e70b5a0e46ab4abe7f21ad653edb37422de36 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 4 Sep 2025 12:07:28 +0200 Subject: [PATCH 15/20] Added test cases for aws sources --- .../XssWithAdditionalSources.expected | 96 ++++++++++++++ .../Security/CWE-079/DomBasedXss/aws.js | 118 ++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/aws.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 3393efc34499..f8781e6741b9 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 @@ -60,6 +60,54 @@ nodes | aws-db.js:74:35:74:43 | data.Item | semmle.label | data.Item | | aws-db.js:77:35:77:38 | data | semmle.label | data | | aws-db.js:77:35:77:43 | data.Item | semmle.label | data.Item | +| aws.js:14:31:14:36 | result | semmle.label | result | +| aws.js:14:31:14:44 | result.comment | semmle.label | result.comment | +| aws.js:18:31:18:37 | result2 | semmle.label | result2 | +| aws.js:18:31:18:45 | result2.comment | semmle.label | result2.comment | +| aws.js:22:31:22:37 | result3 | semmle.label | result3 | +| aws.js:22:31:22:45 | result3.comment | semmle.label | result3.comment | +| aws.js:26:31:26:37 | result4 | semmle.label | result4 | +| aws.js:26:31:26:45 | result4.comment | semmle.label | result4.comment | +| aws.js:34:31:34:34 | data | semmle.label | data | +| aws.js:34:31:34:42 | data.comment | semmle.label | data.comment | +| aws.js:37:35:37:38 | data | semmle.label | data | +| aws.js:37:35:37:46 | data.comment | semmle.label | data.comment | +| aws.js:47:31:47:34 | data | semmle.label | data | +| aws.js:47:31:47:42 | data.comment | semmle.label | data.comment | +| aws.js:50:35:50:38 | data | semmle.label | data | +| aws.js:50:35:50:46 | data.comment | semmle.label | data.comment | +| aws.js:59:31:59:34 | data | semmle.label | data | +| aws.js:59:31:59:42 | data.comment | semmle.label | data.comment | +| aws.js:62:35:62:38 | data | semmle.label | data | +| aws.js:62:35:62:46 | data.comment | semmle.label | data.comment | +| aws.js:66:31:66:35 | data2 | semmle.label | data2 | +| aws.js:66:31:66:43 | data2.comment | semmle.label | data2.comment | +| aws.js:69:35:69:38 | data | semmle.label | data | +| aws.js:69:35:69:46 | data.comment | semmle.label | data.comment | +| aws.js:78:31:78:34 | data | semmle.label | data | +| aws.js:78:31:78:42 | data.comment | semmle.label | data.comment | +| aws.js:81:35:81:38 | data | semmle.label | data | +| aws.js:81:35:81:46 | data.comment | semmle.label | data.comment | +| aws.js:85:31:85:35 | data2 | semmle.label | data2 | +| aws.js:85:31:85:43 | data2.comment | semmle.label | data2.comment | +| aws.js:88:35:88:38 | data | semmle.label | data | +| aws.js:88:35:88:46 | data.comment | semmle.label | data.comment | +| aws.js:92:31:92:35 | data3 | semmle.label | data3 | +| aws.js:92:31:92:43 | data3.comment | semmle.label | data3.comment | +| aws.js:95:35:95:38 | data | semmle.label | data | +| aws.js:95:35:95:46 | data.comment | semmle.label | data.comment | +| aws.js:99:31:99:35 | data4 | semmle.label | data4 | +| aws.js:99:31:99:43 | data4.comment | semmle.label | data4.comment | +| aws.js:102:35:102:38 | data | semmle.label | data | +| aws.js:102:35:102:46 | data.comment | semmle.label | data.comment | +| aws.js:106:31:106:35 | data5 | semmle.label | data5 | +| aws.js:106:31:106:43 | data5.comment | semmle.label | data5.comment | +| aws.js:109:35:109:38 | data | semmle.label | data | +| aws.js:109:35:109:46 | data.comment | semmle.label | data.comment | +| aws.js:113:31:113:35 | data6 | semmle.label | data6 | +| aws.js:113:31:113:43 | data6.comment | semmle.label | data6.comment | +| aws.js:116:35:116:38 | data | semmle.label | data | +| aws.js:116:35:116:46 | data.comment | semmle.label | data.comment | | classnames.js:7:31:7:84 | `` | semmle.label | `` | | classnames.js:7:47:7:69 | classNa ... w.name) | semmle.label | classNa ... w.name) | | classnames.js:7:58:7:68 | window.name | semmle.label | window.name | @@ -766,6 +814,30 @@ edges | aws-db.js:69:35:69:38 | data | aws-db.js:69:35:69:52 | data.updateResults | provenance | | | aws-db.js:74:35:74:38 | data | aws-db.js:74:35:74:43 | data.Item | provenance | | | aws-db.js:77:35:77:38 | data | aws-db.js:77:35:77:43 | data.Item | provenance | | +| aws.js:14:31:14:36 | result | aws.js:14:31:14:44 | result.comment | provenance | | +| aws.js:18:31:18:37 | result2 | aws.js:18:31:18:45 | result2.comment | provenance | | +| aws.js:22:31:22:37 | result3 | aws.js:22:31:22:45 | result3.comment | provenance | | +| aws.js:26:31:26:37 | result4 | aws.js:26:31:26:45 | result4.comment | provenance | | +| aws.js:34:31:34:34 | data | aws.js:34:31:34:42 | data.comment | provenance | | +| aws.js:37:35:37:38 | data | aws.js:37:35:37:46 | data.comment | provenance | | +| aws.js:47:31:47:34 | data | aws.js:47:31:47:42 | data.comment | provenance | | +| aws.js:50:35:50:38 | data | aws.js:50:35:50:46 | data.comment | provenance | | +| aws.js:59:31:59:34 | data | aws.js:59:31:59:42 | data.comment | provenance | | +| aws.js:62:35:62:38 | data | aws.js:62:35:62:46 | data.comment | provenance | | +| aws.js:66:31:66:35 | data2 | aws.js:66:31:66:43 | data2.comment | provenance | | +| aws.js:69:35:69:38 | data | aws.js:69:35:69:46 | data.comment | provenance | | +| aws.js:78:31:78:34 | data | aws.js:78:31:78:42 | data.comment | provenance | | +| aws.js:81:35:81:38 | data | aws.js:81:35:81:46 | data.comment | provenance | | +| aws.js:85:31:85:35 | data2 | aws.js:85:31:85:43 | data2.comment | provenance | | +| aws.js:88:35:88:38 | data | aws.js:88:35:88:46 | data.comment | provenance | | +| aws.js:92:31:92:35 | data3 | aws.js:92:31:92:43 | data3.comment | provenance | | +| aws.js:95:35:95:38 | data | aws.js:95:35:95:46 | data.comment | provenance | | +| aws.js:99:31:99:35 | data4 | aws.js:99:31:99:43 | data4.comment | provenance | | +| aws.js:102:35:102:38 | data | aws.js:102:35:102:46 | data.comment | provenance | | +| aws.js:106:31:106:35 | data5 | aws.js:106:31:106:43 | data5.comment | provenance | | +| aws.js:109:35:109:38 | data | aws.js:109:35:109:46 | data.comment | provenance | | +| aws.js:113:31:113:35 | data6 | aws.js:113:31:113:43 | data6.comment | provenance | | +| aws.js:116:35:116:38 | data | aws.js:116:35:116:46 | data.comment | provenance | | | classnames.js:7:47:7:69 | classNa ... w.name) | classnames.js:7:31:7:84 | `` | provenance | | | classnames.js:7:58:7:68 | window.name | classnames.js:7:47:7:69 | classNa ... w.name) | provenance | | | classnames.js:8:47:8:70 | classNa ... w.name) | classnames.js:8:31:8:85 | `` | provenance | | @@ -1375,6 +1447,30 @@ subpaths | aws-db.js:69:35:69:52 | data.updateResults | aws-db.js:69:35:69:38 | data | aws-db.js:69:35:69:52 | data.updateResults | Cross-site scripting vulnerability due to $@. | aws-db.js:69:35:69:38 | data | user-provided value | | aws-db.js:74:35:74:43 | data.Item | aws-db.js:74:35:74:38 | data | aws-db.js:74:35:74:43 | data.Item | Cross-site scripting vulnerability due to $@. | aws-db.js:74:35:74:38 | data | user-provided value | | aws-db.js:77:35:77:43 | data.Item | aws-db.js:77:35:77:38 | data | aws-db.js:77:35:77:43 | data.Item | Cross-site scripting vulnerability due to $@. | aws-db.js:77:35:77:38 | data | user-provided value | +| aws.js:14:31:14:44 | result.comment | aws.js:14:31:14:36 | result | aws.js:14:31:14:44 | result.comment | Cross-site scripting vulnerability due to $@. | aws.js:14:31:14:36 | result | user-provided value | +| aws.js:18:31:18:45 | result2.comment | aws.js:18:31:18:37 | result2 | aws.js:18:31:18:45 | result2.comment | Cross-site scripting vulnerability due to $@. | aws.js:18:31:18:37 | result2 | user-provided value | +| aws.js:22:31:22:45 | result3.comment | aws.js:22:31:22:37 | result3 | aws.js:22:31:22:45 | result3.comment | Cross-site scripting vulnerability due to $@. | aws.js:22:31:22:37 | result3 | user-provided value | +| aws.js:26:31:26:45 | result4.comment | aws.js:26:31:26:37 | result4 | aws.js:26:31:26:45 | result4.comment | Cross-site scripting vulnerability due to $@. | aws.js:26:31:26:37 | result4 | user-provided value | +| aws.js:34:31:34:42 | data.comment | aws.js:34:31:34:34 | data | aws.js:34:31:34:42 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:34:31:34:34 | data | user-provided value | +| aws.js:37:35:37:46 | data.comment | aws.js:37:35:37:38 | data | aws.js:37:35:37:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:37:35:37:38 | data | user-provided value | +| aws.js:47:31:47:42 | data.comment | aws.js:47:31:47:34 | data | aws.js:47:31:47:42 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:47:31:47:34 | data | user-provided value | +| aws.js:50:35:50:46 | data.comment | aws.js:50:35:50:38 | data | aws.js:50:35:50:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:50:35:50:38 | data | user-provided value | +| aws.js:59:31:59:42 | data.comment | aws.js:59:31:59:34 | data | aws.js:59:31:59:42 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:59:31:59:34 | data | user-provided value | +| aws.js:62:35:62:46 | data.comment | aws.js:62:35:62:38 | data | aws.js:62:35:62:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:62:35:62:38 | data | user-provided value | +| aws.js:66:31:66:43 | data2.comment | aws.js:66:31:66:35 | data2 | aws.js:66:31:66:43 | data2.comment | Cross-site scripting vulnerability due to $@. | aws.js:66:31:66:35 | data2 | user-provided value | +| aws.js:69:35:69:46 | data.comment | aws.js:69:35:69:38 | data | aws.js:69:35:69:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:69:35:69:38 | data | user-provided value | +| aws.js:78:31:78:42 | data.comment | aws.js:78:31:78:34 | data | aws.js:78:31:78:42 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:78:31:78:34 | data | user-provided value | +| aws.js:81:35:81:46 | data.comment | aws.js:81:35:81:38 | data | aws.js:81:35:81:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:81:35:81:38 | data | user-provided value | +| aws.js:85:31:85:43 | data2.comment | aws.js:85:31:85:35 | data2 | aws.js:85:31:85:43 | data2.comment | Cross-site scripting vulnerability due to $@. | aws.js:85:31:85:35 | data2 | user-provided value | +| aws.js:88:35:88:46 | data.comment | aws.js:88:35:88:38 | data | aws.js:88:35:88:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:88:35:88:38 | data | user-provided value | +| aws.js:92:31:92:43 | data3.comment | aws.js:92:31:92:35 | data3 | aws.js:92:31:92:43 | data3.comment | Cross-site scripting vulnerability due to $@. | aws.js:92:31:92:35 | data3 | user-provided value | +| aws.js:95:35:95:46 | data.comment | aws.js:95:35:95:38 | data | aws.js:95:35:95:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:95:35:95:38 | data | user-provided value | +| aws.js:99:31:99:43 | data4.comment | aws.js:99:31:99:35 | data4 | aws.js:99:31:99:43 | data4.comment | Cross-site scripting vulnerability due to $@. | aws.js:99:31:99:35 | data4 | user-provided value | +| aws.js:102:35:102:46 | data.comment | aws.js:102:35:102:38 | data | aws.js:102:35:102:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:102:35:102:38 | data | user-provided value | +| aws.js:106:31:106:43 | data5.comment | aws.js:106:31:106:35 | data5 | aws.js:106:31:106:43 | data5.comment | Cross-site scripting vulnerability due to $@. | aws.js:106:31:106:35 | data5 | user-provided value | +| aws.js:109:35:109:46 | data.comment | aws.js:109:35:109:38 | data | aws.js:109:35:109:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:109:35:109:38 | data | user-provided value | +| aws.js:113:31:113:43 | data6.comment | aws.js:113:31:113:35 | data6 | aws.js:113:31:113:43 | data6.comment | Cross-site scripting vulnerability due to $@. | aws.js:113:31:113:35 | data6 | user-provided value | +| aws.js:116:35:116:46 | data.comment | aws.js:116:35:116:38 | data | aws.js:116:35:116:46 | data.comment | Cross-site scripting vulnerability due to $@. | aws.js:116:35:116:38 | data | user-provided value | | 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 | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/aws.js b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/aws.js new file mode 100644 index 000000000000..103689c078f2 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/aws.js @@ -0,0 +1,118 @@ +const AWS = require('aws-sdk'); +const { AthenaClient } = require('@aws-sdk/client-athena'); +const { S3Client } = require('@aws-sdk/client-s3'); +const { RDSDataClient } = require('@aws-sdk/client-rds-data'); +const { DynamoDBClient } = require('@aws-sdk/client-dynamodb'); +const express = require('express'); + +const app = express(); + +// AWS V3 Common tests +app.post('/aws-v3-common', async (req, res) => { + const athenaClient = new AthenaClient({}); + const result = await athenaClient.send({}); + document.body.innerHTML = result.comment; // $ Alert[js/xss-additional-sources-dom-test] + + const s3Client = new S3Client({}); + const result2 = await s3Client.send({}); + document.body.innerHTML = result2.comment; // $ Alert[js/xss-additional-sources-dom-test] + + const rdsDataClient = new RDSDataClient({}); + const result3 = await rdsDataClient.send({}); + document.body.innerHTML = result3.comment; // $ Alert[js/xss-additional-sources-dom-test] + + const dynamoClient = new DynamoDBClient({}); + const result4 = await dynamoClient.send({}); + document.body.innerHTML = result4.comment; // $ Alert[js/xss-additional-sources-dom-test] +}); + +// Athena Client V2 tests +app.post('/athena-v2', async (req, res) => { + const athena = new AWS.Athena(); + + const data = await athena.getQueryResults({}).promise(); + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + + athena.getQueryResults({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); +}); + +// S3 Client V2 tests +app.post('/s3-v2', async (req, res) => { + const s3 = new AWS.S3(); + + + const data = await s3.getObject({}).promise(); + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + + s3.getObject({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); +}); + +// RDS Data Client V2 tests +app.post('/rds-data-v2', async (req, res) => { + const rdsData = new AWS.RDSDataService(); + + const data = await rdsData.executeStatement({}).promise(); + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + + rdsData.executeStatement({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const data2 = await rdsData.batchExecuteStatement({}).promise(); + document.body.innerHTML = data2.comment; // $ Alert[js/xss-additional-sources-dom-test] + + rdsData.batchExecuteStatement({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); +}); + +// DynamoDB Client V2 tests +app.post('/dynamodb-v2', async (req, res) => { + const dynamodb = new AWS.DynamoDB(); + + const data = await dynamodb.executeStatement({}).promise(); + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + + dynamodb.executeStatement({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const data2 = await dynamodb.batchExecuteStatement({}).promise(); + document.body.innerHTML = data2.comment; // $ Alert[js/xss-additional-sources-dom-test] + + dynamodb.batchExecuteStatement({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const data3 = await dynamodb.query({}).promise(); + document.body.innerHTML = data3.comment; // $ Alert[js/xss-additional-sources-dom-test] + + dynamodb.query({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const data4 = await dynamodb.scan({}).promise(); + document.body.innerHTML = data4.comment; // $ Alert[js/xss-additional-sources-dom-test] + + dynamodb.scan({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const data5 = await dynamodb.getItem({}).promise(); + document.body.innerHTML = data5.comment; // $ Alert[js/xss-additional-sources-dom-test] + + dynamodb.getItem({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); + + const data6 = await dynamodb.batchGetItem({}).promise(); + document.body.innerHTML = data6.comment; // $ Alert[js/xss-additional-sources-dom-test] + + dynamodb.batchGetItem({}, function(err, data) { + document.body.innerHTML = data.comment; // $ Alert[js/xss-additional-sources-dom-test] + }); +}); From 872b6d8bee6cd3b456539f48fb889a96e08fe5cc Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 4 Sep 2025 12:28:55 +0200 Subject: [PATCH 16/20] Added test case for `CreatePreparedStatementCommand` --- .../Security/CWE-089/untyped/athena.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js index 2b661d5d4aea..0c6e5e1d8a74 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js @@ -1,4 +1,4 @@ -const { AthenaClient, StartQueryExecutionCommand, CreateNamedQueryCommand, UpdateNamedQueryCommand } = require("@aws-sdk/client-athena"); +const { AthenaClient, StartQueryExecutionCommand, CreateNamedQueryCommand, UpdateNamedQueryCommand, CreatePreparedStatementCommand } = require("@aws-sdk/client-athena"); const AWS = require('aws-sdk'); const express = require('express'); const bodyParser = require('body-parser'); @@ -10,7 +10,7 @@ app.post('/v3/athena/all', async (req, res) => { const client = new AthenaClient({ region: "us-east-1" }); - const params1 = { + const params1 = { QueryString: "SQL" + userQuery, QueryExecutionContext: { Database: "default" }, ResultConfiguration: { OutputLocation: "s3://my-results/" } @@ -70,3 +70,16 @@ app.post('/v2/athena/all', async (req, res) => { res.end(); }); + +app.post('/dynamodb-v3', async (req, res) => { + const userQueryStatement = req.body.query; // $ MISSING: Source + const client = new AthenaClient({ region: "us-east-1" }); + const input = { + StatementName: "STRING_VALUE", + WorkGroup: "STRING_VALUE", + QueryStatement: userQueryStatement, + Description: "STRING_VALUE", + }; + const command = new CreatePreparedStatementCommand(input); + await client.send(command); // $ MISSING: Alert +}); From 9ca4773227a2ade0f2148b47574869a87f6d6e65 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 4 Sep 2025 12:31:08 +0200 Subject: [PATCH 17/20] Added modeling for `CreatePreparedStatementCommand` --- javascript/ql/lib/ext/aws-sdk.model.yml | 1 + .../CWE-089/untyped/SqlInjection.expected | 24 ++++++++++++++++--- .../Security/CWE-089/untyped/athena.js | 4 ++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/javascript/ql/lib/ext/aws-sdk.model.yml b/javascript/ql/lib/ext/aws-sdk.model.yml index 17164c18298d..dfda93d2d3f8 100644 --- a/javascript/ql/lib/ext/aws-sdk.model.yml +++ b/javascript/ql/lib/ext/aws-sdk.model.yml @@ -30,6 +30,7 @@ extensions: extensible: summaryModel data: - ["@aws-sdk/client-athena", "Member[StartQueryExecutionCommand,CreateNamedQueryCommand,UpdateNamedQueryCommand]", "Argument[0].Member[QueryString]", "ReturnValue", "taint"] + - ["@aws-sdk/client-athena", "Member[CreatePreparedStatementCommand]", "Argument[0].Member[QueryStatement]", "ReturnValue", "taint"] - ["@aws-sdk/client-s3", "Member[SelectObjectContentCommand]", "Argument[0].Member[Expression]", "ReturnValue", "taint"] - ["@aws-sdk/client-rds-data", "Member[ExecuteStatementCommand,BatchExecuteStatementCommand]", "Argument[0].Member[sql]", "ReturnValue", "taint"] - ["@aws-sdk/client-rds-data", "Member[BatchExecuteStatementCommand]", "Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "ReturnValue", "taint"] 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 623ea18de74c..d6b000b03c25 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 @@ -5,6 +5,7 @@ | athena.js:48:22:48:30 | userQuery | athena.js:43:23:43:30 | req.body | athena.js:48:22:48:30 | userQuery | This query string depends on a $@. | athena.js:43:23:43:30 | req.body | user-provided value | | athena.js:57:22:57:30 | userQuery | athena.js:43:23:43:30 | req.body | athena.js:57:22:57:30 | userQuery | This query string depends on a $@. | athena.js:43:23:43:30 | req.body | user-provided value | | athena.js:66:22:66:30 | userQuery | athena.js:43:23:43:30 | req.body | athena.js:66:22:66:30 | userQuery | This query string depends on a $@. | athena.js:43:23:43:30 | req.body | user-provided value | +| athena.js:84:23:84:29 | command | athena.js:75:32:75:39 | req.body | athena.js:84:23:84:29 | command | This query string depends on a $@. | athena.js:75:32:75:39 | req.body | user-provided value | | clients3.js:18:23:18:60 | new Sel ... params) | clients3.js:10:26:10:33 | req.body | clients3.js:18:23:18:60 | new Sel ... params) | This query string depends on a $@. | clients3.js:10:26:10:33 | req.body | user-provided value | | clients3.js:29:21:29:68 | "SELECT ... usInput | clients3.js:23:26:23:33 | req.body | clients3.js:29:21:29:68 | "SELECT ... usInput | This query string depends on a $@. | clients3.js:23:26:23:33 | req.body | user-provided value | | clients3.js:38:21:38:68 | "SELECT ... usInput | clients3.js:23:26:23:33 | req.body | clients3.js:38:21:38:68 | "SELECT ... usInput | This query string depends on a $@. | clients3.js:23:26:23:33 | req.body | user-provided value | @@ -161,8 +162,8 @@ edges | athena.js:9:11:9:19 | userQuery | athena.js:33:22:33:30 | userQuery | provenance | | | athena.js:9:23:9:30 | req.body | athena.js:9:11:9:19 | userQuery | provenance | | | athena.js:13:11:13:17 | params1 [QueryString] | athena.js:18:46:18:52 | params1 [QueryString] | provenance | | -| athena.js:13:21:17:5 | {\\n ... }\\n } [QueryString] | athena.js:13:11:13:17 | params1 [QueryString] | provenance | | -| athena.js:14:22:14:38 | "SQL" + userQuery | athena.js:13:21:17:5 | {\\n ... }\\n } [QueryString] | provenance | | +| athena.js:13:21:17:5 | { \\n ... }\\n } [QueryString] | athena.js:13:11:13:17 | params1 [QueryString] | provenance | | +| athena.js:14:22:14:38 | "SQL" + userQuery | athena.js:13:21:17:5 | { \\n ... }\\n } [QueryString] | provenance | | | athena.js:14:30:14:38 | userQuery | athena.js:14:22:14:38 | "SQL" + userQuery | provenance | | | athena.js:18:11:18:11 | p | athena.js:19:23:19:23 | p | provenance | | | athena.js:18:15:18:53 | new Sta ... arams1) | athena.js:18:11:18:11 | p | provenance | | @@ -179,6 +180,14 @@ edges | athena.js:43:11:43:19 | userQuery | athena.js:57:22:57:30 | userQuery | provenance | | | athena.js:43:11:43:19 | userQuery | athena.js:66:22:66:30 | userQuery | provenance | | | athena.js:43:23:43:30 | req.body | athena.js:43:11:43:19 | userQuery | provenance | | +| athena.js:75:11:75:28 | userQueryStatement | athena.js:80:25:80:42 | userQueryStatement | provenance | | +| athena.js:75:32:75:39 | req.body | athena.js:75:11:75:28 | userQueryStatement | provenance | | +| athena.js:77:11:77:15 | input [QueryStatement] | athena.js:83:56:83:60 | input [QueryStatement] | provenance | | +| athena.js:77:19:82:5 | {\\n ... ,\\n } [QueryStatement] | athena.js:77:11:77:15 | input [QueryStatement] | provenance | | +| athena.js:80:25:80:42 | userQueryStatement | athena.js:77:19:82:5 | {\\n ... ,\\n } [QueryStatement] | provenance | | +| athena.js:83:11:83:17 | command | athena.js:84:23:84:29 | command | provenance | | +| athena.js:83:21:83:61 | new Cre ... (input) | athena.js:83:11:83:17 | command | provenance | | +| athena.js:83:56:83:60 | input [QueryStatement] | athena.js:83:21:83:61 | new Cre ... (input) | provenance | | | clients3.js:10:9:10:22 | maliciousInput | clients3.js:16:55:16:68 | maliciousInput | provenance | | | clients3.js:10:26:10:33 | req.body | clients3.js:10:9:10:22 | maliciousInput | provenance | | | clients3.js:12:11:12:16 | params [Expression] | clients3.js:18:54:18:59 | params [Expression] | provenance | | @@ -615,7 +624,7 @@ nodes | athena.js:9:11:9:19 | userQuery | semmle.label | userQuery | | athena.js:9:23:9:30 | req.body | semmle.label | req.body | | athena.js:13:11:13:17 | params1 [QueryString] | semmle.label | params1 [QueryString] | -| athena.js:13:21:17:5 | {\\n ... }\\n } [QueryString] | semmle.label | {\\n ... }\\n } [QueryString] | +| athena.js:13:21:17:5 | { \\n ... }\\n } [QueryString] | semmle.label | { \\n ... }\\n } [QueryString] | | athena.js:14:22:14:38 | "SQL" + userQuery | semmle.label | "SQL" + userQuery | | athena.js:14:30:14:38 | userQuery | semmle.label | userQuery | | athena.js:18:11:18:11 | p | semmle.label | p | @@ -637,6 +646,15 @@ nodes | athena.js:48:22:48:30 | userQuery | semmle.label | userQuery | | athena.js:57:22:57:30 | userQuery | semmle.label | userQuery | | athena.js:66:22:66:30 | userQuery | semmle.label | userQuery | +| athena.js:75:11:75:28 | userQueryStatement | semmle.label | userQueryStatement | +| athena.js:75:32:75:39 | req.body | semmle.label | req.body | +| athena.js:77:11:77:15 | input [QueryStatement] | semmle.label | input [QueryStatement] | +| athena.js:77:19:82:5 | {\\n ... ,\\n } [QueryStatement] | semmle.label | {\\n ... ,\\n } [QueryStatement] | +| athena.js:80:25:80:42 | userQueryStatement | semmle.label | userQueryStatement | +| athena.js:83:11:83:17 | command | semmle.label | command | +| athena.js:83:21:83:61 | new Cre ... (input) | semmle.label | new Cre ... (input) | +| athena.js:83:56:83:60 | input [QueryStatement] | semmle.label | input [QueryStatement] | +| athena.js:84:23:84:29 | command | semmle.label | command | | clients3.js:10:9:10:22 | maliciousInput | semmle.label | maliciousInput | | clients3.js:10:26:10:33 | req.body | semmle.label | req.body | | clients3.js:12:11:12:16 | params [Expression] | semmle.label | params [Expression] | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js b/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js index 0c6e5e1d8a74..a671fdc53567 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/untyped/athena.js @@ -72,7 +72,7 @@ app.post('/v2/athena/all', async (req, res) => { }); app.post('/dynamodb-v3', async (req, res) => { - const userQueryStatement = req.body.query; // $ MISSING: Source + const userQueryStatement = req.body.query; // $ Source const client = new AthenaClient({ region: "us-east-1" }); const input = { StatementName: "STRING_VALUE", @@ -81,5 +81,5 @@ app.post('/dynamodb-v3', async (req, res) => { Description: "STRING_VALUE", }; const command = new CreatePreparedStatementCommand(input); - await client.send(command); // $ MISSING: Alert + await client.send(command); // $ Alert }); From 10f3a83fcb1df4652355b1d0f06db3e06694e6f9 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 16 Sep 2025 14:50:49 +0200 Subject: [PATCH 18/20] Fixed model type names Co-authored-by: asgerf --- javascript/ql/lib/ext/aws-sdk.model.yml | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/javascript/ql/lib/ext/aws-sdk.model.yml b/javascript/ql/lib/ext/aws-sdk.model.yml index dfda93d2d3f8..0598669006cf 100644 --- a/javascript/ql/lib/ext/aws-sdk.model.yml +++ b/javascript/ql/lib/ext/aws-sdk.model.yml @@ -3,10 +3,10 @@ extensions: pack: codeql/javascript-all extensible: typeModel data: - - ["AthenaClientV2", "aws-sdk", "Member[Athena]"] - - ["S3ClientV2", "aws-sdk", "Member[S3]"] - - ["RDSDataClientV2", "aws-sdk", "Member[RDSDataService]"] - - ["DynamoDBClientV2", "aws-sdk", "Member[DynamoDB]"] + - ["aws-sdk.Athena", "aws-sdk", "Member[Athena]"] + - ["aws-sdk.S3", "aws-sdk", "Member[S3]"] + - ["aws-sdk.RDSDataService", "aws-sdk", "Member[RDSDataService]"] + - ["aws-sdk.DynamoDB", "aws-sdk", "Member[DynamoDB]"] - ["AWS-V3-Common", "@aws-sdk/client-athena", "Member[AthenaClient]"] - ["AWS-V3-Common", "@aws-sdk/client-s3", "Member[S3Client]"] - ["AWS-V3-Common", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] @@ -19,12 +19,12 @@ extensions: - ["aws-sdk", "AnyMember.Member[secretAccessKey,accessKeyId]", "credentials-key"] - ["aws-sdk", "Member[Credentials].Argument[0,1]", "credentials-key"] - ["AWS-V3-Common", "ReturnValue.Member[send].Argument[0]", "sql-injection"] - - ["AthenaClientV2", "ReturnValue.Member[startQueryExecution,createNamedQuery,updateNamedQuery].Argument[0].Member[QueryString]", "sql-injection"] - - ["S3ClientV2", "ReturnValue.Member[selectObjectContent].Argument[0].Member[Expression]", "sql-injection"] - - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[0].Member[sql]", "sql-injection"] - - ["RDSDataClientV2", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "sql-injection"] - - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement].Argument[0].Member[Statement]", "sql-injection"] - - ["DynamoDBClientV2", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[Statements].ArrayElement.Member[Statement]", "sql-injection"] + - ["aws-sdk.Athena", "ReturnValue.Member[startQueryExecution,createNamedQuery,updateNamedQuery].Argument[0].Member[QueryString]", "sql-injection"] + - ["aws-sdk.S3", "ReturnValue.Member[selectObjectContent].Argument[0].Member[Expression]", "sql-injection"] + - ["aws-sdk.RDSDataService", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[0].Member[sql]", "sql-injection"] + - ["aws-sdk.RDSDataService", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[parameterSets].ArrayElement.Member[sql]", "sql-injection"] + - ["aws-sdk.DynamoDB", "ReturnValue.Member[executeStatement].Argument[0].Member[Statement]", "sql-injection"] + - ["aws-sdk.DynamoDB", "ReturnValue.Member[batchExecuteStatement].Argument[0].Member[Statements].ArrayElement.Member[Statement]", "sql-injection"] - addsTo: pack: codeql/javascript-all extensible: summaryModel @@ -42,11 +42,11 @@ extensions: extensible: sourceModel data: - ["AWS-V3-Common", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] - - ["AthenaClientV2", "ReturnValue.Member[getQueryResults].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - - ["AthenaClientV2", "ReturnValue.Member[getQueryResults].Argument[1].Parameter[1]", "database-access-result"] - - ["S3ClientV2", "ReturnValue.Member[getObject].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - - ["S3ClientV2", "ReturnValue.Member[getObject].Argument[1].Parameter[1]", "database-access-result"] - - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - - ["RDSDataClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[1].Parameter[1]", "database-access-result"] - - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement,query,scan,getItem,batchGetItem].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - - ["DynamoDBClientV2", "ReturnValue.Member[executeStatement,batchExecuteStatement,query,scan,getItem,batchGetItem].Argument[1].Parameter[1]", "database-access-result"] + - ["aws-sdk.Athena", "ReturnValue.Member[getQueryResults].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["aws-sdk.Athena", "ReturnValue.Member[getQueryResults].Argument[1].Parameter[1]", "database-access-result"] + - ["aws-sdk.S3", "ReturnValue.Member[getObject].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["aws-sdk.S3", "ReturnValue.Member[getObject].Argument[1].Parameter[1]", "database-access-result"] + - ["aws-sdk.RDSDataService", "ReturnValue.Member[executeStatement,batchExecuteStatement].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["aws-sdk.RDSDataService", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[1].Parameter[1]", "database-access-result"] + - ["aws-sdk.DynamoDB", "ReturnValue.Member[executeStatement,batchExecuteStatement,query,scan,getItem,batchGetItem].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] + - ["aws-sdk.DynamoDB", "ReturnValue.Member[executeStatement,batchExecuteStatement,query,scan,getItem,batchGetItem].Argument[1].Parameter[1]", "database-access-result"] From 4df8db0d7e2df76d50d9a97b6fe0dc800a87e1eb Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Tue, 16 Sep 2025 14:51:53 +0200 Subject: [PATCH 19/20] Renamed `AWS-V3-Common` to `@aws-sdk/client.Client` --- javascript/ql/lib/ext/aws-sdk.model.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/javascript/ql/lib/ext/aws-sdk.model.yml b/javascript/ql/lib/ext/aws-sdk.model.yml index 0598669006cf..40fa4b8b703a 100644 --- a/javascript/ql/lib/ext/aws-sdk.model.yml +++ b/javascript/ql/lib/ext/aws-sdk.model.yml @@ -7,10 +7,10 @@ extensions: - ["aws-sdk.S3", "aws-sdk", "Member[S3]"] - ["aws-sdk.RDSDataService", "aws-sdk", "Member[RDSDataService]"] - ["aws-sdk.DynamoDB", "aws-sdk", "Member[DynamoDB]"] - - ["AWS-V3-Common", "@aws-sdk/client-athena", "Member[AthenaClient]"] - - ["AWS-V3-Common", "@aws-sdk/client-s3", "Member[S3Client]"] - - ["AWS-V3-Common", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] - - ["AWS-V3-Common", "@aws-sdk/client-rds-data", "Member[RDSDataClient]"] + - ["@aws-sdk/client.Client", "@aws-sdk/client-athena", "Member[AthenaClient]"] + - ["@aws-sdk/client.Client", "@aws-sdk/client-s3", "Member[S3Client]"] + - ["@aws-sdk/client.Client", "@aws-sdk/client-dynamodb", "Member[DynamoDBClient,DynamoDB]"] + - ["@aws-sdk/client.Client", "@aws-sdk/client-rds-data", "Member[RDSDataClient]"] - addsTo: pack: codeql/javascript-all extensible: sinkModel @@ -18,7 +18,7 @@ extensions: - ["aws-sdk", "AnyMember.Argument[0].Member[secretAccessKey,accessKeyId]", "credentials-key"] - ["aws-sdk", "AnyMember.Member[secretAccessKey,accessKeyId]", "credentials-key"] - ["aws-sdk", "Member[Credentials].Argument[0,1]", "credentials-key"] - - ["AWS-V3-Common", "ReturnValue.Member[send].Argument[0]", "sql-injection"] + - ["@aws-sdk/client.Client", "ReturnValue.Member[send].Argument[0]", "sql-injection"] - ["aws-sdk.Athena", "ReturnValue.Member[startQueryExecution,createNamedQuery,updateNamedQuery].Argument[0].Member[QueryString]", "sql-injection"] - ["aws-sdk.S3", "ReturnValue.Member[selectObjectContent].Argument[0].Member[Expression]", "sql-injection"] - ["aws-sdk.RDSDataService", "ReturnValue.Member[executeStatement,batchExecuteStatement].Argument[0].Member[sql]", "sql-injection"] @@ -41,7 +41,7 @@ extensions: pack: codeql/javascript-all extensible: sourceModel data: - - ["AWS-V3-Common", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] + - ["@aws-sdk/client.Client", "ReturnValue.Member[send].ReturnValue.Awaited", "database-access-result"] - ["aws-sdk.Athena", "ReturnValue.Member[getQueryResults].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] - ["aws-sdk.Athena", "ReturnValue.Member[getQueryResults].Argument[1].Parameter[1]", "database-access-result"] - ["aws-sdk.S3", "ReturnValue.Member[getObject].ReturnValue.Member[promise].ReturnValue.Awaited", "database-access-result"] From ca667b51319a86d7e83477cb4d5caf9c1309e592 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 17 Sep 2025 10:24:45 +0200 Subject: [PATCH 20/20] JS: fix test expectations from rebasing --- .../CWE-089/untyped/SqlInjection.expected | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) 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 d6b000b03c25..f28fb93238d9 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 @@ -576,21 +576,21 @@ edges | pg-promise.js:22:11:22:15 | query | pg-promise.js:60:20:60:24 | query | provenance | | | pg-promise.js:22:11:22:15 | query | pg-promise.js:63:23:63:27 | query | provenance | | | pg-promise.js:22:11:22:15 | query | pg-promise.js:64:16:64:20 | query | provenance | | -| rds-client.js:8:11:8:36 | userQuery | rds-client.js:17:14:17:22 | userQuery | provenance | | -| rds-client.js:8:11:8:36 | userQuery | rds-client.js:33:24:33:32 | userQuery | provenance | | -| rds-client.js:8:23:8:30 | req.body | rds-client.js:8:11:8:36 | userQuery | provenance | | -| rds-client.js:13:11:18:5 | params1 [sql] | rds-client.js:19:51:19:57 | params1 [sql] | provenance | | -| rds-client.js:13:21:18:5 | {\\n ... y\\n } [sql] | rds-client.js:13:11:18:5 | params1 [sql] | provenance | | +| rds-client.js:8:11:8:19 | userQuery | rds-client.js:17:14:17:22 | userQuery | provenance | | +| rds-client.js:8:11:8:19 | userQuery | rds-client.js:33:24:33:32 | userQuery | provenance | | +| rds-client.js:8:23:8:30 | req.body | rds-client.js:8:11:8:19 | userQuery | provenance | | +| rds-client.js:13:11:13:17 | params1 [sql] | rds-client.js:19:51:19:57 | params1 [sql] | provenance | | +| rds-client.js:13:21:18:5 | {\\n ... y\\n } [sql] | rds-client.js:13:11:13:17 | params1 [sql] | provenance | | | rds-client.js:17:14:17:22 | userQuery | rds-client.js:13:21:18:5 | {\\n ... y\\n } [sql] | provenance | | | rds-client.js:19:51:19:57 | params1 [sql] | rds-client.js:19:23:19:58 | new Exe ... arams1) | provenance | | -| rds-client.js:29:11:34:5 | params [sqlStatements] | rds-client.js:36:45:36:50 | params [sqlStatements] | provenance | | -| rds-client.js:29:20:34:5 | {\\n ... y\\n } [sqlStatements] | rds-client.js:29:11:34:5 | params [sqlStatements] | provenance | | +| rds-client.js:29:11:29:16 | params [sqlStatements] | rds-client.js:36:45:36:50 | params [sqlStatements] | provenance | | +| rds-client.js:29:20:34:5 | {\\n ... y\\n } [sqlStatements] | rds-client.js:29:11:29:16 | params [sqlStatements] | provenance | | | rds-client.js:33:24:33:32 | userQuery | rds-client.js:29:20:34:5 | {\\n ... y\\n } [sqlStatements] | provenance | | | rds-client.js:36:45:36:50 | params [sqlStatements] | rds-client.js:36:23:36:51 | new Exe ... params) | provenance | | -| rds-client.js:44:11:44:36 | userQuery | rds-client.js:53:14:53:22 | userQuery | provenance | | -| rds-client.js:44:23:44:30 | req.body | rds-client.js:44:11:44:36 | userQuery | provenance | | -| rds-client.js:45:11:45:40 | userQueries | rds-client.js:61:24:61:34 | userQueries | provenance | | -| rds-client.js:45:25:45:32 | req.body | rds-client.js:45:11:45:40 | userQueries | provenance | | +| rds-client.js:44:11:44:19 | userQuery | rds-client.js:53:14:53:22 | userQuery | provenance | | +| rds-client.js:44:23:44:30 | req.body | rds-client.js:44:11:44:19 | userQuery | provenance | | +| rds-client.js:45:11:45:21 | userQueries | rds-client.js:61:24:61:34 | userQueries | provenance | | +| rds-client.js:45:25:45:32 | req.body | rds-client.js:45:11:45:21 | userQueries | provenance | | | rds-client.js:61:24:61:34 | userQueries | rds-client.js:61:40:61:42 | sql | provenance | | | rds-client.js:61:40:61:42 | sql | rds-client.js:61:50:61:52 | sql | provenance | | | redis.js:10:16:10:23 | req.body | redis.js:10:16:10:27 | req.body.key | provenance | Config | @@ -979,21 +979,21 @@ nodes | pg-promise.js:60:20:60:24 | query | semmle.label | query | | pg-promise.js:63:23:63:27 | query | semmle.label | query | | pg-promise.js:64:16:64:20 | query | semmle.label | query | -| rds-client.js:8:11:8:36 | userQuery | semmle.label | userQuery | +| rds-client.js:8:11:8:19 | userQuery | semmle.label | userQuery | | rds-client.js:8:23:8:30 | req.body | semmle.label | req.body | -| rds-client.js:13:11:18:5 | params1 [sql] | semmle.label | params1 [sql] | +| rds-client.js:13:11:13:17 | params1 [sql] | semmle.label | params1 [sql] | | rds-client.js:13:21:18:5 | {\\n ... y\\n } [sql] | semmle.label | {\\n ... y\\n } [sql] | | rds-client.js:17:14:17:22 | userQuery | semmle.label | userQuery | | rds-client.js:19:23:19:58 | new Exe ... arams1) | semmle.label | new Exe ... arams1) | | rds-client.js:19:51:19:57 | params1 [sql] | semmle.label | params1 [sql] | -| rds-client.js:29:11:34:5 | params [sqlStatements] | semmle.label | params [sqlStatements] | +| rds-client.js:29:11:29:16 | params [sqlStatements] | semmle.label | params [sqlStatements] | | rds-client.js:29:20:34:5 | {\\n ... y\\n } [sqlStatements] | semmle.label | {\\n ... y\\n } [sqlStatements] | | rds-client.js:33:24:33:32 | userQuery | semmle.label | userQuery | | rds-client.js:36:23:36:51 | new Exe ... params) | semmle.label | new Exe ... params) | | rds-client.js:36:45:36:50 | params [sqlStatements] | semmle.label | params [sqlStatements] | -| rds-client.js:44:11:44:36 | userQuery | semmle.label | userQuery | +| rds-client.js:44:11:44:19 | userQuery | semmle.label | userQuery | | rds-client.js:44:23:44:30 | req.body | semmle.label | req.body | -| rds-client.js:45:11:45:40 | userQueries | semmle.label | userQueries | +| rds-client.js:45:11:45:21 | userQueries | semmle.label | userQueries | | rds-client.js:45:25:45:32 | req.body | semmle.label | req.body | | rds-client.js:53:14:53:22 | userQuery | semmle.label | userQuery | | rds-client.js:61:24:61:34 | userQueries | semmle.label | userQueries |