Activity Feed
- Government or Industry Alert (https://www.cisa.gov/news-events/alerts/2024/10/08/cisa-adds-three-known-exploited-vulnerabilities-catalog)
- Government or Industry Alert (https://www.cisa.gov/news-events/alerts/2024/10/09/cisa-adds-three-known-exploited-vulnerabilities-catalog)
- Government or Industry Alert (https://www.cisa.gov/news-events/alerts/2024/10/09/cisa-adds-three-known-exploited-vulnerabilities-catalog)
- Government or Industry Alert (https://www.cisa.gov/news-events/alerts/2024/10/09/cisa-adds-three-known-exploited-vulnerabilities-catalog)
- Government or Industry Alert (https://www.cisa.gov/news-events/alerts/2024/10/03/cisa-adds-one-known-exploited-vulnerability-catalog)
Technical Analysis
This is one of a list of vulnerabilities disclosed in Synacor’s Zimbra Collaboration Suite recently — this particular issue lies in Zimbra’s postjournal service and evidently allows for unauthenticated command execution. Multiple sources are reporting either attempted or successful exploitation along with insights on post-exploitation behavior.
One of the technical staff on Zimbra commented to HelpNetSecurity that the postjournal service “may be optional or not enabled on most systems,” which probably means a lower exploitable target population. Zimbra has historically been a target for both APT and commodity attackers, so for orgs that run this software, it’s a good idea to patch up (and/or verify the vulnerable service isn’t enabled).
Scoring this as a Medium for attacker value as of now since 1) attackers like Zimbra and are into whatever lets ‘em read emails (particularly from gov servers!); and 2) this config doesn’t seem to be the default, and some of the public write-ups do mention misses on getting exploits working.
More references:
- Zimbra advisory page
- Root cause analysis and PoC (Project Discovery)
- Additional context (Bleeping Computer)
Other Zimbra CVE analysis in AttackerKB:
- Other: Social media reports of exploitation and post-exploitation behavior (https://x.com/threatinsight/status/1841089939905134793)
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
@dhmosfunk this is a great write-up, thank you!