curl · Report #3639277
Bypassing Strict SSH Server Verification via Connection Pool Reuse in libcurl
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
- Handle A (Relaxed): A routine connects to
sftp://example.com. It does not enforce aknown_hostsfile. An attacker performs a Man-in-the-Middle (MITM) attack; the connection is established and added to the pool. - Handle B (Strict): A separate routine handles sensitive data and connects to the same host. It explicitly sets
CURLOPT_SSH_KNOWNHOSTSto a trusted file to prevent MITM. - The Bypass:
ssh_config_matches()evaluates the connection from Handle A. Since the client credentials match, it reuses the connection. - 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_KNOWNHOSTSSTRING_SSH_HOST_PUBLIC_KEY_MD5STRING_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.