Improper enforcement of CURLOPT_SOCKS5_AUTH due to missing reuse key validation in libcurl
Low
Vulnerability Details
# detail:
- `lib/setopt.c:1048-1051`
- `CURLOPT_SOCKS5_AUTH` is stored into `data->set.socks5auth`
- `lib/socks.c:597-641` (`socks5_req0_init`)
- fresh SOCKS5 handshake reads `data->set.socks5auth`, if BASIC is not allowed, it clears `sx->proxy_user` at 618-620, so username/password auth is not even offered
- `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_info` stores host / port / proxytype / user / passwd, but no SOCKS5 auth-method policy
- `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()`
- `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:
1. create a normal easy handle
2. set:
- `CURLOPT_URL = http://127.0.0.1:18081/`
- `CURLOPT_PROXY = socks5://127.0.0.1:19081`
- `CURLOPT_PROXYUSERPWD = "user:pass"`
- `CURLOPT_SOCKS5_AUTH = CURLAUTH_BASIC`
3. perform transfer #1
- the SOCKS5 proxy requires username/password, so the SOCKS handshake succeeds and the connection stays alive for reuse
4. on the same easy handle, change only:
- `CURLOPT_SOCKS5_AUTH = CURLAUTH_NONE`
5. perform transfer #2 to the same proxy/destination
- libcurl reuses the existing connection because the reuse key ignores the SOCKS5 auth-method policy
6. no new SOCKS5 handshake happens, so the request succeeds over the already-authenticated tunnel
7. do the same “NONE only” transfer on a fresh easy handle
- it fails with `CURLE_PROXY` / error 97, because `CURLAUTH_NONE` means only no-auth is allowed and the proxy requires username/password
# 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:**
1. transfer #1 on one easy handle with
- `CURLOPT_PROXY = socks5://127.0.0.1:19081`
- `CURLOPT_PROXYUSERPWD = "user:pass"`
- `CURLOPT_SOCKS5_AUTH = CURLAUTH_BASIC`
- result: success
2. 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
3. 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_NONE` can 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
Actions
View on HackerOneReport Stats
- Report ID: 3650435
- State: Closed
- Substate: informative