CVE-2025-15224: libssh key passphrase bypass without agent set
What the report claims
libcurl's libssh backend incorrectly treats CURLSSH_AUTH_PUBLICKEY as implicitly including CURLSSH_AUTH_AGENT, causing SSH key passphrase bypass when ssh-agent is present.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2025-15224 libssh key passphrase bypass without agent set
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The available evidence does not settle the report. A maintainer should investigate.
Why this finding?
- The call to ssh_userauth_publickey_auto at lib/vssh/libssh.c:980 is confirmed, and external libssh documentation confirms this function may use ssh-agent.
- However, the critical logic that decides whether to call this function based on the CURLSSH_AUTH_AGENT flag (i.e., the code that reads CURLOPT_SSH_AUTH_TYPES and selects the authentication method) is not present in the provided source snippet (lines 955-1005 only show one authentication function, not the dispatcher).
- Without seeing how the auth types bitmask controls the code path, it cannot be determined whether the flag is correctly implemented or whether the agent is used when it should not be.
- The claim is plausible because the function that uses agent is called in at least one code path, but the guarding condition is missing from evidence.
What still needs checking
- The function that processes CURLOPT_SSH_AUTH_TYPES and dispatches to the appropriate authentication method (likely the caller of myssh_in_AUTH_PKEY or similar).
- Code showing how sshc->pubkey is populated and whether that depends on the auth types flags.
- Documentation for CURLOPT_SSH_AUTH_TYPES describing the expected interaction between PUBLICKEY and AGENT flags.
How this analysis ran 5 claims · 7 citations verified
Checked against curl permalink 5c0cdd26d663 (5c0cdd26d663). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thank you for the report. The call to ssh_userauth_publickey_auto at lib/vssh/libssh.c:980 is confirmed, and libssh's documentation confirms this function may use ssh-agent. However, the provided code snippet does not include the logic that reads CURLOPT_SSH_AUTH_TYPES and decides which authentication function to call. Could you point to the function that handles the auth types bitmask and selects between publickey-file auth vs agent auth? That code would determine whether CURLSSH_AUTH_AGENT is correctly respected when only CURLSSH_AUTH_PUBLICKEY is set.
Inspect all claims and source code 5 claims · 5 code excerpts
Report claims
CURLOPT_SSH_AUTH_TYPES doesn't implement CURLSSH_AUTH_AGENT flag correctly
Read the reasoning
The claim that CURLSSH_AUTH_AGENT flag is not implemented correctly depends on code that reads the auth types bitmask and chooses which libssh function to call. The provided snippet shows the call to ssh_userauth_publickey_auto at line 980, but does not show the surrounding logic that decides whether to take this path based on the CURLSSH_AUTH_AGENT flag. Without that guarding code, the claim cannot be verified or contradicted from the provided source.
curl code calls libssh ssh_userauth_publickey_auto
Read the reasoning
The provided code at line 980 explicitly calls ssh_userauth_publickey_auto, confirming this claim.
application sets CURLOPT_SSH_AUTH_TYPES to CURLSSH_AUTH_PUBLICKEY without setting CURLSSH_AUTH_AGENT
Read the reasoning
The flag definitions exist as described. The trigger condition (setting PUBLICKEY without AGENT) is a valid API usage pattern per the defined constants.
authentication bypasses requirement for knowing the key passphrase
Read the reasoning
While ssh_userauth_publickey_auto is documented to potentially use ssh-agent (per external sources), whether this constitutes a 'bypass' of the passphrase requirement depends on whether the curl code should have called this function when CURLSSH_AUTH_AGENT is not set. That guarding logic is not in the provided code, so the impact claim cannot be decided.
application performs curl_easy_perform after setting CURLOPT_SSH_AUTH_TYPES to CURLSSH_AUTH_PUBLICKEY with ssh-agent running
Read the reasoning
The reproduction steps use valid API constants and the code path containing the cited function call exists. Whether it reproduces the claimed behavior depends on the unverified guarding logic.
What the report says about other projects
confirmed ssh_userauth_publickey_auto talks to ssh-agent
“ssh_userauth_publickey_auto: Tries to automatically authenticate with public key and 'none'.” source
confirmed ssh_userauth_publickey_auto implements publickey authentication flow including agent
“ssh_userauth_publickey_auto: Tries to automatically authenticate with public key and 'none'.” source
Read the original report
Source code
| 955 | if(sshc->pubkey && !data->set.ssl.key_passwd) { |
| 956 | rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey); |
| 957 | if(rc == SSH_AUTH_AGAIN) |
| 958 | return SSH_AGAIN; |
| 959 | |
| 960 | if(rc != SSH_OK) { |
| 961 | rc = myssh_to_GSSAPI_AUTH(data, sshc); |
| 962 | return rc; |
| 963 | } |
| 964 | } |
| 965 | |
| 966 | rc = ssh_pki_import_privkey_file(data-> |
| 967 | set.str[STRING_SSH_PRIVATE_KEY], |
| 968 | data->set.ssl.key_passwd, NULL, |
| 969 | NULL, &sshc->privkey); |
| 970 | if(rc != SSH_OK) { |
| 971 | failf(data, "Could not load private key file %s", |
| 972 | data->set.str[STRING_SSH_PRIVATE_KEY]); |
| 973 | rc = myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED); |
| 974 | return rc; |
| 975 | } |
| 976 | |
| 977 | myssh_to(data, sshc, SSH_AUTH_PKEY); |
| 978 | } |
| 979 | else { |
| 980 | rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, |
| C1: Call to ssh_userauth_publickey_auto exists, but the guarding logic that checks CURLOPT_SSH_AUTH_TYPES for CURLSSH_AUTH_AGENT before this call is not in the provided code snippet (lines 955-1005 only). | |
| C2: rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, data->set.ssl.key_passwd); | |
| C4: ssh_userauth_publickey_auto is called with data->set.ssl.key_passwd | |
| C5: ssh_userauth_publickey_auto called in code path | |
| 981 | data->set.ssl.key_passwd); |
| 982 | if(rc == SSH_AUTH_AGAIN) |
| 983 | return SSH_AGAIN; |
| 984 | |
| 985 | if(rc == SSH_AUTH_SUCCESS) { |
| 986 | rc = SSH_OK; |
| 987 | sshc->authed = TRUE; |
| 988 | infof(data, "Completed public key authentication"); |
| 989 | myssh_to(data, sshc, SSH_AUTH_DONE); |
| 990 | return rc; |
| 991 | } |
| 992 | |
| 993 | rc = myssh_to_GSSAPI_AUTH(data, sshc); |
| 994 | } |
| 995 | return rc; |
| 996 | } |
| 997 | |
| 998 | static int myssh_in_AUTH_PKEY(struct Curl_easy *data, |
| 999 | struct ssh_conn *sshc) |
| 1000 | { |
| 1001 | int rc = ssh_userauth_publickey(sshc->ssh_session, NULL, sshc->privkey); |
| 1002 | if(rc == SSH_AUTH_AGAIN) |
| 1003 | return SSH_AGAIN; |
| 1004 | else if(rc == SSH_AUTH_SUCCESS) { |
| 1005 | sshc->authed = TRUE; |
| 854 | #define CURLSSH_AUTH_AGENT (1L << 4) /* agent (ssh-agent, pageant...) */ |
| C3: #define CURLSSH_AUTH_AGENT (1L << 4) /* agent (ssh-agent, pageant...) */ |
| 850 | #define CURLSSH_AUTH_PUBLICKEY (1L << 0) /* public/private key files */ |
| C3: #define CURLSSH_AUTH_PUBLICKEY (1L << 0) /* public/private key files */ | |
| C5: CURLSSH_AUTH_PUBLICKEY defined |
Show 1 lines (not cited by the judge)
| 855 | #define CURLSSH_AUTH_AGENT (1L<<4) /* agent (ssh-agent, pageant...) */ |
Show 1 lines (not cited by the judge)
| 851 | #define CURLSSH_AUTH_PUBLICKEY (1L<<0) /* public/private key files */ |