HexBrain

curl · Report #3650435

Improper enforcement of CURLOPT_SOCKS5_AUTH due to missing reuse key validation in libcurl

Informativestatus
lowseverity
15upvotes
7 Apr 2026disclosed

How it was found

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

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original