HexBrain

curl · Report #3973158

41: `main_checkfds()` pipe reuse leaks proxy credentials into HTTPS upload body

Informativestatus
12upvotes
31 Aug 2026disclosed

How it was found

Summary

When curl is launched with stdin (fd 0) and stderr (fd 2) closed, main_checkfds() in src/tool_main.c can assign opposite ends of the same pipe to fd 0 and fd 2. In verbose mode with a stdin-based upload through an authenticated proxy, the Proxy-Authorization header written to stderr becomes readable from stdin and is sent as upload body data to the HTTPS origin server.

Affected version

curl CLI 7.12.3 through 8.21.0, plus tested 8.22.0 prerelease/development versions on applicable POSIX systems.

Root cause

main_checkfds() (lines 87–95 of src/tool_main.c) loops pipe() until fds 0, 1, and 2 are all open. If fd 0 and fd 2 are both closed while fd 1 is open, the first pipe() call allocates its read end as fd 0 and its write end as fd 2 — opposite ends of the same pipe.

tool_init_stderr() runs before main_checkfds() (line 148 vs 169), binding tool_stderr to the C stderr stream backed by fd 2. In verbose mode, curl writes the outgoing CONNECT request — including Proxy-Authorization — to tool_stderr → fd 2 → pipe write end. An upload from --upload-file . or --upload-file - reads from per->infd = STDIN_FILENO (fd 0) → pipe read end. The proxy credential thus crosses into the upload body sent to the HTTPS origin.

Reproduction

  1. Build an affected curl command-line tool on a POSIX platform that provides pipe() and fcntl().
  2. Start an HTTP CONNECT proxy that requires Basic authentication, records the incoming proxy request, and tunnels accepted connections to a separate HTTPS origin.
  3. Start the HTTPS origin with a test certificate and have it record the complete decrypted upload request, including the request body.
  4. Prepare a launcher that keeps stdout open but closes file descriptors 0 and 2 immediately before executing curl.
  5. Invoke curl through the authenticated proxy with verbose diagnostics enabled and configure an upload whose source is stdin, using either --upload-file - or the nonblocking stdin form --upload-file ..
  6. Direct the upload to the distinct HTTPS origin and allow the test certificate as appropriate for the isolated environment.
  7. Confirm that the proxy receives the expected Proxy-Authorization header during the CONNECT request.
  8. Inspect the origin's decrypted request and locate the boundary between its HTTP headers and upload body.
  9. Observe the vulnerable result: the upload body contains verbose diagnostic text, including the proxy's Proxy-Authorization header, even though the origin is not authorized to receive that credential.
  10. Confirm the descriptor arrangement in the launched process: repaired fd 0 is the read end and repaired fd 2 is the write end of the same pipe.
  11. Repeat the request while leaving stdin open. Confirm that the origin no longer receives the proxy credential in the body.
  12. Repeat while leaving stderr open. Confirm that the credential is not inserted into the upload.
  13. Repeat with verbose mode disabled. Confirm that no credential-bearing diagnostic text enters the upload.
  14. Repeat with verbose diagnostics redirected using --stderr to a dedicated file. Confirm that the origin does not receive the credential.
  15. Confirm a fix by independently attaching each missing standard descriptor to /dev/null, for example with open() and dup2(), instead of retaining connected pipe endpoints.
  16. Repeat the original closed-stdin-and-stderr case and verify that proxy authentication still reaches the proxy but no diagnostic bytes or credentials appear in the HTTPS upload body.

Expected result

Repaired standard descriptors must be independent. Data written to stderr must never become readable as stdin or be incorporated into an upload.

Vulnerable result

When stdin and stderr are both closed, curl can retain opposite ends of one pipe as descriptors 0 and 2. Verbose proxy diagnostics written to stderr are then read as stdin and transmitted to the origin as upload data.

Impact

The proxy credential crosses a principal boundary: the proxy is authorized to receive it, the HTTPS origin is not. TLS does not prevent the disclosure because curl injects the credential into plaintext upload data before encryption.

Practical severity is limited — exploitation requires an unusual local launch configuration (closed fd 0 + fd 2, verbose mode, stdin upload, authenticated proxy, distinct origin). A remote origin cannot independently establish these prerequisites.

Suggested fix

Replace the pipe() loop in main_checkfds() with open("/dev/null", O_RDWR) and dup2() for each missing standard descriptor. This ensures repaired fds are independent sinks/sources rather than connected pipe endpoints.

Impact

Summary:

Under the required closed-descriptor launch configuration, an HTTPS origin can receive credentials intended only for an authenticated proxy because curl inserts verbose diagnostic output into the upload body. This directly discloses reusable proxy credentials to another principal, although a remote origin generally cannot create the unusual local descriptor and command-line prerequisites by itself.

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