Moderate
CVE-2023-25950
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
CVE-2023-25950
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
Description
HTTP request/response smuggling vulnerability in HAProxy version 2.7.0, and 2.6.1 to 2.6.7 allows a remote attacker to alter a legitimate user’s request. As a result, the attacker may obtain sensitive information or cause a denial-of-service (DoS) condition.
Add Assessment
Ratings
-
Attacker ValueMedium
-
ExploitabilityLow
Technical Analysis
Table of contents
Vulnerability Description
HAProxy’s HTTP/3 implementation fails to block a malformed HTTP header field name, and when deployed in front of a server that incorrectly process this malformed header, it may be used to conduct an HTTP request/response smuggling attack. A remote attacker may alter a legitimate user’s request. As a result, the attacker may obtain sensitive information or cause a denial-of-service (DoS) condition.
https://jvn.jp/en/jp/JVN38170084/
Source Code Review
A very good approach before starting any research on CVEs is to begin by reading the vulnerability description and then check if the commit(s) for patching are available.
In my case, the commit was available, and it is evident that the developers of HAProxy forgot to include the RFC 9114 4.1.2. Malformed Requests and Responses checks during the parsing of the standard headers on HTTP3 implementation.
Refer to the patch commit below.
--- a/src/h3.c +++ b/src/h3.c @@ -352,7 +352,27 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf, //struct ist scheme = IST_NULL, authority = IST_NULL; struct ist authority = IST_NULL; int hdr_idx, ret; - int cookie = -1, last_cookie = -1; + int cookie = -1, last_cookie = -1, i; + + /* RFC 9114 4.1.2. Malformed Requests and Responses + * + * A malformed request or response is one that is an otherwise valid + * sequence of frames but is invalid due to: + * - the presence of prohibited fields or pseudo-header fields, + * - the absence of mandatory pseudo-header fields, + * - invalid values for pseudo-header fields, + * - pseudo-header fields after fields, + * - an invalid sequence of HTTP messages, + * - the inclusion of uppercase field names, or + * - the inclusion of invalid characters in field names or values. + * + * [...] + * + * Intermediaries that process HTTP requests or responses (i.e., any + * intermediary not acting as a tunnel) MUST NOT forward a malformed + * request or response. Malformed requests or responses that are + * detected MUST be treated as a stream error of type H3_MESSAGE_ERROR. + */ TRACE_ENTER(H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs); @@ -416,6 +436,14 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf, if (isteq(list[hdr_idx].n, ist(""))) break; + for (i = 0; i < list[hdr_idx].n.len; ++i) { + const char c = list[hdr_idx].n.ptr[i]; + if ((uint8_t)(c - 'A') < 'Z' - 'A' || !HTTP_IS_TOKEN(c)) { + TRACE_ERROR("invalid characters in field name", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs); + return -1; + } + } + if (isteq(list[hdr_idx].n, ist("cookie"))) { http_cookie_register(list, hdr_idx, &cookie, &last_cookie); continue;
Repositories – haproxy-2.7.git/commit
Below, you can find the code that demonstrates the absence of header name sanitization in the implementation of handling standard headers on HAProxy 2.7.0.
/* src/h3.c lines: 413 - 428 */ /* now treat standard headers */ hdr_idx = 0; while (1) { if (isteq(list[hdr_idx].n, ist(""))) break; if (isteq(list[hdr_idx].n, ist("cookie"))) { http_cookie_register(list, hdr_idx, & cookie, & last_cookie); continue; } if (!istmatch(list[hdr_idx].n, ist(":"))) htx_add_header(htx, list[hdr_idx].n, list[hdr_idx].v); ++hdr_idx; }
Below, you can find the code that is used to check if header name is valid.
The code starts with a for loop that iterates through each character of a header field name. The loop runs from i = 0
to i < list[hdr_idx0.n.len]
, where list
is an array or structure containing header information, and hdr_idx
is an index representing the spicific header being checked.
Inside the loop , the code extracts the current character c
from the header field name.
list[hdr_idx].n.ptr[i]
accesses the character at the i
-th position in the header field’s name.
The next part of the code contains an if
statement. It checks whether the current character c
satisfies one of two conditions:
(uint8_t)(c - 'A') < 'Z' - 'A'
: This checks if the character is an uppercase letter (A to Z) by subtracting ‘A’ fromc
and casting the result touint8_t
If the result is less than the difference between ‘Z’ and ‘A’, then the character is an uppercase letter.
!HTTP_IS_TOKEN(c)
: This condition checks if the character is a valid HTTP token character. TheHTTP_IS_TOKEN
works by first checking if the header name contains any tokens. A token is a sequence of characters that is not a reserved character in the HTTP protocol. Reserved characters are characters that have special meaning in the HTTP protocol, e.g.:
,/
,?
, and#
.
/* src/h3.c lines: 439 - 445 */ for (i = 0; i < list[hdr_idx].n.len; ++i) { const char c = list[hdr_idx].n.ptr[i]; if ((uint8_t)(c - 'A') < 'Z' - 'A' || !HTTP_IS_TOKEN(c)) { TRACE_ERROR("invalid characters in field name", H3_EV_RX_FRAME | H3_EV_RX_HDR, qcs -> qcc -> conn, qcs); return -1; } }
Lab Setup
The entire lab is running on Docker. You can run the lab with the following commands:
cd /lab
docker-compose up --build
Please note that the Docker build will take 15-20 minutes to finish.
However, before running the lab, you have to make some configuration changes:
/lab/haproxy/conf/haproxy.cfg
... default_backend api_server backend api_server balance roundrobin server api_server [YOUR-LOCAL-IPv4]:8080 # replace with local IPv4
/etc/hosts
[YOUR-LOCAL-IPv4] foo.com
/lab/docker-compose.yml
You can choose between the vulnerable version and the patched version by changing the argument’s value to ‘vuln’ or ‘patched’ to conduct your test on it.
... args: - haproxy_version=patched || vuln ...
and finally import the minica.crt certificate in your browser.
⚠️ Please run the lab in a Linux environment.
Identifying the issue
Sending the following curl request:
curl --http3 -H "foooooo\r\n: barr" -iL -k https://192.168.1.104/
Vulnerable version response:
HTTP/3 200 server: Werkzeug/2.3.6 Python/3.8.17 date: Sat, 12 Aug 2023 13:10:52 GMT content-type: text/html; charset=utf-8 content-length: 76 alt-svc: h3=":443";ma=900; Host: 192.168.1.104 User-Agent: curl/8.1.2-DEV Accept: */* Foooooo\R\N: barr <-- Malformed header
Patched version response:
curl: (56) HTTP/3 stream 0 reset by server
Based on the above findings, the corresponding responses indicate that the vulnerable version of HAProxy allowed the \r\n prefix to pass through to the backend server, whereas the patched version dropped the connection between the client and HAProxy.
An attacker can conduct an HTTP Request Smuggling attack based on backend behavior and how the backend server will treat the malformed header. In my view, the most significant concern is that an attacker could exploit the aforementioned CVE to carry out a Denial of Service (DoS) attack.
References:
https://jvn.jp/en/jp/JVN38170084/
https://github.com/haproxytechblog/haproxy-2.6-http3
https://www.haproxy.com/blog/how-to-enable-quic-load-balancing-on-haproxy
https://git.haproxy.org/
https://github.com/jsha/minica
https://curl.se/docs/http3.html
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportCVSS V3 Severity and Metrics
General Information
Vendors
- haproxy
Products
- haproxy,
- haproxy 2.7.0
References
Exploit
A PoC added here by the AKB Worker must have at least 2 GitHub stars.
Additional Info
Technical Analysis
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
@dhmosfunk this is a great write-up, thank you!