curl · Report #3650435
Improper enforcement of CURLOPT_SOCKS5_AUTH due to missing reuse key validation in libcurl
How it was found
detail:
-
lib/setopt.c:1048-1051CURLOPT_SOCKS5_AUTHis stored intodata->set.socks5auth
-
lib/socks.c:597-641(socks5_req0_init)- fresh SOCKS5 handshake reads
data->set.socks5auth, if BASIC is not allowed, it clearssx->proxy_userat 618-620, so username/password auth is not even offered
- fresh SOCKS5 handshake reads
-
lib/socks.c:665-688(socks5_check_resp0)- on a fresh connection, if the proxy selects BASIC while BASIC is disabled, or if no acceptable method exists, the connection fails
-
lib/urldata.h:345-351- struct
proxy_infostores host / port / proxytype / user / passwd, but no SOCKS5 auth-method policy
- struct
-
lib/url.c:578-590(proxy_info_matches)- reuse match compares only proxy type, port, host, proxy user, and proxy password
-
lib/url.c:879-913(url_match_proxy_use)- SOCKS proxy reuse uses
proxy_info_matches()
- SOCKS proxy reuse uses
-
lib/url.c:1207-1229(url_match_conn)- connection reuse is accepted if that proxy match succeeds
-
lib/url.c:2965-2998(url_conn_reuse_adjust)- on reuse, curl may refresh proxy credentials, but it does not compare or refresh the SOCKS5 auth-method policy
so the invariant break is:
fresh SOCKS5 connections enforce CURLOPT_SOCKS5_AUTH, but connection reuse does not include that policy in the reuse key
docs:
this is not an API misuse case
changing options between transfers on the same easy handle is a documented libcurl usage pattern, and connection reuse is the default behavior unless the application forces a fresh connection,
if curl wanted CURLOPT_SOCKS5_AUTH to be “new connection only,” its docs should say so the same way they do for CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, they do not, that makes silent reuse under a stricter later auth policy a bug, not intended behavior
( ref: https://curl.se/libcurl/c/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.html, https://curl.se/libcurl/c/curl_easy_setopt.html , https://curl.se/libcurl/c/CURLOPT_SOCKS5_AUTH.html , https://curl.se/libcurl/c/CURLOPT_FRESH_CONNECT.html )
exploit:
-
create a normal easy handle
-
set:
CURLOPT_URL = http://127.0.0.1:18081/CURLOPT_PROXY = socks5://127.0.0.1:19081CURLOPT_PROXYUSERPWD = "user:pass"CURLOPT_SOCKS5_AUTH = CURLAUTH_BASIC
-
perform transfer #1
- the SOCKS5 proxy requires username/password, so the SOCKS handshake succeeds and the connection stays alive for reuse
-
on the same easy handle, change only:
CURLOPT_SOCKS5_AUTH = CURLAUTH_NONE
-
perform transfer #2 to the same proxy/destination
- libcurl reuses the existing connection because the reuse key ignores the SOCKS5 auth-method policy
-
no new SOCKS5 handshake happens, so the request succeeds over the already-authenticated tunnel
-
do the same “NONE only” transfer on a fresh easy handle
- it fails with
CURLE_PROXY/ error 97, becauseCURLAUTH_NONEmeans only no-auth is allowed and the proxy requires username/password
- it fails with
poc:
i ran a local setup:
- SOCKS5 proxy on 127.0.0.1:19081 that requires username/password
- HTTP origin on 127.0.0.1:18081
- C poc using the uploaded libcurl build
what happened:
-
transfer #1 on one easy handle with
CURLOPT_PROXY = socks5://127.0.0.1:19081CURLOPT_PROXYUSERPWD = "user:pass"CURLOPT_SOCKS5_AUTH = CURLAUTH_BASIC- result: success
-
transfer #2 on the same easy handle, changing only
CURLOPT_SOCKS5_AUTH = CURLAUTH_NONE- result: success
- log: reusing existing http: connection with proxy 127.0.0.1
-
transfer #3 on a fresh easy handle with
CURLOPT_SOCKS5_AUTH = CURLAUTH_NONE- result: failure
- error: 97 (proxy handshake error)
- log: no authentication method was acceptable
server-side logs:
- first connection offered methods
[0, 2]and completed username/password auth - second transfer reused the same SOCKS connection and sent the HTTP request with no new SOCKS handshake
- fresh third attempt offered only method
[0]and the proxy rejected it
Impact
impacts:
- SOCKS5 proxy-auth policy bypass
- a transfer that is explicitly configured with
CURLOPT_SOCKS5_AUTH = CURLAUTH_NONEcan succeed when it should fail - the second transfer silently continues over an already-authenticated SOCKS5 tunnel created under a less restrictive earlier policy
- applications can believe they enforced “no proxy authentication” for a later transfer, while libcurl actually keeps using a previously BASIC-authenticated proxy connection