Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions javascript/ql/lib/change-notes/2025-03-26-hana-db-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added support for the `@sap/hana-client`, `@sap/hdbext` and `hdb` packages.
16 changes: 16 additions & 0 deletions javascript/ql/lib/ext/hana-db-client.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: sinkModel
data:
- ["@sap/hana-client", "Member[createConnection].ReturnValue.Member[exec,prepare].Argument[0]", "sql-injection"]
- ["hdb.Client", "Member[exec,prepare,execute].Argument[0]", "sql-injection"]
- ["@sap/hdbext", "Member[loadProcedure].Argument[2]", "sql-injection"]
- ["@sap/hana-client/extension/Stream", "Member[createProcStatement].Argument[1]", "sql-injection"]

- addsTo:
pack: codeql/javascript-all
extensible: typeModel
data:
- ["hdb.Client", "hdb", "Member[createClient].ReturnValue"]
- ["hdb.Client", "@sap/hdbext", "Member[middleware].ReturnValue.GuardedRouteHandler.Parameter[0].Member[db]"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a test that reg.db.exec(..) isn't a sink when there is no call to hdb.createClient().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 32d6ac8

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
| graphql.js:74:46:74:64 | "{ foo" + id + " }" | graphql.js:73:14:73:25 | req.query.id | graphql.js:74:46:74:64 | "{ foo" + id + " }" | This query string depends on a $@. | graphql.js:73:14:73:25 | req.query.id | user-provided value |
| graphql.js:82:14:88:8 | `{\\n ... }` | graphql.js:73:14:73:25 | req.query.id | graphql.js:82:14:88:8 | `{\\n ... }` | This query string depends on a $@. | graphql.js:73:14:73:25 | req.query.id | user-provided value |
| graphql.js:118:38:118:48 | `foo ${id}` | graphql.js:117:16:117:28 | req.params.id | graphql.js:118:38:118:48 | `foo ${id}` | This query string depends on a $@. | graphql.js:117:16:117:28 | req.params.id | user-provided value |
| hana.js:11:19:11:23 | query | hana.js:9:30:9:37 | req.body | hana.js:11:19:11:23 | query | This query string depends on a $@. | hana.js:9:30:9:37 | req.body | user-provided value |
| hana.js:17:35:17:100 | `SELECT ... usInput | hana.js:16:32:16:39 | req.body | hana.js:17:35:17:100 | `SELECT ... usInput | This query string depends on a $@. | hana.js:16:32:16:39 | req.body | user-provided value |
| hana.js:24:33:24:96 | `INSERT ... usInput | hana.js:23:32:23:39 | req.body | hana.js:24:33:24:96 | `INSERT ... usInput | This query string depends on a $@. | hana.js:23:32:23:39 | req.body | user-provided value |
| hana.js:31:31:31:97 | "SELECT ... usInput | hana.js:30:30:30:37 | req.body | hana.js:31:31:31:97 | "SELECT ... usInput | This query string depends on a $@. | hana.js:30:30:30:37 | req.body | user-provided value |
| hana.js:48:15:48:52 | 'SELECT ... usInput | hana.js:47:24:47:31 | req.body | hana.js:48:15:48:52 | 'SELECT ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value |
| hana.js:50:40:50:89 | 'CALL P ... usInput | hana.js:47:24:47:31 | req.body | hana.js:50:40:50:89 | 'CALL P ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value |
| hana.js:54:38:54:66 | 'PROC_D ... usInput | hana.js:47:24:47:31 | req.body | hana.js:54:38:54:66 | 'PROC_D ... usInput | This query string depends on a $@. | hana.js:47:24:47:31 | req.body | user-provided value |
| hana.js:71:44:71:99 | "INSERT ... usInput | hana.js:68:24:68:31 | req.body | hana.js:71:44:71:99 | "INSERT ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value |
| hana.js:73:17:73:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:73:17:73:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value |
| hana.js:74:17:74:54 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:74:17:74:54 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value |
| hana.js:76:20:76:73 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:76:20:76:73 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value |
| hana.js:80:20:80:69 | 'call P ... usInput | hana.js:68:24:68:31 | req.body | hana.js:80:20:80:69 | 'call P ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value |
| hana.js:84:20:84:78 | 'select ... usInput | hana.js:68:24:68:31 | req.body | hana.js:84:20:84:78 | 'select ... usInput | This query string depends on a $@. | hana.js:68:24:68:31 | req.body | user-provided value |
| html-sanitizer.js:16:9:16:59 | `SELECT ... param1 | html-sanitizer.js:13:39:13:44 | param1 | html-sanitizer.js:16:9:16:59 | `SELECT ... param1 | This query string depends on a $@. | html-sanitizer.js:13:39:13:44 | param1 | user-provided value |
| json-schema-validator.js:33:22:33:26 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:33:22:33:26 | query | This query object depends on a $@. | json-schema-validator.js:25:34:25:47 | req.query.data | user-provided value |
| json-schema-validator.js:35:18:35:22 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:35:18:35:22 | query | This query object depends on a $@. | json-schema-validator.js:25:34:25:47 | req.query.data | user-provided value |
Expand Down Expand Up @@ -152,6 +165,41 @@ edges
| graphql.js:117:11:117:28 | id | graphql.js:118:45:118:46 | id | provenance | |
| graphql.js:117:16:117:28 | req.params.id | graphql.js:117:11:117:28 | id | provenance | |
| graphql.js:118:45:118:46 | id | graphql.js:118:38:118:48 | `foo ${id}` | provenance | |
| hana.js:9:13:9:42 | maliciousInput | hana.js:10:64:10:77 | maliciousInput | provenance | |
| hana.js:9:30:9:37 | req.body | hana.js:9:13:9:42 | maliciousInput | provenance | |
| hana.js:10:15:10:80 | query | hana.js:11:19:11:23 | query | provenance | |
| hana.js:10:64:10:77 | maliciousInput | hana.js:10:15:10:80 | query | provenance | |
| hana.js:16:15:16:44 | maliciousInput | hana.js:17:87:17:100 | maliciousInput | provenance | |
| hana.js:16:32:16:39 | req.body | hana.js:16:15:16:44 | maliciousInput | provenance | |
| hana.js:17:87:17:100 | maliciousInput | hana.js:17:35:17:100 | `SELECT ... usInput | provenance | |
| hana.js:23:15:23:44 | maliciousInput | hana.js:24:83:24:96 | maliciousInput | provenance | |
| hana.js:23:32:23:39 | req.body | hana.js:23:15:23:44 | maliciousInput | provenance | |
| hana.js:24:83:24:96 | maliciousInput | hana.js:24:33:24:96 | `INSERT ... usInput | provenance | |
| hana.js:30:13:30:42 | maliciousInput | hana.js:31:84:31:97 | maliciousInput | provenance | |
| hana.js:30:30:30:37 | req.body | hana.js:30:13:30:42 | maliciousInput | provenance | |
| hana.js:31:84:31:97 | maliciousInput | hana.js:31:31:31:97 | "SELECT ... usInput | provenance | |
| hana.js:47:7:47:36 | maliciousInput | hana.js:48:39:48:52 | maliciousInput | provenance | |
| hana.js:47:7:47:36 | maliciousInput | hana.js:50:76:50:89 | maliciousInput | provenance | |
| hana.js:47:7:47:36 | maliciousInput | hana.js:54:53:54:66 | maliciousInput | provenance | |
| hana.js:47:24:47:31 | req.body | hana.js:47:7:47:36 | maliciousInput | provenance | |
| hana.js:48:39:48:52 | maliciousInput | hana.js:48:15:48:52 | 'SELECT ... usInput | provenance | |
| hana.js:48:39:48:52 | maliciousInput | hana.js:50:76:50:89 | maliciousInput | provenance | |
| hana.js:50:76:50:89 | maliciousInput | hana.js:50:40:50:89 | 'CALL P ... usInput | provenance | |
| hana.js:50:76:50:89 | maliciousInput | hana.js:54:53:54:66 | maliciousInput | provenance | |
| hana.js:54:53:54:66 | maliciousInput | hana.js:54:38:54:66 | 'PROC_D ... usInput | provenance | |
| hana.js:68:7:68:36 | maliciousInput | hana.js:71:86:71:99 | maliciousInput | provenance | |
| hana.js:68:7:68:36 | maliciousInput | hana.js:73:41:73:54 | maliciousInput | provenance | |
| hana.js:68:7:68:36 | maliciousInput | hana.js:74:41:74:54 | maliciousInput | provenance | |
| hana.js:68:7:68:36 | maliciousInput | hana.js:76:60:76:73 | maliciousInput | provenance | |
| hana.js:68:7:68:36 | maliciousInput | hana.js:80:56:80:69 | maliciousInput | provenance | |
| hana.js:68:7:68:36 | maliciousInput | hana.js:84:65:84:78 | maliciousInput | provenance | |
| hana.js:68:24:68:31 | req.body | hana.js:68:7:68:36 | maliciousInput | provenance | |
| hana.js:71:86:71:99 | maliciousInput | hana.js:71:44:71:99 | "INSERT ... usInput | provenance | |
| hana.js:73:41:73:54 | maliciousInput | hana.js:73:17:73:54 | 'select ... usInput | provenance | |
| hana.js:74:41:74:54 | maliciousInput | hana.js:74:17:74:54 | 'select ... usInput | provenance | |
| hana.js:76:60:76:73 | maliciousInput | hana.js:76:20:76:73 | 'select ... usInput | provenance | |
| hana.js:80:56:80:69 | maliciousInput | hana.js:80:20:80:69 | 'call P ... usInput | provenance | |
| hana.js:84:65:84:78 | maliciousInput | hana.js:84:20:84:78 | 'select ... usInput | provenance | |
| html-sanitizer.js:13:39:13:44 | param1 | html-sanitizer.js:14:18:14:23 | param1 | provenance | |
| html-sanitizer.js:14:5:14:24 | param1 | html-sanitizer.js:16:54:16:59 | param1 | provenance | |
| html-sanitizer.js:14:14:14:24 | xss(param1) | html-sanitizer.js:14:5:14:24 | param1 | provenance | |
Expand Down Expand Up @@ -504,6 +552,45 @@ nodes
| graphql.js:117:16:117:28 | req.params.id | semmle.label | req.params.id |
| graphql.js:118:38:118:48 | `foo ${id}` | semmle.label | `foo ${id}` |
| graphql.js:118:45:118:46 | id | semmle.label | id |
| hana.js:9:13:9:42 | maliciousInput | semmle.label | maliciousInput |
| hana.js:9:30:9:37 | req.body | semmle.label | req.body |
| hana.js:10:15:10:80 | query | semmle.label | query |
| hana.js:10:64:10:77 | maliciousInput | semmle.label | maliciousInput |
| hana.js:11:19:11:23 | query | semmle.label | query |
| hana.js:16:15:16:44 | maliciousInput | semmle.label | maliciousInput |
| hana.js:16:32:16:39 | req.body | semmle.label | req.body |
| hana.js:17:35:17:100 | `SELECT ... usInput | semmle.label | `SELECT ... usInput |
| hana.js:17:87:17:100 | maliciousInput | semmle.label | maliciousInput |
| hana.js:23:15:23:44 | maliciousInput | semmle.label | maliciousInput |
| hana.js:23:32:23:39 | req.body | semmle.label | req.body |
| hana.js:24:33:24:96 | `INSERT ... usInput | semmle.label | `INSERT ... usInput |
| hana.js:24:83:24:96 | maliciousInput | semmle.label | maliciousInput |
| hana.js:30:13:30:42 | maliciousInput | semmle.label | maliciousInput |
| hana.js:30:30:30:37 | req.body | semmle.label | req.body |
| hana.js:31:31:31:97 | "SELECT ... usInput | semmle.label | "SELECT ... usInput |
| hana.js:31:84:31:97 | maliciousInput | semmle.label | maliciousInput |
| hana.js:47:7:47:36 | maliciousInput | semmle.label | maliciousInput |
| hana.js:47:24:47:31 | req.body | semmle.label | req.body |
| hana.js:48:15:48:52 | 'SELECT ... usInput | semmle.label | 'SELECT ... usInput |
| hana.js:48:39:48:52 | maliciousInput | semmle.label | maliciousInput |
| hana.js:50:40:50:89 | 'CALL P ... usInput | semmle.label | 'CALL P ... usInput |
| hana.js:50:76:50:89 | maliciousInput | semmle.label | maliciousInput |
| hana.js:54:38:54:66 | 'PROC_D ... usInput | semmle.label | 'PROC_D ... usInput |
| hana.js:54:53:54:66 | maliciousInput | semmle.label | maliciousInput |
| hana.js:68:7:68:36 | maliciousInput | semmle.label | maliciousInput |
| hana.js:68:24:68:31 | req.body | semmle.label | req.body |
| hana.js:71:44:71:99 | "INSERT ... usInput | semmle.label | "INSERT ... usInput |
| hana.js:71:86:71:99 | maliciousInput | semmle.label | maliciousInput |
| hana.js:73:17:73:54 | 'select ... usInput | semmle.label | 'select ... usInput |
| hana.js:73:41:73:54 | maliciousInput | semmle.label | maliciousInput |
| hana.js:74:17:74:54 | 'select ... usInput | semmle.label | 'select ... usInput |
| hana.js:74:41:74:54 | maliciousInput | semmle.label | maliciousInput |
| hana.js:76:20:76:73 | 'select ... usInput | semmle.label | 'select ... usInput |
| hana.js:76:60:76:73 | maliciousInput | semmle.label | maliciousInput |
| hana.js:80:20:80:69 | 'call P ... usInput | semmle.label | 'call P ... usInput |
| hana.js:80:56:80:69 | maliciousInput | semmle.label | maliciousInput |
| hana.js:84:20:84:78 | 'select ... usInput | semmle.label | 'select ... usInput |
| hana.js:84:65:84:78 | maliciousInput | semmle.label | maliciousInput |
| html-sanitizer.js:13:39:13:44 | param1 | semmle.label | param1 |
| html-sanitizer.js:14:5:14:24 | param1 | semmle.label | param1 |
| html-sanitizer.js:14:14:14:24 | xss(param1) | semmle.label | xss(param1) |
Expand Down
86 changes: 86 additions & 0 deletions javascript/ql/test/query-tests/Security/CWE-089/untyped/hana.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const hana = require('@sap/hana-client');
const express = require('express');

const app = express();
const connectionParams = {};
app.post('/documents/find', (req, res) => {
const conn = hana.createConnection();
conn.connect(connectionParams, (err) => {
let maliciousInput = req.body.data; // $ Source
const query = `SELECT * FROM Users WHERE username = '${maliciousInput}'`;
conn.exec(query, (err, rows) => {}); // $ Alert
conn.disconnect();
});

conn.connect(connectionParams, (err) => {
const maliciousInput = req.body.data; // $ Source
const stmt = conn.prepare(`SELECT * FROM Test WHERE ID = ? AND username = ` + maliciousInput); // $ Alert
stmt.exec([maliciousInput], (err, rows) => {}); // maliciousInput is treated as a parameter
conn.disconnect();
});

conn.connect(connectionParams, (err) => {
const maliciousInput = req.body.data; // $ Source
var stmt = conn.prepare(`INSERT INTO Customers(ID, NAME) VALUES(?, ?) ` + maliciousInput); // $ Alert
stmt.execBatch([[1, maliciousInput], [2, maliciousInput]], function(err, rows) {}); // maliciousInput is treated as a parameter
conn.disconnect();
});

conn.connect(connectionParams, (err) => {
const maliciousInput = req.body.data; // $ Source
var stmt = conn.prepare("SELECT * FROM Customers WHERE ID >= ? AND ID < ?" + maliciousInput); // $ Alert
stmt.execQuery([100, maliciousInput], function(err, rs) {}); // $ maliciousInput is treated as a parameter
conn.disconnect();
});
});

var hdbext = require('@sap/hdbext');
var express = require('express');
var dbStream = require('@sap/hana-client/extension/Stream');

var app1 = express();
const hanaConfig = {};
app1.use(hdbext.middleware(hanaConfig));

app1.get('/execute-query', function (req, res) {
var client = req.db;
let maliciousInput = req.body.data; // $ Source
client.exec('SELECT * FROM DUMMY' + maliciousInput, function (err, rs) {}); // $ Alert

dbStream.createProcStatement(client, 'CALL PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function (err, stmt) { // $ Alert
stmt.exec({ A: maliciousInput, B: 4 }, function (err, params, dummyRows, tablesRows) {}); // maliciousInput is treated as a parameter
});

hdbext.loadProcedure(client, null, 'PROC_DUMMY' + maliciousInput, function(err, sp) { // $ Alert
sp(3, maliciousInput, function(err, parameters, dummyRows, tablesRows) {}); // maliciousInput is treated as a parameter
});
});


var hdb = require('hdb');
const async = require('async');

const options = {};
const app2 = express();

app2.post('/documents/find', (req, res) => {
var client = hdb.createClient(options);
let maliciousInput = req.body.data; // $ Source

client.connect(function onconnect(err) {
async.series([client.exec.bind(client, "INSERT INTO NUMBERS VALUES (1, 'one')" + maliciousInput)], function (err) {}); // $ Alert

client.exec('select * from DUMMY' + maliciousInput, function (err, rows) {}); // $ Alert
client.exec('select * from DUMMY' + maliciousInput, options, function(err, rows) {}); // $ Alert

client.prepare('select * from DUMMY where DUMMY = ?' + maliciousInput, function (err, statement){ // $ Alert
statement.exec([maliciousInput], function (err, rows) {}); // maliciousInput is treated as a parameter
});

client.prepare('call PROC_DUMMY (?, ?, ?, ?, ?)' + maliciousInput, function(err, statement){ // $ Alert
statement.exec({A: 3, B: maliciousInput}, function(err, parameters, dummyRows, tableRows) {});
});

client.execute('select A, B from TEST.NUMBERS order by A' + maliciousInput, function(err, rs) {}); // $ Alert
});
});