This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch fix/002-r2dbc-mysql-ssl-downgrade
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git

commit cebb7c150272ac7167037248372ff74a7a0d5ccf
Author: liuhy <[email protected]>
AuthorDate: Tue Jul 28 07:23:14 2026 -0700

    [fix] stop silent SSL-to-plaintext downgrade in R2DBC MySQL route
    
    The R2DBC MySQL query route retried a failed connection with
    SslMode.DISABLED whenever the first (PREFERRED) attempt errored with a
    handshake-style failure (handshake_failure, ssl/tls handshake,
    ClosedChannelException, connection unexpectedly closed).
    
    A TLS handshake failure is ambiguous: it can be induced by a MitM that
    disrupts the handshake specifically to trigger this fallback. Retrying
    with SSL disabled then authenticates the monitoring user to the
    (possibly tampered) endpoint over plaintext, exposing the credentials.
    
    SslMode.PREFERRED already handles the legitimate plaintext case: when
    the server advertises no SSL capability in the initial handshake, the
    driver falls back to plaintext through protocol negotiation, without
    error. So the explicit DISABLED retry only ever fires on errors, which
    is exactly the unsafe path. Drop it and surface the failure instead;
    the caching_sha2_password / must-require-ssl diagnostic is preserved.
    
    Co-Authored-By: Claude <[email protected]>
---
 .../mysql/r2dbc/MysqlR2dbcQueryExecutor.java       | 33 ++++++++--------------
 1 file changed, 12 insertions(+), 21 deletions(-)

diff --git 
a/hertzbeat-collector/hertzbeat-collector-mysql-r2dbc/src/main/java/org/apache/hertzbeat/collector/mysql/r2dbc/MysqlR2dbcQueryExecutor.java
 
b/hertzbeat-collector/hertzbeat-collector-mysql-r2dbc/src/main/java/org/apache/hertzbeat/collector/mysql/r2dbc/MysqlR2dbcQueryExecutor.java
index d8a5e9698e..41f5ed36a7 100644
--- 
a/hertzbeat-collector/hertzbeat-collector-mysql-r2dbc/src/main/java/org/apache/hertzbeat/collector/mysql/r2dbc/MysqlR2dbcQueryExecutor.java
+++ 
b/hertzbeat-collector/hertzbeat-collector-mysql-r2dbc/src/main/java/org/apache/hertzbeat/collector/mysql/r2dbc/MysqlR2dbcQueryExecutor.java
@@ -46,21 +46,23 @@ public class MysqlR2dbcQueryExecutor implements 
MysqlQueryExecutor {
     @Override
     public QueryResult execute(String sql, QueryOptions options) {
         String normalizedSql = sqlGuard.normalizeAndValidate(sql);
-        QueryResult firstAttempt = executeOnce(normalizedSql, options, 
SslMode.PREFERRED);
-        if (!firstAttempt.hasError() || 
!shouldRetryWithoutSsl(firstAttempt.getError())) {
-            return firstAttempt;
-        }
-        QueryResult fallbackAttempt = executeOnce(normalizedSql, options, 
SslMode.DISABLED);
-        if (!fallbackAttempt.hasError()) {
-            return fallbackAttempt;
+        // A single PREFERRED attempt negotiates TLS when the server supports 
it and
+        // falls back to plaintext only through the protocol-level handshake 
when the
+        // server advertises no SSL capability. It must NOT be followed by a 
second
+        // SslMode.DISABLED attempt on handshake *failures*: a handshake 
failure is
+        // ambiguous (a MitM can induce it to force a downgrade), and retrying 
with
+        // SSL disabled would then ship the monitoring credentials in the 
clear.
+        QueryResult result = executeOnce(normalizedSql, options, 
SslMode.PREFERRED);
+        if (!result.hasError()) {
+            return result;
         }
-        if (requiresSslCompatibleAuth(fallbackAttempt.getError())) {
+        if (requiresSslCompatibleAuth(result.getError())) {
             return QueryResult.builder()
-                    .error(fallbackAttempt.getError()
+                    .error(result.getError()
                             + ". This route currently needs a TLS-compatible 
runtime or a mysql_native_password monitoring user.")
                     .build();
         }
-        return fallbackAttempt;
+        return result;
     }
 
     private QueryResult executeOnce(String sql, QueryOptions options, SslMode 
sslMode) {
@@ -128,17 +130,6 @@ public class MysqlR2dbcQueryExecutor implements 
MysqlQueryExecutor {
         return false;
     }
 
-    private boolean shouldRetryWithoutSsl(String error) {
-        if (error == null) {
-            return false;
-        }
-        String normalized = error.toLowerCase(Locale.ROOT);
-        return normalized.contains("handshake_failure")
-                || normalized.contains("ssl/tls handshake")
-                || normalized.contains("closedchannelexception")
-                || normalized.contains("connection unexpectedly closed");
-    }
-
     private boolean requiresSslCompatibleAuth(String error) {
         if (error == null) {
             return false;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to