HexBrain

MariaDB · Report #3896617

Connector/C TLS auth-switch bypass exposes plaintext password on hostname verification failure

Validstatus
12upvotes
4 Nov 2026disclosed

How it was found

Summary

A logic flaw in run_plugin_auth() allows a man-in-the-middle attacker to capture a client's plaintext database password during TLS-secured authentication. The auth-switch guard in plugins/auth/my_auth.c checks only MARIADB_TLS_VERIFY_TRUST (bit 0, value 1) before blocking non-hashing auth plugins. A hostname-only verification failure (MARIADB_TLS_VERIFY_HOST = 2) bypasses this check because (2 & 1) == 0, letting the attacker switch to mysql_clear_password and receive the password in cleartext before the post-auth catch-all rejects the connection.

Originally reported by: Aisle Research

Environment

  • Component: MariaDB Connector/C (mariadb-connector-c)
  • Affected versions: All versions through current 3.4 branch (verified on commit a4a04c52, 82 commits after v3.4.9 tag). The v3.4.9 release is affected.
  • Platform: All platforms (Linux, Windows, macOS) — the vulnerable code path is platform-independent.
  • Build configuration: Default build with TLS support enabled. mysql_clear_password is registered as a default dynamic auth plugin.

Vulnerability Details

In plugins/auth/my_auth.c, run_plugin_auth() handles server auth-switch requests (0xFE packet). The TLS verification guard at line 875 is:

if ((mysql->net.tls_verify_status & MARIADB_TLS_VERIFY_TRUST) &&
    !password_and_hashing(mysql, auth_plugin))

The tls_verify_status flags are defined as:

  • MARIADB_TLS_VERIFY_TRUST = 1 (CA trust chain failure)
  • MARIADB_TLS_VERIFY_HOST = 2 (hostname mismatch)
  • MARIADB_TLS_VERIFY_AUTO = 3 (both)

When only hostname verification fails, tls_verify_status = 2. The bitwise AND (2 & 1) evaluates to 0, bypassing the guard entirely. This allows a non-hashing plugin like mysql_clear_password to execute and send the password in plaintext before the later catch-all check at line 894 (if (!mysql->net.tls_verify_status) return 0; ... return 1;) terminates the connection.

The initial-auth check at line 484 uses a different comparison: tls_verify_status > MARIADB_TLS_VERIFY_AUTO (i.e. > 3). This is intentionally more permissive — it allows hashing plugins through on first auth even without full CA verification. The auth-switch path needs to be stricter: once a server requests a non-hashing plugin mid-authentication, ANY verification failure should block it.

Steps to Reproduce

  1. Set up a MariaDB/MySQL-compatible test server with a TLS certificate from a trusted CA, but issued for a different hostname (e.g., server is at db.example.com but the cert is issued for other.example.com).

  2. Configure a client using Connector/C with hostname verification enabled (MYSQL_OPT_SSL_VERIFY_SERVER_CERT=1).

  3. Connect. The TLS handshake completes because the CA is trusted, but ma_verification_callback() in openssl.c sets tls_verify_status = MARIADB_TLS_VERIFY_HOST (value 2).

  4. Initial auth check at line 484: 2 > 3 is false — falls through. password_and_hashing() returns true for the default plugin (e.g., caching_sha2_password) — the initial hashed auth exchange proceeds.

  5. The attacker-controlled server responds with 0xFE AuthSwitchRequest selecting mysql_clear_password.

  6. Auth-switch guard at line 875: (2 & 1) == 0 — bypassed.

  7. clear_password_auth_client() in mariadb_cleartext.c sends the full password in cleartext via vio->write_packet().

  8. The attacker captures the plaintext password on the wire.

  9. Only after the plugin completes does the catch-all at line 894 reject the connection — too late, the password is already disclosed.

Network trace expectation: The attacker observes the password bytes in the auth-switch response packet, before the connection error is returned to the client application.

Suggested Fix

Replace the bitmask check with a nonzero check, so that ANY tls_verify_status (trust failure, hostname failure, or both) blocks non-hashing plugins on auth-switch:

diff --git a/plugins/auth/my_auth.c b/plugins/auth/my_auth.c
index 8ee14b66..17007b8b 100644
--- a/plugins/auth/my_auth.c
+++ b/plugins/auth/my_auth.c
@@ -872,12 +872,12 @@ retry:
       auth_plugin= &dummy_fallback_client_plugin;
 
     /* can we use this plugin with this tls server cert ? */
-    if ((mysql->net.tls_verify_status & MARIADB_TLS_VERIFY_TRUST) &&
+    if (mysql->net.tls_verify_status &&
         !password_and_hashing(mysql, auth_plugin))
     {
       my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
                    ER(CR_SSL_CONNECTION_ERROR),
-                   "Certificate verification failure: The certificate is NOT trusted.");
+                   "Certificate verification failure: peer certificate verification failed.");
       return 1;
     }
     goto retry;

This does not change the initial-auth check at line 484 (tls_verify_status > MARIADB_TLS_VERIFY_AUTO), which is intentionally more permissive for hashing plugins on first auth. The auth-switch path is the only one affected.

Originally reported by: Aisle Research to Red Hat via Embargo process. Aisle research should be attributed for the finding. To my knowledge this report was not shared outside of Aisle Research and Red Hat.

Impact

Impact

A man-in-the-middle attacker who can present a valid certificate from any trusted CA (but for the wrong hostname) can capture the client's database password in plaintext. The captured credentials can then be used to authenticate to the legitimate database server, achieving full unauthorized access. This is a credential disclosure that enables subsequent access control bypass.

The attack requires:

  • A MitM position between client and server
  • A valid TLS certificate from a trusted CA (wrong hostname is sufficient)
  • mysql_clear_password available in the client build (default)

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original