Intra-cluster SASL mechanism allowlist missing in egress connection setup, enabling PLAIN downgrade and cleartext keyfile disclosure

XMLWordPrintableJSON

    • Type: Bug
    • Resolution: Unresolved
    • Priority: Major - P3
    • 8.2.13, 8.0.29, 7.0.40, 8.3.8, 9.0.0-rc2
    • Affects Version/s: None
    • Component/s: None
    • None
    • Server Security
    • ALL
    • v9.0, v8.3, v8.2, v8.0, v7.0
    • Server Security 2026-07-03, Server Security 2026-07-17, Server Security 2026-07-31, Server Security 2026-08-14
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Security Context

      See SECBUG-1100 for problem description, security impact, preconditions, reproduction steps, and severity rationale.

      Root Cause

      • src/mongo/executor/connection_pool_tl.cpp: TLConnectionSetupHook::validateHost copies the saslSupportedMechs array from the unauthenticated hello reply directly into _saslMechsForInternalAuth (line 288-294 on master) without filtering against a client-side allowlist. A forged hello reply advertising only "PLAIN" passes through unmodified.
      • src/mongo/executor/connection_pool_tl.cpp: TLConnection::setup passes the first entry from _saslMechsForInternalAuth straight to authenticateInterna
        l() (line 475-477 on master) with no further validation.
      • src/mongo/client/authenticate.cpp: negotiateSaslMechanism returns the mechanismHint verbatim when it is non-empty (line 225-226 on master) — there is no client-side preference order or mechanism allowlist at this layer.
      • src/mongo/client/internal_auth.cpp: getInternalAuthParams accepts "PLAIN" as a valid mechanism and returns the raw keyfile string as the credential 
        password (line 103-118 on master). PLAIN is not explicitly rejected for internal authentication.
      • The result is that a PLAIN saslStart carrying the raw keyfile in cleartext is sent to the attacker-controlled socket.

      Note: PLAIN is already blocked for speculative SASL start (authenticate.cpp:323), confirming that the intent is to exclude PLAIN from this code path —
       the non-speculative path was left unguarded.

      Proposed Fix

      The minimal fix is in TLConnectionSetupHook::validateHost: filter the peer-supplied saslSupportedMechs array against a hardcoded allowlist of mechanisms that are safe for internal authentication before storing them in _saslMechsForInternalAuth. The allowlist should be:

      • SCRAM-SHA-256
        SCRAM-SHA-1
        MONGODB-X509

      Any mechanism not in this set (including PLAIN, GSSAPI, or unknown values) must be silently discarded. If the filtered list is empty and the reply con
      tained at least one mechanism, treat it as an error.

      // connection_pool_tl.cpp — TLConnectionSetupHook::validateHost
      static constexpr std::array<StringData, 3> kInternalAuthAllowlist = {
          "SCRAM-SHA-256"_sd, "SCRAM-SHA-1"_sd, "MONGODB-X509"_sd};const auto saslMechsElem = reply.getField("saslSupportedMechs");
      if (saslMechsElem.type() == BSONType::array) {
          for (const auto& elem : saslMechsElem.Array()) {
              auto mech = elem.checkAndGetStringData();
              if (std::find(kInternalAuthAllowlist.begin(), kInternalAuthAllowlist.end(), mech) !=
                  kInternalAuthAllowlist.end()) {
                  _saslMechsForInternalAuth.push_back(std::string{mech});
              }
          }
      } 

      As a defence-in-depth second layer, getInternalAuthParams in internal_auth.cpp should also reject PLAIN explicitly by returning boost::none when the m echanism is kSaslPlain, consistent with the existing speculative-auth check in authenticate.cpp:323.

      Acceptance Criteria

      • A forged hello reply advertising only PLAIN does not cause a PLAIN saslStart to be issued on any intra-cluster connection.
      • A forged hello reply containing PLAIN mixed with SCRAM-SHA-256 results in SCRAM-SHA-256 being selected.
      • A hello reply from a legitimate server advertising SCRAM-SHA-256 continues to result in a successful SCRAM-SHA-256 authentication.
      • A hello reply advertising no recognised mechanisms causes the connection setup to fail with a clear error rather than falling back to PLAIN.
      • getInternalAuthParams returns boost::none for mechanism PLAIN.

      Test Coverage

      • Unit test for TLConnectionSetupHook::validateHost: supply a hello reply with saslSupportedMechs: ["PLAIN"] and verify _saslMechsForInternalAuth is empty (or returns an error) after the call.
      • Unit test: supply ["PLAIN", "SCRAM-SHA-256"] and verify only "SCRAM-SHA-256" is stored.
      • Unit test for getInternalAuthParams: call with mechanism "PLAIN" and verify it returns boost::none.
      • Integration test: configure a mock intra-cluster peer that responds with saslSupportedMechs: ["PLAIN"]; assert that the connecting node fails the connection setup and does not emit a saslStart with a PLAIN payload.

      Notes

      The vulnerable copy on master (connection_pool_tl.cpp:288-294):

      const auto saslMechsElem = reply.getField("saslSupportedMechs");
      if (saslMechsElem.type() == BSONType::array) {
          auto array = saslMechsElem.Array();
          for (const auto& elem : array) {
              // No filtering — attacker-controlled value copied verbatim
              _saslMechsForInternalAuth.push_back(std::string{elem.checkAndGetStringData()});
          }
      } 

      The first entry is then passed without further validation (connection_pool_tl.cpp: 475-476):

      if (!helloHook->saslMechsForInternalAuth().empty())
          mechanism = helloHook->saslMechsForInternalAuth().front();
      return _client->authenticateInternal(std::move(mechanism), authParametersProvider); 

      PLAIN is already blocked for speculative SASL (authenticate.cpp:322-323) confirming the intent to exclude it:

      if (credential.mechanism == AuthMechanism::kSaslPlain) {
          return {ErrorCodes::BadValue, "PLAIN mechanism not supported with speculativeSaslStart"};
      } 

            Assignee:
            Ken Martin
            Reporter:
            Chye Lin Chee
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: