Summary
GHSA-qqf5-x7mj-v43p (fixed in 3.39.19) addressed SQL identifier injection in Budibase's PostgreSQL, MySQL, and SQL Server datasource connectors by adding dedicated identifier-quoting helpers. The same release left the Oracle connector untouched. Oracle's post-write row lookup still wraps the table name in double quotes without escaping, so an Oracle datasource whose table name contains a double-quote character produces a second-order SQL injection that executes as the datasource's database user.
The unfixed sink
packages/server/src/integrations/oracle.ts:577, in OracleIntegration.query(), after a CREATE or UPDATE:
const lastRow = await this.internalQuery({
sql: `SELECT * FROM "${json.table.name}" WHERE ROWID = '${response.lastRowid}'`,
})
json.table.name is interpolated directly inside a double-quoted identifier with no escaping and no parameter binding. A table name containing a " closes the identifier early and lets the remainder be parsed as SQL. (response.lastRowid is the Oracle-generated ROWID and is not attacker-controllable; the sole vector is the table name.)
Why the 3.39.19 fix does not cover it
The patch introduced quotePostgresIdentifier, quoteMySqlIdentifier, and quoteSqlServerIdentifier in packages/server/src/integrations/utils/sqlIdentifiers.ts and applied them at postgres.ts:358 and mysql.ts:306. There is no quoteOracleIdentifier, and oracle.ts was not modified. The Oracle ROWID-lookup sink is a different code path from the three introspection sinks the advisory fixed (Postgres SET search_path, MSSQL INFORMATION_SCHEMA, MySQL DESCRIBE), so it was missed.
Where the table name comes from
json.table.name is the Oracle table name fetched during schema introspection from user_tables.TABLE_NAME (oracle.ts, buildSchema, ~line 185) and stored verbatim in Budibase's metadata. Oracle permits quoted identifiers that contain a ".
Reproduction
- A table whose name contains a double-quote exists in the connected Oracle database — pre-existing, or created by a principal able to run DDL on that database (the Budibase connection user, or a Budibase builder via schema management). For example a table named:
EMP" UNION SELECT username, password, account_status, 1 FROM dba_users --
- Budibase introspects the schema and stores the name verbatim.
- Any Budibase user with WRITE permission on that table performs a row create or update.
- After the write,
oracle.ts:577 runs the ROWID lookup with the table name interpolated; the injected SQL executes as the datasource's Oracle user, and its rows are returned to the caller as the "created/updated" row.
Impact
Arbitrary SQL execution against the Oracle database as the configured connection user — reading or modifying any data that user can reach, beyond the table/row permissions Budibase enforces. The injected SQL is not bounded to Budibase's own schema, so the impact can extend to other database objects and users.
Remediation
Add a quoteOracleIdentifier helper that wraps the identifier in "..." and doubles any embedded " (mirroring quotePostgresIdentifier), and apply it to the table name at oracle.ts:577 — and to any other raw identifier interpolation in oracle.ts. Better still, route the ROWID lookup through the same parameterized/Knex path used by the rest of the query builder so the identifier is quoted by the driver.
Summary
GHSA-qqf5-x7mj-v43p (fixed in 3.39.19) addressed SQL identifier injection in Budibase's PostgreSQL, MySQL, and SQL Server datasource connectors by adding dedicated identifier-quoting helpers. The same release left the Oracle connector untouched. Oracle's post-write row lookup still wraps the table name in double quotes without escaping, so an Oracle datasource whose table name contains a double-quote character produces a second-order SQL injection that executes as the datasource's database user.
The unfixed sink
packages/server/src/integrations/oracle.ts:577, inOracleIntegration.query(), after a CREATE or UPDATE:json.table.nameis interpolated directly inside a double-quoted identifier with no escaping and no parameter binding. A table name containing a"closes the identifier early and lets the remainder be parsed as SQL. (response.lastRowidis the Oracle-generated ROWID and is not attacker-controllable; the sole vector is the table name.)Why the 3.39.19 fix does not cover it
The patch introduced
quotePostgresIdentifier,quoteMySqlIdentifier, andquoteSqlServerIdentifierinpackages/server/src/integrations/utils/sqlIdentifiers.tsand applied them atpostgres.ts:358andmysql.ts:306. There is noquoteOracleIdentifier, andoracle.tswas not modified. The Oracle ROWID-lookup sink is a different code path from the three introspection sinks the advisory fixed (PostgresSET search_path, MSSQLINFORMATION_SCHEMA, MySQLDESCRIBE), so it was missed.Where the table name comes from
json.table.nameis the Oracle table name fetched during schema introspection fromuser_tables.TABLE_NAME(oracle.ts,buildSchema, ~line 185) and stored verbatim in Budibase's metadata. Oracle permits quoted identifiers that contain a".Reproduction
EMP" UNION SELECT username, password, account_status, 1 FROM dba_users --oracle.ts:577runs the ROWID lookup with the table name interpolated; the injected SQL executes as the datasource's Oracle user, and its rows are returned to the caller as the "created/updated" row.Impact
Arbitrary SQL execution against the Oracle database as the configured connection user — reading or modifying any data that user can reach, beyond the table/row permissions Budibase enforces. The injected SQL is not bounded to Budibase's own schema, so the impact can extend to other database objects and users.
Remediation
Add a
quoteOracleIdentifierhelper that wraps the identifier in"..."and doubles any embedded"(mirroringquotePostgresIdentifier), and apply it to the table name atoracle.ts:577— and to any other raw identifier interpolation inoracle.ts. Better still, route the ROWID lookup through the same parameterized/Knex path used by the rest of the query builder so the identifier is quoted by the driver.