CVE-2023-41338 (Go, published 2023-09-08T19:15Z)
Description: value: 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. 
Severity: nan (CVSS v3 base score: 5.3)
Weakness: CWE-670 \u2014 Always-Incorrect Control Flow Implementation
The code contains a control flow path that does not reflect the algorithm that the path is intended to implement, leading to incorrect behavior any time this path is navigated.
Repository: https://github.com/gofiber/fiber
Files changed:
ctx.go
ctx_test.go
Commit message: IsFromLocal corrected


Vulnerable code:
var localHosts = [...]string{"127.0.0.1", "0.0.0.0", "::1"}
		if strings.Contains(address, h) {
	ips := c.IPs()
	if len(ips) == 0 {
		ips = append(ips, c.IP())
	}
	return c.isLocalHost(ips[0])
// go test -run Test_Ctx_IsFromLocal
func Test_Ctx_IsFromLocal(t *testing.T) {
	// Test "0.0.0.0", "127.0.0.1" and "::1".
		utils.AssertEqual(t, true, c.IsFromLocal())
	// This is a test for "0.0.0.0"
		c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.0")
		utils.AssertEqual(t, true, c.IsFromLocal())

	// This is a test for "127.0.0.1"
		c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1")
		utils.AssertEqual(t, true, c.IsFromLocal())

	// This is a test for "localhost"
		utils.AssertEqual(t, true, 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.
		c.Request().Header.Set(HeaderXForwardedFor, "::1")
		utils.AssertEqual(t, true, c.IsFromLocal())
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90")

Fixed code:
var localHosts = [...]string{"127.0.0.1", "::1"}
		if address == h {
	return c.isLocalHost(c.fasthttp.RemoteIP().String())
	"net"
// go test -run Test_Ctx_IsFromLocal_X_Forwarded
func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) {
	// Test unset X-Forwarded-For header.
		// 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())
	// Test when setting X-Forwarded-For header to localhost "127.0.0.1"
		c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1")
		utils.AssertEqual(t, false, c.IsFromLocal())
	// Test when setting X-Forwarded-For header to localhost "::1"
		c.Request().Header.Set(HeaderXForwardedFor, "::1")
		utils.AssertEqual(t, false, c.IsFromLocal())
	// Test when setting X-Forwarded-For to full localhost IPv6 address "0:0:0:0:0:0:0:1"
		c.Request().Header.Set(HeaderXForwardedFor, "0:0:0:0:0:0:0:1")
		utils.AssertEqual(t, false, c.IsFromLocal())
	// Test for a random IP address.
		c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90")
		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".
		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)
		utils.AssertEqual(t, "2001:db8:85a3::8a2e:370:7334", c.IP())