GHSA-FQ95-V8XC-JM3V
Vulnerability from github – Published: 2026-09-22 20:34 – Updated: 2026-09-22 20:34Affected: github.com/fabiolb/fabio >= 1.6.6 through 1.7.1 and master HEAD (c75f8a6).
Summary
The v1.6.6 fix for CVE-2025-48865 sweeps the client Connection header against a hardcoded allowlist protectHeaders (proxy/http_headers.go:28-36) containing only the 7 X-Forwarded family headers. Fabio also injects three operator-configured server-side trust headers that are NOT in that allowlist, so the original hop-by-hop stripping attack still works against them.
Details
The three unprotected trust headers and where fabio sets them:
- ClientIPHeader (proxy.header.clientip) - http_headers.go:73 r.Header.Set(cfg.ClientIPHeader, remoteIP)
- TLSHeader (proxy.header.tls) - http_headers.go:153 r.Header.Set(cfg.TLSHeader, cfg.TLSHeaderValue) (TLS connections)
- RequestID (proxy.header.requestid) - http_proxy.go:90 r.Header.Set(p.Config.RequestID, id())
Order of operations in HTTPProxy.ServeHTTP: (1) line 90 sets RequestID; (2) line 175 calls addHeaders, which sweeps the Connection header (keeping the three configured names because they are absent from protectHeaders) and then sets ClientIPHeader/TLSHeader; (3) line 223 runs the Go httputil.ReverseProxy, whose removeHopByHopHeaders (Go stdlib net/http/httputil/reverseproxy.go) iterates the inbound Connection header and h.Dels every listed header. The three trust headers fabio just set are therefore deleted before the request reaches the backend.
PoC
Configure proxy.header.clientip=X-Client-IP, proxy.header.requestid=X-Request-ID. Raw request:
GET / HTTP/1.1
Host: foo.com
Connection: close, X-Client-IP, X-Request-ID
Backend sees X-Client-IP: <empty> and X-Request-ID: <empty>. Baseline (Connection: close) => backend sees the real client IP and a request id. The same works for proxy.header.tls over TLS (Connection: close, X-Secure strips the TLS assertion). Reproduced by the two PASS-ing tests in proxy/poc_cve48865_incomplete_test.go on master HEAD (Go 1.26.4). Control: X-Forwarded-For (in protectHeaders) survives, confirming the gap is specific to the configured headers.
Impact
Where a backend trusts these fabio-set headers, an external unauthenticated client can strip/downgrade the trust signal: bypass/poison IP-based ACL or audit that reads the clientip header, downgrade a TLS-terminated request to appear non-TLS to a backend keying off the TLS header, or drop request-id correlation. Scope: deployments that enable the relevant proxy.header.* option (all empty by default). Fix: add the configured ClientIPHeader/TLSHeader/RequestID names to the protectHeaders set (or strip their token from the inbound Connection header) inside addHeaders.
Parent: CVE-2025-48865 / GHSA-q7p4-7xjv-j3wf (fixed in v1.6.6). This is an incomplete-fix sibling - the same hop-by-hop stripping primitive applies to the operator-configured trust headers the allowlist does not cover.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.7.1"
},
"package": {
"ecosystem": "Go",
"name": "github.com/fabiolb/fabio"
},
"ranges": [
{
"events": [
{
"introduced": "1.6.6"
},
{
"fixed": "1.7.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-62987"
],
"database_specific": {
"cwe_ids": [
"CWE-290",
"CWE-348"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-22T20:34:22Z",
"nvd_published_at": "2026-09-21T17:17:38Z",
"severity": "MODERATE"
},
"details": "**Affected:** github.com/fabiolb/fabio \u003e= 1.6.6 through 1.7.1 and master HEAD (c75f8a6).\n\n### Summary\nThe v1.6.6 fix for CVE-2025-48865 sweeps the client `Connection` header against a hardcoded allowlist `protectHeaders` (proxy/http_headers.go:28-36) containing only the 7 X-Forwarded family headers. Fabio also injects three *operator-configured* server-side trust headers that are NOT in that allowlist, so the original hop-by-hop stripping attack still works against them.\n\n### Details\nThe three unprotected trust headers and where fabio sets them:\n- ClientIPHeader (`proxy.header.clientip`) - http_headers.go:73 `r.Header.Set(cfg.ClientIPHeader, remoteIP)`\n- TLSHeader (`proxy.header.tls`) - http_headers.go:153 `r.Header.Set(cfg.TLSHeader, cfg.TLSHeaderValue)` (TLS connections)\n- RequestID (`proxy.header.requestid`) - http_proxy.go:90 `r.Header.Set(p.Config.RequestID, id())`\n\nOrder of operations in HTTPProxy.ServeHTTP: (1) line 90 sets RequestID; (2) line 175 calls addHeaders, which sweeps the Connection header (keeping the three configured names because they are absent from protectHeaders) and then sets ClientIPHeader/TLSHeader; (3) line 223 runs the Go `httputil.ReverseProxy`, whose `removeHopByHopHeaders` (Go stdlib net/http/httputil/reverseproxy.go) iterates the inbound `Connection` header and `h.Del`s every listed header. The three trust headers fabio just set are therefore deleted before the request reaches the backend.\n\n### PoC\nConfigure `proxy.header.clientip=X-Client-IP`, `proxy.header.requestid=X-Request-ID`. Raw request:\n```\nGET / HTTP/1.1\nHost: foo.com\nConnection: close, X-Client-IP, X-Request-ID\n```\nBackend sees `X-Client-IP: \u003cempty\u003e` and `X-Request-ID: \u003cempty\u003e`. Baseline (`Connection: close`) =\u003e backend sees the real client IP and a request id. The same works for `proxy.header.tls` over TLS (`Connection: close, X-Secure` strips the TLS assertion). Reproduced by the two PASS-ing tests in proxy/poc_cve48865_incomplete_test.go on master HEAD (Go 1.26.4). Control: `X-Forwarded-For` (in protectHeaders) survives, confirming the gap is specific to the configured headers.\n\n### Impact\nWhere a backend trusts these fabio-set headers, an external unauthenticated client can strip/downgrade the trust signal: bypass/poison IP-based ACL or audit that reads the clientip header, downgrade a TLS-terminated request to appear non-TLS to a backend keying off the TLS header, or drop request-id correlation. Scope: deployments that enable the relevant `proxy.header.*` option (all empty by default). Fix: add the configured ClientIPHeader/TLSHeader/RequestID names to the protectHeaders set (or strip their token from the inbound Connection header) inside addHeaders.\n\nParent: CVE-2025-48865 / GHSA-q7p4-7xjv-j3wf (fixed in v1.6.6). This is an incomplete-fix sibling - the same hop-by-hop stripping primitive applies to the operator-configured trust headers the allowlist does not cover.",
"id": "GHSA-fq95-v8xc-jm3v",
"modified": "2026-09-22T20:34:22Z",
"published": "2026-09-22T20:34:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/fabiolb/fabio/security/advisories/GHSA-fq95-v8xc-jm3v"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-62987"
},
{
"type": "WEB",
"url": "https://github.com/fabiolb/fabio/commit/240526a8004077edad4fb96d25b382bfc3901357"
},
{
"type": "PACKAGE",
"url": "https://github.com/fabiolb/fabio"
},
{
"type": "WEB",
"url": "https://github.com/fabiolb/fabio/releases/tag/v1.7.2"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Fabio - Incomplete fix for CVE-2025-48865: operator-configured trust headers (clientip/tls/requestid) still strippable via the Connection header"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.