Vulnerability in Ctx.IsFromLocal() in gofiber
Description
Fiber is an Express inspired web framework built in the go language. Versions of gofiber prior to 2.49.2 did not properly restrict access to localhost. This issue impacts users of our project who rely on the ctx.IsFromLocal method to restrict access to localhost requests. If exploited, it could allow unauthorized access to resources intended only for localhost. Setting X-Forwarded-For: 127.0.0.1 in a request from a foreign host, will result in true for ctx.IsFromLocal. Access is limited to the scope of the affected process. This issue has been patched in version 2.49.2 with commit b8c9ede6. Users are advised to upgrade. There are no known workarounds to remediate this vulnerability without upgrading to the patched version.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/gofiber/fiberGo | <= 1.14.6 | — |
github.com/gofiber/fiber/v2Go | < 2.49.2 | 2.49.2 |
Affected products
1Patches
12 files changed · +94 −28
ctx.go+3 −7 modified@@ -1887,12 +1887,12 @@ func (c *Ctx) IsProxyTrusted() bool { return false } -var localHosts = [...]string{"127.0.0.1", "0.0.0.0", "::1"} +var localHosts = [...]string{"127.0.0.1", "::1"} // IsLocalHost will return true if address is a localhost address. func (*Ctx) isLocalHost(address string) bool { for _, h := range localHosts { - if strings.Contains(address, h) { + if address == h { return true } } @@ -1901,9 +1901,5 @@ func (*Ctx) isLocalHost(address string) bool { // IsFromLocal will return true if request came from local. func (c *Ctx) IsFromLocal() bool { - ips := c.IPs() - if len(ips) == 0 { - ips = append(ips, c.IP()) - } - return c.isLocalHost(ips[0]) + return c.isLocalHost(c.fasthttp.RemoteIP().String()) }
ctx_test.go+91 −21 modified@@ -17,6 +17,7 @@ import ( "fmt" "io" "mime/multipart" + "net" "net/http/httptest" "net/url" "os" @@ -4918,57 +4919,126 @@ func Test_Ctx_GetReqHeaders(t *testing.T) { }) } -// go test -run Test_Ctx_IsFromLocal -func Test_Ctx_IsFromLocal(t *testing.T) { +// go test -run Test_Ctx_IsFromLocal_X_Forwarded +func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) { t.Parallel() - // Test "0.0.0.0", "127.0.0.1" and "::1". + // Test unset X-Forwarded-For header. { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - utils.AssertEqual(t, true, c.IsFromLocal()) + // fasthttp returns "0.0.0.0" as IP as there is no remote address. + utils.AssertEqual(t, "0.0.0.0", c.IP()) + utils.AssertEqual(t, false, c.IsFromLocal()) } - // This is a test for "0.0.0.0" + // Test when setting X-Forwarded-For header to localhost "127.0.0.1" { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.0") + c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1") defer app.ReleaseCtx(c) - utils.AssertEqual(t, true, c.IsFromLocal()) + utils.AssertEqual(t, false, c.IsFromLocal()) } - - // This is a test for "127.0.0.1" + // Test when setting X-Forwarded-For header to localhost "::1" { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1") + c.Request().Header.Set(HeaderXForwardedFor, "::1") defer app.ReleaseCtx(c) - utils.AssertEqual(t, true, c.IsFromLocal()) + utils.AssertEqual(t, false, c.IsFromLocal()) } - - // This is a test for "localhost" + // Test when setting X-Forwarded-For to full localhost IPv6 address "0:0:0:0:0:0:0:1" { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) + c.Request().Header.Set(HeaderXForwardedFor, "0:0:0:0:0:0:0:1") defer app.ReleaseCtx(c) - utils.AssertEqual(t, true, c.IsFromLocal()) + utils.AssertEqual(t, false, c.IsFromLocal()) } - - // This is testing "::1", it is the compressed format IPV6 loopback address 0:0:0:0:0:0:0:1. - // It is the equivalent of the IPV4 address 127.0.0.1. + // Test for a random IP address. { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "::1") + c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90") defer app.ReleaseCtx(c) - utils.AssertEqual(t, true, c.IsFromLocal()) + utils.AssertEqual(t, false, c.IsFromLocal()) } +} +// go test -run Test_Ctx_IsFromLocal_RemoteAddr +func Test_Ctx_IsFromLocal_RemoteAddr(t *testing.T) { + t.Parallel() + + localIPv4 := net.Addr(&net.TCPAddr{IP: net.ParseIP("127.0.0.1")}) + localIPv6 := net.Addr(&net.TCPAddr{IP: net.ParseIP("::1")}) + localIPv6long := net.Addr(&net.TCPAddr{IP: net.ParseIP("0:0:0:0:0:0:0:1")}) + + zeroIPv4 := net.Addr(&net.TCPAddr{IP: net.IPv4zero}) + + someIPv4 := net.Addr(&net.TCPAddr{IP: net.ParseIP("93.46.8.90")}) + someIPv6 := net.Addr(&net.TCPAddr{IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")}) + + // Test for the case fasthttp remoteAddr is set to "127.0.0.1". { app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90") + fastCtx := &fasthttp.RequestCtx{} + fastCtx.SetRemoteAddr(localIPv4) + c := app.AcquireCtx(fastCtx) + defer app.ReleaseCtx(c) + + utils.AssertEqual(t, "127.0.0.1", c.IP()) + utils.AssertEqual(t, true, c.IsFromLocal()) + } + // Test for the case fasthttp remoteAddr is set to "::1". + { + app := New() + fastCtx := &fasthttp.RequestCtx{} + fastCtx.SetRemoteAddr(localIPv6) + c := app.AcquireCtx(fastCtx) + defer app.ReleaseCtx(c) + utils.AssertEqual(t, "::1", c.IP()) + utils.AssertEqual(t, true, c.IsFromLocal()) + } + // Test for the case fasthttp remoteAddr is set to "0:0:0:0:0:0:0:1". + { + app := New() + fastCtx := &fasthttp.RequestCtx{} + fastCtx.SetRemoteAddr(localIPv6long) + c := app.AcquireCtx(fastCtx) + defer app.ReleaseCtx(c) + // fasthttp should return "::1" for "0:0:0:0:0:0:0:1". + // otherwise IsFromLocal() will break. + utils.AssertEqual(t, "::1", c.IP()) + utils.AssertEqual(t, true, c.IsFromLocal()) + } + // Test for the case fasthttp remoteAddr is set to "0.0.0.0". + { + app := New() + fastCtx := &fasthttp.RequestCtx{} + fastCtx.SetRemoteAddr(zeroIPv4) + c := app.AcquireCtx(fastCtx) + defer app.ReleaseCtx(c) + utils.AssertEqual(t, "0.0.0.0", c.IP()) + utils.AssertEqual(t, false, c.IsFromLocal()) + } + // Test for the case fasthttp remoteAddr is set to "93.46.8.90". + { + app := New() + fastCtx := &fasthttp.RequestCtx{} + fastCtx.SetRemoteAddr(someIPv4) + c := app.AcquireCtx(fastCtx) + defer app.ReleaseCtx(c) + utils.AssertEqual(t, "93.46.8.90", c.IP()) + utils.AssertEqual(t, false, c.IsFromLocal()) + } + // Test for the case fasthttp remoteAddr is set to "2001:0db8:85a3:0000:0000:8a2e:0370:7334". + { + app := New() + fastCtx := &fasthttp.RequestCtx{} + fastCtx.SetRemoteAddr(someIPv6) + c := app.AcquireCtx(fastCtx) defer app.ReleaseCtx(c) + utils.AssertEqual(t, "2001:db8:85a3::8a2e:370:7334", c.IP()) utils.AssertEqual(t, false, c.IsFromLocal()) } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-3q5p-3558-364fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-41338ghsaADVISORY
- developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Forghsax_refsource_MISCWEB
- docs.gofiber.io/api/ctxghsax_refsource_MISCWEB
- github.com/gofiber/fiber/commit/b8c9ede6efa231116c4bd8bb9d5e03eac1cb76dcghsax_refsource_MISCWEB
- github.com/gofiber/fiber/security/advisories/GHSA-3q5p-3558-364fghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.