GHSA-fr9j-6mvq-frcv: Kysely has a MySQL SQL Injection via Backslash Escape Bypass in non-type-safe usage of JSON path keys.
SummaryThe `sanitizeStringLiteral` method in Kysely's query compiler escapes single quotes (`'` → `''`) but does not escape backslashes. On MySQL with the default `BACKSLASH_ESCAPES` SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL.DetailsWhen a user calls `.key(value)` on a JSON path builder, the value flows through:`JSONPathBuilder.key(key)` at `src/query-builder/json-path-builder.ts:166` stores the key as a `JSONPathLegNode` with type `'Member'`.During compilation, `DefaultQueryCompiler.visitJSONPath()` at `src/query-compiler/default-query-compiler.ts:1609` wraps the full path in single quotes (`'$...'`).`DefaultQueryCompiler.visitJSONPathLeg()` at `src/query-compiler/default-query-compiler.ts:1623` calls `sanitizeStringLiteral(node.value)` for string values (line 1630).`sanitizeStringLiteral()` at `src/query-compiler/default-query-compiler.ts:1819-1821` only doubles single quotes:The `MysqlQueryCompiler` does not override `sanitizeStringLiteral` — it only overrides `sanitizeIdentifier` for backtick escaping.**The bypass mechanism:**In MySQL's default `BACKSLASH_ESCAPES` mode, `\'` inside a string literal is interpreted as an escaped single quote (not a literal backslash followed by a string terminator). Given the input `\' OR 1=1 --`:`sanitizeStringLiteral` sees the `'` and doubles it: `\'' OR 1=1 --`The full compiled path becomes: `'$.\'' OR 1=1 --'`MySQL parses `\'` as an escaped quote character (consuming the first `'` of the doubled pair)The second `'` now terminates the string literal` OR 1=1 --` is parsed as SQL, achieving injectionThe existing test at `test/node/src/sql-injection.test.ts:61-83` only tests single-quote injection (`first' as ...`), which the `''` doubling correctly prevents. It does not test the backslash bypass vector.PoC**Simplified verification of the bypass mechanics:****Note:** PostgreSQL is unaffected because `standard_conforming_strings=on` (default since 9.1) disables backslash escape interpretation. SQLite does not interpret backslash escapes in string literals. Only MySQL (and MariaDB) with the default `BACKSLASH_ESCAPES` mode are vulnerable.Impact**SQL Injection:** An attacker who can control values passed to the `.key()` JSON path builder API can inject arbitrary SQL into queries executed against MySQL databases.**Data Exfiltration:** Using UNION-based injection, an attacker can read arbitrary data from any table accessible to the database user.**Data Modification/Deletion:** If the application's database user has write permissions, stacked queries (when enabled via `multipleStatements: true`) or subquery-based injection can modify or delete data.**Full Database Compromise:** Depending on MySQL user privileges, the attacker could potentially execute administrative operations.**Scope:** Any application using Kysely with MySQL that passes user-controlled input to `.key()`, `.at()`, or other JSON path builder methods. While this is a specific API usage pattern (justifying AC:H), it is realistic in applications with dynamic JSON schema access or user-configurable JSON field selection.Recommended FixEscape backslashes in addition to single quotes in `sanitizeStringLiteral`. This neutralizes the bypass in MySQL's `BACKSLASH_ESCAPES` mode:With this fix, the input `\' OR 1=1 --` becomes `\\'' OR 1=1 --`, where MySQL parses `\\` as a literal backslash, `''` as an escaped quote, and the string literal is never terminated.Alternatively, the MySQL-specific compiler could override `sanitizeStringLiteral` to handle backslash escaping only for MySQL, keeping the base implementation unchanged for PostgreSQL and SQLite which don't need it:A corresponding test should be added to `test/node/src/sql-injection.test.ts`:
CVSS v3.1: 8.1