VYPR
Critical severityNVD Advisory· Published May 30, 2025· Updated May 30, 2025

Fabio allows HTTP clients to manipulate custom headers it adds

CVE-2025-48865

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.

PackageAffected versionsPatched versions
github.com/fabiolb/fabioGo
< 1.6.61.6.6

Affected products

2
  • Fabio/Fabiollm-create
    Range: <1.6.6
  • fabiolb/fabiov5
    Range: < 1.6.6

Patches

1
fdaf1e966162

Merge commit from fork

https://github.com/fabiolb/fabioTristan MorganMay 26, 2025via ghsa
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

News mentions

0

No linked articles in our index yet.