MariaDB connector multi-byte charset SQL injection and cleartext auth-plugin boundary checks¶
An August 28 GitHub-reviewed wave across the MariaDB connector family yields two durable operator patterns: (1) client-side Buffer parameter escaping under a multi-byte client charset whose trail-byte range includes 0x5C, which lets an attacker-controlled lead byte swallow the connector's escape backslash and break out of the string literal, and (2) cleartext-password authentication plugins (mysql_clear_password, dialog/PAM) that are not gated on a secure transport, so a hostile or man-in-the-middle server can receive the account password in plaintext over plain TCP. A third pattern — mid-session character_set_client divergence between driver and server — is the charset-confusion primitive that re-enables byte-wise quoting defeats.
Sources:
- GHSA-g5xc-5w98-jfvm: mariadb-connector-nodejs multi-byte-charset Buffer-parameter SQL injection — fix
0148cadd(CONJS-350) - GHSA-xvr9-35cr-46v9: mariadb-java-client mid-session charset divergence
- GHSA-5rqc-86vf-g8r2: mariadb-connector-r2dbc mid-session charset divergence — fix
38bad9af(R2DBC-124) - GHSA-cqhc-2h57-wpxf: mariadb-connector-nodejs cleartext password leak to a MITM despite
ssl: true - GHSA-g9jj-cgmh-9f38: mariadb-java-client cleartext password disclosure on the initial handshake
- GHSA-42r5-vhpq-m858: mariadb-connector-nodejs PAM/dialog cleartext transmission
- GHSA-qxvw-fvwx-5cp7: mariadb-java-client PAM/dialog cleartext transmission
- GHSA-c857-9x2m-cvh2: mariadb-connector-r2dbc cleartext password disclosure to a MITM server
Reviewed package ranges: node connector <3.2.4, >=3.3.0,<3.3.3, >=3.4.0,<3.4.6, >=3.5.0,<3.5.3; Java connector <2.7.14, >=3.0.0,<3.3.5, >=3.4.0,<3.4.3, >=3.5.0,<3.5.9; R2DBC <1.4.1. Confirm exact package, connector version, client charset, TLS configuration, and fixed-build behavior before reporting.
Authorized validation only
Use a disposable MariaDB/MySQL server and a lab network namespace or container with no route to production, cloud metadata, or internal services. Generate a test CA and fake certificates; use synthetic accounts and a marker-only SQL sink. Never capture real credentials, target a third-party database, relay production traffic, or escalate against a production account.
What changed¶
| Advisory | Component | Boundary | Operator value |
|---|---|---|---|
| GHSA-g5xc-5w98-jfvm | mariadb-connector-nodejs | client-side Buffer escaping under big5/gbk/sjis/cp932/gb18030 swallows the 0x5C escape byte, breaking out of the string literal | Add non-UTF-8 client charsets to any review that binds untrusted data into a Buffer query parameter; binary/server-side prepared statements are not affected. |
| GHSA-xvr9-35cr-46v9 / GHSA-5rqc-86vf-g8r2 | mariadb-java-client / r2dbc-mariadb | a hostile server or stored routine/trigger switches character_set_client mid-session; the driver keeps UTF-8 while the server reinterprets bytes | Treat any path that can steer SET NAMES/session charset state to a non-UTF-8 value as a charset-confusion primitive that can re-enable quoting/escaping defeats. |
| GHSA-cqhc-2h57-wpxf | mariadb-connector-nodejs | fingerprint/identity validation runs after the auth exchange, so a MITM presenting any certificate receives the password before the connection is aborted | For ssl: true + no pinned CA, the credential is transmitted before identity check; capture only a synthetic credential on an owned MITM listener. |
| GHSA-g9jj-cgmh-9f38 | mariadb-java-client | sslMode=verify-full/verify-ca accepts an untrusted cert at the TLS layer and binds fingerprint into the auth exchange, but the initial-handshake path is not covered | mysql_clear_password named in the initial handshake leaks the password in cleartext before the fingerprint check; prove with a fake account against an owned listener. |
| GHSA-42r5-vhpq-m858 / GHSA-qxvw-fvwx-5cp7 | node + Java connectors | PAM/dialog plugin handler did not inherit the mysql_clear_password secure-transport gate, so it sends the password in cleartext over plain TCP | A hostile/MITM server can issue an Authentication Switch Request for dialog and read the plaintext password; the AuthenticationPlugin capability is missing. |
| GHSA-c857-9x2m-cvh2 | r2dbc-mariadb | clear-text password plugins are not gated on transport encryption at the interface level | Same as above: mysql_clear_password/dialog over plain TCP exposes the password. |
Multi-byte-charset Buffer-escape SQL injection (the durable primitive)¶
This is the classic multi-byte escaping bypass, now in the connector's client-side Buffer path:
- Under
utf8mb4(the default) or any charset whose trail-byte range does not include0x5C, byte-wise backslash escaping is sound. - Under
big5,gbk,sjis,cp932, orgb18030, the SQL lexer performs multi-byte recognition (my_ismbchar) before interpreting escape sequences. An attacker byte the lexer treats as a valid lead byte, followed by0x5C, is consumed as a single multi-byte character. The connector's inserted escape backslash is swallowed as that character's trail byte, so the following0x27is no longer escaped and closes the string literal. - Only
Buffer(binary) parameters that are escaped into the SQL text are affected. Parameters bound through the binary/server-side prepared-statement path are sent out-of-band and are not affected.
Bounded PoC workflow¶
- Provision a disposable MariaDB server and a client that uses the affected node connector version with
character_set_clientset to one of the five charsets. Use a synthetic low-privilege account. - Baseline a parameterized query that binds an inert
Buffercontaining only safe text; record the prepared SQL text the connector emits. - Craft the smallest
Buffervalue: a valid lead byte for the chosen charset, then0x5C(backslash), then0x27(quote), then a marker that would terminate the literal and append an inertUNION SELECTthat returns only a synthetic constant. Do not read real tables or escalate. - Capture the connector's escaped SQL text and the server's parse result. A positive result is the marker constant returned as a new column, proving the literal terminated early.
- Add controls: the same payload under
utf8mb4(must stay inert), a binary prepared-statement path (must stay inert), and the fixed connector version (must reject or contain). - Repeat on the Java connector only if it shares the same client-side escaping path for
Buffer-equivalent binary parameters under the same charsets; otherwise note it as out of scope.
Strong evidence is non-UTF-8 client charset + untrusted Buffer bytes -> connector escapes byte-wise -> server lexer consumes the escape backslash as a trail byte -> the closing quote is unescaped -> inert marker SELECT executes. Do not read production data, do not escalate the database account, and do not target any real server.
Cleartext auth-plugin transport gating¶
The mysql_clear_password plugin is gated behind a secure connection: the driver refuses to transmit the password in cleartext over plain TCP. The sibling PAM/dialog plugin handler did not override that gate and inherited the default, so it was not subject to the same secure-transport requirement. Additionally, on the fingerprint-validation paths (ssl: true with no pinned CA, and sslMode=verify-full/verify-ca on the initial handshake), the credential is transmitted before the identity/fingerprint check runs, so an on-path attacker presenting any certificate captures the password before the connection is torn down.
Bounded PoC workflow¶
- Run an owned plain-TCP MariaDB listener (a fake server that speaks enough of the protocol to issue an
Authentication Switch Requestnamingmysql_clear_passwordordialog). Use a lab network namespace with no production routes. - Configure the affected connector to connect to that listener with
sslMode=DISABLE(orssl: false) and a synthetic low-privilege account with a marker password. - Capture the wire bytes. A positive result is the marker password appearing in cleartext in the
dialog/mysql_clear_passwordauth packet. - For the MITM variant: insert an owned TLS-terminating proxy that presents a self-signed certificate, capture the cleartext password during the handshake, and confirm the connector then aborts the connection on fingerprint mismatch. Record only that the synthetic credential was observed; do not relay to a real server.
- Add controls: a secure-transport connection (must not leak), a
dialogplugin under TLS, and the fixed connector version (must gate the clear-text plugin on a secure transport).
Strong evidence is plain-TCP connection + hostile/MITM server naming dialog/mysql_clear_password -> connector sends the synthetic password in cleartext -> fixed version requires TLS before the clear-text plugin. Do not use real credentials, do not relay production traffic, and do not capture internal hostnames.
Charset-confusion as a quoting-defeat primitive¶
The mid-session character_set_client divergence advisories (Java + R2DBC) are the same trust-confusion shape as the Buffer-escape SQLi: the driver and the server interpret the same bytes under different encodings after the server switches the client charset. This is the primitive that can defeat byte-wise quoting/escaping when client and server disagree on a multi-byte encoding. The R2DBC fix rejects any post-init charset change to a non-UTF-8 value with R2dbcNonTransientResourceException (SQLState 08000).
Reusable heuristic: in any review of a database connector, enumerate the paths that can steer SET NAMES / session-state charset tracking to a non-UTF-8 value (stored routine, trigger, server configuration, hostile server) and confirm whether the driver re-validates the encoding assumption after the switch.
Operator triage¶
- Start with the client charset. Multi-byte escaping defeats only fire when the connection charset is
big5,gbk,sjis,cp932, orgb18030. Audit the connector configuration and anySET NAMES/SET character_set_clientin the target's startup path. - Separate text-bound parameters from binary prepared statements. Only parameters escaped into the SQL text are affected; binary/server-side prepared statements are out of scope.
- Bind the auth transport to the plugin. For any review of a DB connector, confirm that every clear-text password plugin is gated on a secure transport and that identity/fingerprint validation happens before the credential is transmitted.
- Keep all proofs disposable. Use a lab server, a synthetic account, a marker-only SQL sink, an owned MITM listener, and generated test certificates. Do not read production data, escalate a database account, or capture real credentials.
Reporting notes¶
- Lead with the precise trust boundary: non-UTF-8 client charset + untrusted
Buffer-> escaped SQL text -> literal breakout, clear-text auth plugin over plain TCP / before identity validation, or mid-session charset divergence defeating byte-wise quoting. - Include connector version, client charset, TLS configuration, the raw and canonical SQL text (for the SQLi) or the wire bytes (for the credential leak), and a patched negative control.
- Keep all proof artifacts disposable: synthetic accounts, marker constants, owned listeners, generated certificates. Do not include real passwords, production table data, internal hostnames, or metadata in wiki/report evidence.