✓ProveIt
Examples / Report #3480925 Saved analysis

CVE-2025-15224: libssh key passphrase bypass without agent set

curl · 2025-12-2822.4s analysis · $0.021 in model calls

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

Needs human review

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.

1 · Grounding
grounded
no model · 6 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 1711 tokens · $0.0002
3 · Evidence
5 code snippets
real curl source at 5c0cdd26d663 · 5 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
12130 tokens · 20.1s · $0.0212
5 · Receipts
7 of 8 citations verified
every citation must point at code the judge was shown
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

C1flaw not verifiable

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.

C2location supported

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.

C3trigger supported

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.

C4impact not verifiable

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.

C5reproduction supported

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

Full report on HackerOne ↗

## Summary: libcurl libssh backend `CURLOPT_SSH_AUTH_TYPES` doesn't implement `CURLSSH_AUTH_AGENT` flag correctly. Rather if `CURLSSH_AUTH_PUBLICKEY` is set, the implementation acts as if `CURLSSH_AUTH_AGENT` is always implicitly defined, and thus no key passphrase is required for authentication. As a result if the user has ssh-agent /pageant running, libcurl using application that specifically does not set `CURLSSH_AUTH_AGENT` will still use the ssh-agent / pageant for authentication bypassing the requirement for knowing the key passphrase. ## Affected version 8.17.0 ## Steps To Reproduce: 1. Configure and build libcurl with `--with-libssh` 2. Compile the following proof-of-concept app: ``` #include <curl/curl.h> int main(void) { CURL *curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_URL, "sftp://host.example/"); curl_easy_setopt(curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PUBLICKEY); curl_easy_perform(curl); curl_easy_cleanup(curl); } return 0; } ``` 3. Have the current user have account on `host.example`. Have a passphrase set for the user's ssh key. Have the ssh key pubkey added to the `host.example` users `~/.ssh/authorized_keys`. Have ssh-agent running with the user's key added. 4. Execute the proof of concept application. As a result the application authenticates to the `host.example` without needing the ssh key passphrase. ## Supporting Material/References: * libcurl code calling libssh `ssh_userauth_publickey_auto`: https://github.com/curl/curl/blob/5c0cdd26d66342efaaf2685f2a539be5db21cacd/lib/vssh/libssh.c#L980 * libssh `ssh_userauth_publickey_auto` code talking to ssh-agent: https://github.com/libssh/libssh-mirror/blob/ac6d2fad4a8bf07277127736367e90387646363f/src/auth.c#L1120 ## Impact ## Summary: Bypassing the SSH key passphrase requirement.

Source code

lib/vssh/libssh.clines 955–1005 · lines 980-980 cited by the report
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
998static 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;
include/curl/curl.hlines 854–854 · definition of CURLSSH_AUTH_AGENT
854#define CURLSSH_AUTH_AGENT (1L << 4) /* agent (ssh-agent, pageant...) */
C3: #define CURLSSH_AUTH_AGENT (1L << 4) /* agent (ssh-agent, pageant...) */
include/curl/curl.hlines 850–850 · definition of CURLSSH_AUTH_PUBLICKEY
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
include/curl/curl.h@curl-8_17_0lines 855–855 · definition of CURLSSH_AUTH_AGENT as in release curl-8_17_0; differs from master
Show 1 lines (not cited by the judge)
855#define CURLSSH_AUTH_AGENT (1L<<4) /* agent (ssh-agent, pageant...) */
include/curl/curl.h@curl-8_17_0lines 851–851 · definition of CURLSSH_AUTH_PUBLICKEY as in release curl-8_17_0; differs from master
Show 1 lines (not cited by the judge)
851#define CURLSSH_AUTH_PUBLICKEY (1L<<0) /* public/private key files */