Fabio allows HTTP clients to manipulate custom headers it adds
Description
Fabio is an HTTP(S) and TCP router for deploying applications managed by consul. Prior to version 1.6.6, Fabio allows clients to remove X-Forwarded headers (except X-Forwarded-For) due to a vulnerability in how it processes hop-by-hop headers. Fabio adds HTTP headers like X-Forwarded-Host and X-Forwarded-Port when routing requests to backend applications. Since the receiving application should trust these headers, allowing HTTP clients to remove or modify them creates potential security vulnerabilities. Some of these custom headers can be removed and, in certain cases, manipulated. The attack relies on the behavior that headers can be defined as hop-by-hop via the HTTP Connection header. This issue has been patched in version 1.6.6.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Fabio before 1.6.6 allows clients to strip or manipulate trusted X-Forwarded headers by abusing the HTTP Connection header, leading to security bypass.
Vulnerability
Overview
Fabio is an HTTP(S) and TCP router for Consul-managed applications. Prior to version 1.6.6, it mishandles hop-by-hop headers defined via the HTTP Connection header. An attacker can include headers such as X-Forwarded-Host or X-Forwarded-Port in the Connection list, causing Fabio to remove them from the proxied request. This occurs because Fabio did not protect these custom headers from being stripped when processing the Connection header [1][2].
Exploitation
The attack requires only the ability to send HTTP requests to Fabio (no prior authentication). By sending a request with a Connection header listing one or more of the protected headers (e.g., Connection: close, X-Forwarded-Host), the client causes Fabio to delete those headers before forwarding to the backend. The following headers are removable: X-Forwarded-Host, X-Forwarded-Port, X-Forwarded-Proto, X-Real-Ip, and Forwarded [2]. Notably, X-Forwarded-For is not removable, but its presence does not mitigate the removal of other security-relevant headers.
Impact
Backend applications typically trust these forwarded headers for decision-making (e.g., constructing redirect URLs, enforcing access controls, or logging client IPs). By removing or manipulating them, an attacker could bypass security checks, redirect traffic, or spoof the perceived client identity or protocol. For example, removing X-Forwarded-Proto could trick a backend into believing the request arrived over plain HTTP when it came via HTTPS, potentially bypassing TLS enforcement [1][2].
Mitigation
The issue is fixed in Fabio version 1.6.6. The patch introduces a protectHeaders map that prevents these critical headers from being removed via the Connection mechanism [4]. Users should upgrade immediately. No workarounds are documented, but restricting client access to the Fabio proxy (e.g., via network segmentation) can reduce exposure until patching is possible.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/fabiolb/fabioGo | < 1.6.6 | 1.6.6 |
Affected products
2- fabiolb/fabiov5Range: < 1.6.6
Patches
1fdaf1e966162Merge commit from fork
3 files changed · +57 −0
proxy/http_headers.go+27 −0 modified@@ -5,6 +5,7 @@ import ( "errors" "net" "net/http" + "net/textproto" "strings" "github.com/fabiolb/fabio/config" @@ -26,6 +27,16 @@ func addResponseHeaders(w http.ResponseWriter, r *http.Request, cfg config.Proxy return nil } +var protectHeaders = map[string]bool{ + "Forwarded": true, + "X-Forwarded-For": true, + "X-Forwarded-Host": true, + "X-Forwarded-Port": true, + "X-Forwarded-Proto": true, + "X-Forwarded-Prefix": true, + "X-Real-Ip": true, +} + // addHeaders adds/updates headers in request // // * add/update `Forwarded` header @@ -39,6 +50,22 @@ func addHeaders(r *http.Request, cfg config.Proxy, stripPath string) error { return errors.New("cannot parse " + r.RemoteAddr) } + // exclude headers from Connection rules. + var conHeaders []string + for _, s := range r.Header.Values("Connection") { + for _, p := range strings.Split(s, ",") { + p = strings.TrimSpace(p) + if !protectHeaders[textproto.CanonicalMIMEHeaderKey(p)] { + conHeaders = append(conHeaders, p) + } + } + } + + r.Header.Del("Connection") + if len(conHeaders) > 0 { + r.Header.Set("Connection", strings.Join(conHeaders, ", ")) + } + // set configurable ClientIPHeader // X-Real-Ip is set later and X-Forwarded-For is set // by the Go HTTP reverse proxy.
proxy/http_headers_test.go+14 −0 modified@@ -55,6 +55,20 @@ func TestAddHeaders(t *testing.T) { "", }, + {"https request hack", + &http.Request{RemoteAddr: "1.2.3.4:5555", Header: http.Header{"Connection": {"keep-alive", "X-Real-Ip"}}}, + config.Proxy{}, + "", + http.Header{ + "Connection": []string{"keep-alive"}, + "Forwarded": []string{"for=1.2.3.4; proto=http"}, + "X-Forwarded-Proto": []string{"http"}, + "X-Forwarded-Port": []string{"80"}, + "X-Real-Ip": []string{"1.2.3.4"}, + }, + "", + }, + {"ws request", &http.Request{RemoteAddr: "1.2.3.4:5555", Header: http.Header{"Upgrade": {"websocket"}}}, config.Proxy{},
proxy/http_integration_test.go+16 −0 modified@@ -36,6 +36,11 @@ const ( // Global GlobCache for Testing var globCache = route.NewGlobCache(1000) +const ( + legitHeader1 = "Legit-Header1" + legitHeader2 = "Legit-Header2" +) + func TestProxyProducesCorrectXForwardedSomethingHeader(t *testing.T) { var hdr http.Header = make(http.Header) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -55,6 +60,11 @@ func TestProxyProducesCorrectXForwardedSomethingHeader(t *testing.T) { req, _ := http.NewRequest("GET", proxy.URL, nil) req.Host = "foo.com" req.Header.Set("X-Forwarded-For", "3.3.3.3") + req.Header.Set(legitHeader1, "asdf") + req.Header.Set(legitHeader2, "qwerty") + req.Header.Set("Connection", + fmt.Sprintf("keep-alive, x-forwarded-for, x-forwarded-host, %s, %s", + strings.ToLower(legitHeader1), strings.ToLower(legitHeader2))) mustDo(req) if got, want := hdr.Get("X-Forwarded-For"), "3.3.3.3, 127.0.0.1"; got != want { @@ -63,6 +73,12 @@ func TestProxyProducesCorrectXForwardedSomethingHeader(t *testing.T) { if got, want := hdr.Get("X-Forwarded-Host"), "foo.com"; got != want { t.Errorf("got %v want %v", got, want) } + if got, want := hdr.Get(legitHeader1), ""; got != want { + t.Errorf("got %v want %v", got, want) + } + if got, want := hdr.Get(legitHeader2), ""; got != want { + t.Errorf("got %v want %v", got, want) + } } func TestProxyRequestIDHeader(t *testing.T) {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-q7p4-7xjv-j3wfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-48865ghsaADVISORY
- github.com/fabiolb/fabio/commit/fdaf1e966162e9dd3b347ffdd0647b39dc71a1a3ghsax_refsource_MISCWEB
- github.com/fabiolb/fabio/releases/tag/v1.6.6ghsax_refsource_MISCWEB
- github.com/fabiolb/fabio/security/advisories/GHSA-q7p4-7xjv-j3wfghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.