CVE-2023-45128: Improper Input Validation (CWE-20)
value: Fiber is an express inspired web framework written in Go. A Cross-Site Request Forgery (CSRF) vulnerability has been identified in the application, which allows an attacker to inject arbitrary values and forge malicious requests on behalf of a user. This vulnerability can allow an attacker to inject arbitrary values without any authentication, or perform various malicious actions on behalf of an authenticated user, potentially compromising the security and integrity of the application. The vulnerability is caused by improper validation and enforcement of CSRF tokens within the application. This issue has been addressed in version 2.50.0 and users are advised to upgrade. Users should take additional security measures like captchas or Two-Factor Authentication (2FA) and set Session cookies with SameSite=Lax or SameSite=Secure, and the Secure and HttpOnly attributes as defense in depth measures. There are no known workarounds for this vulnerability.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 8.8
Published: 2023-10-16T21:15Z
Repository: https://github.com/gofiber/fiber
Language: Go
Files changed: docs/api/middleware/csrf.md, middleware/csrf/csrf.go, middleware/csrf/csrf_test.go, middleware/csrf/extractors.go, middleware/csrf/helpers.go
Commit date: 2023-10-16 09:06:30 +0200
Commit message: Merge pull request from GHSA-94w9-97p3-p368

* feat: improved csrf with session support

* fix: double submit cookie

* feat: add warning cookie extractor without session

* feat: add warning CsrfFromCookie SameSite

* fix: use byes.Equal instead

* fix: Overriden CookieName KeyLookup cookie:<name>

* feat: Create helpers.go

* feat: use compareTokens (constant time compare)

* feat: validate cookie to prevent token injection

* refactor: clean up csrf.go

* docs: update comment about Double Submit Cookie

* docs: update docs for CSRF changes

* feat: add DeleteToken

* refactor: no else

* test: add more tests

* refactor: re-order tests

* docs: update safe methods RCF add note

* test: add CSRF_Cookie_Injection_Exploit

* feat: add SingleUseToken config

* test: check for new token

* docs: use warning

* fix: always register type Token

* feat: use UUIDv4

* test: swap in UUIDv4 here too

* fix: raw token injection

* fix: merege error

* feat: Sentinel errors

* chore: rename test

* fix: url parse

* test: add path to referer

* test: add expiration tests

* docs: add cookie prefix note

* docs: fix typo

* docs: add warning for refer checks

* test: add referer edge cases

And call ctx.Request.Reset() and
ctx.Response.Reset() before re-using ctx.
Diff: diff --git a/docs/api/middleware/csrf.md b/docs/api/middleware/csrf.md
index 66a3a6e6..3430e009 100644
--- a/docs/api/middleware/csrf.md
+++ b/docs/api/middleware/csrf.md
@@ -40,14 +40,24 @@ When using this method, pre-sessions are required and will be created if a sessi
 
 ### Defense In Depth
 
 When using this middleware, it is recommended that you serve your pages over HTTPS, that the `CookieSecure` option is set to `true`, and that the `CookieSameSite` option is set to `Lax` or `Strict`. This will ensure that the cookie is only sent over HTTPS and that it is not sent on requests from external sites.
 
+:::note
+Cookie prefixes __Host- and __Secure- can be used to further secure the cookie. However, these prefixes are not supported by all browsers and there are some other limitations. See [MDN#Set-Cookie#cookie_prefixes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes) for more information.
+
+To use these prefixes, set the `CookieName` option to `__Host-csrf_` or `__Secure-csrf_`.
+:::
+
 ### Referer Checking
 
 For HTTPS requests, this middleware performs strict referer checking. This means that even if a subdomain can set or modify cookies on your domain, it can’t force a user to post to your application since that request won’t come from your own exact domain.
 
+:::warning
+Referer checking is required for https requests protected by CSRF. All modern browsers will automatically include the Referer header in requests, including those made with the JS Fetch API. However, if you are using this middleware with a custom client you must ensure that the client sends a valid Referer header.
+:::
+
 ### Token Lifecycle
 
 Tokens are valid until they expire, or until they are deleted. By default, tokens are valid for 1 hour and each subsequent request will extend the expiration by 1 hour. This means that if a user makes a request every hour, the token will never expire. If a user makes a request after the token has expired, then a new token will be generated and the `csrf_` cookie will be set again. This means that the token will only expire if the user does not make a request for the duration of the expiration time.
 
 #### Token Reuse
diff --git a/middleware/csrf/csrf.go b/middleware/csrf/csrf.go
index b7b01274..f4dee74c 100644
--- a/middleware/csrf/csrf.go
+++ b/middleware/csrf/csrf.go
@@ -1,9 +1,10 @@
 package csrf
 
 import (
 	"errors"
+	"net/url"
 	"reflect"
 	"time"
 
 	"github.com/gofiber/fiber/v2"
 )
@@ -61,14 +62,14 @@ func New(config ...Config) fiber.Handler {
 		switch c.Method() {
 		case fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions, fiber.MethodTrace:
 			cookieToken := c.Cookies(cfg.CookieName)
 
 			if cookieToken != "" {
-				rawToken := getTokenFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
+				raw := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
 
-				if rawToken != nil {
-					token = string(rawToken)
+				if raw != nil {
+					token = cookieToken // Token is valid, safe to set it
 				}
 			}
 		default:
 			// Assume that anything not defined as 'safe' by RFC7231 needs protection
 
@@ -90,27 +91,27 @@ func New(config ...Config) fiber.Handler {
 			}
 
 			// If not using CsrfFromCookie extractor, check that the token matches the cookie
 			// This is to prevent CSRF attacks by using a Double Submit Cookie method
 			// Useful when we do not have access to the users Session
-			if !isCsrfFromCookie(cfg.Extractor) && extractedToken != c.Cookies(cfg.CookieName) {
+			if !isCsrfFromCookie(cfg.Extractor) && !compareStrings(extractedToken, c.Cookies(cfg.CookieName)) {
 				return cfg.ErrorHandler(c, ErrTokenInvalid)
 			}
 
-			rawToken := getTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
+			raw := getRawFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
 
-			if rawToken == nil {
+			if raw == nil {
 				// If token is not in storage, expire the cookie
 				expireCSRFCookie(c, cfg)
 				// and return an error
 				return cfg.ErrorHandler(c, ErrTokenNotFound)
 			}
 			if cfg.SingleUseToken {
 				// If token is single use, delete it from storage
 				deleteTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
 			} else {
-				token = string(rawToken)
+				token = extractedToken // Token is valid, safe to set it
 			}
 		}
 
 		// Generate CSRF token if not exist
 		if token == "" {
@@ -135,13 +136,13 @@ func New(config ...Config) fiber.Handler {
 		// Continue stack
 		return c.Next()
 	}
 }
 
-// getTokenFromStorage returns the raw token from the storage
+// getRawFromStorage returns the raw value from the storage for the given token
 // returns nil if the token does not exist, is expired or is invalid
-func getTokenFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
+func getRawFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
 	if cfg.Session != nil {
 		return sessionManager.getRaw(c, token, dummyValue)
 	}
 	return storageManager.getRaw(token)
 }
@@ -221,10 +222,17 @@ func isCsrfFromCookie(extractor interface{}) bool {
 func refererMatchesHost(c *fiber.Ctx) error {
 	referer := c.Get(fiber.HeaderReferer)
 	if referer == "" {
 		return ErrNoReferer
 	}
-	if referer != c.Protocol()+"://"+c.Hostname() {
+
+	refererURL, err := url.Parse(referer)
+	if err != nil {
 		return ErrBadReferer
 	}
+
+	if refererURL.Scheme+"://"+refererURL.Host != c.Protocol()+"://"+c.Hostname() {
+		return ErrBadReferer
+	}
+
 	return nil
 }
diff --git a/middleware/csrf/csrf_test.go b/middleware/csrf/csrf_test.go
index c83061e6..562bfaf6 100644
--- a/middleware/csrf/csrf_test.go
+++ b/middleware/csrf/csrf_test.go
@@ -2,10 +2,11 @@ package csrf
 
 import (
 	"net/http/httptest"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/gofiber/fiber/v2"
 	"github.com/gofiber/fiber/v2/middleware/session"
 	"github.com/gofiber/fiber/v2/utils"
 
@@ -151,10 +152,174 @@ func Test_CSRF_WithSession(t *testing.T) {
 		h(ctx)
 		utils.AssertEqual(t, 200, ctx.Response.StatusCode())
 	}
 }
 
+// go test -run Test_CSRF_ExpiredToken
+func Test_CSRF_ExpiredToken(t *testing.T) {
+	t.Parallel()
+	app := fiber.New()
+
+	app.Use(New(Config{
+		Expiration: 1 * time.Second,
+	}))
+
+	app.Post("/", func(c *fiber.Ctx) error {
+		return c.SendStatus(fiber.StatusOK)
+	})
+
+	h := app.Handler()
+	ctx := &fasthttp.RequestCtx{}
+
+	// Generate CSRF token
+	ctx.Request.Header.SetMethod(fiber.MethodGet)
+	h(ctx)
+	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
+	token = strings.Split(strings.Split(token, ";")[0], "=")[1]
+
+	// Use the CSRF token
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.Header.Set(HeaderName, token)
+	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
+	h(ctx)
+	utils.AssertEqual(t, 200, ctx.Response.StatusCode())
+
+	// Wait for the token to expire
+	time.Sleep(1 * time.Second)
+
+	// Expired CSRF token
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.Header.Set(HeaderName, token)
+	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
+	h(ctx)
+	utils.AssertEqual(t, 403, ctx.Response.StatusCode())
+}
+
+// go test -run Test_CSRF_ExpiredToken_WithSession
+func Test_CSRF_ExpiredToken_WithSession(t *testing.T) {
+	t.Parallel()
+
+	// session store
+	store := session.New(session.Config{
+		KeyLookup: "cookie:_session",
+	})
+
+	// fiber instance
+	app := fiber.New()
+
+	// fiber context
+	ctx := &fasthttp.RequestCtx{}
+	defer app.ReleaseCtx(app.AcquireCtx(ctx))
+
+	// get session
+	sess, err := store.Get(app.AcquireCtx(ctx))
+	utils.AssertEqual(t, nil, err)
+	utils.AssertEqual(t, true, sess.Fresh())
+
+	// get session id
+	newSessionIDString := sess.ID()
+	app.AcquireCtx(ctx).Request().Header.SetCookie("_session", newSessionIDString)
+
+	// middleware config
+	config := Config{
+		Session:    store,
+		Expiration: 1 * time.Second,
+	}
+
+	// middleware
+	app.Use(New(config))
+
+	app.Post("/", func(c *fiber.Ctx) error {
+		return c.SendStatus(fiber.StatusOK)
+	})
+
+	h := app.Handler()
+
+	// Generate CSRF token
+	ctx.Request.Header.SetMethod(fiber.MethodGet)
+	ctx.Request.Header.SetCookie("_session", newSessionIDString)
+	h(ctx)
+	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
+	for _, header := range strings.Split(token, ";") {
+		if strings.Split(strings.TrimSpace(header), "=")[0] == ConfigDefault.CookieName {
+			token = strings.Split(header, "=")[1]
+			break
+		}
+	}
+
+	// Use the CSRF token
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.Header.Set(HeaderName, token)
+	ctx.Request.Header.SetCookie("_session", newSessionIDString)
+	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
+	h(ctx)
+	utils.AssertEqual(t, 200, ctx.Response.StatusCode())
+
+	// Wait for the token to expire
+	time.Sleep(1 * time.Second)
+
+	// Expired CSRF token
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.Header.Set(HeaderName, token)
+	ctx.Request.Header.SetCookie("_session", newSessionIDString)
+	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
+	h(ctx)
+	utils.AssertEqual(t, 403, ctx.Response.StatusCode())
+}
+
+// go test -run Test_CSRF_MultiUseToken
+func Test_CSRF_MultiUseToken(t *testing.T) {
+	t.Parallel()
+	app := fiber.New()
+
+	app.Use(New(Config{
+		KeyLookup: "header:X-CSRF-Token",
+	}))
+
+	app.Post("/", func(c *fiber.Ctx) error {
+		return c.SendStatus(fiber.StatusOK)
+	})
+
+	h := app.Handler()
+	ctx := &fasthttp.RequestCtx{}
+
+	// Invalid CSRF token
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.Header.Set("X-CSRF-Token", "johndoe")
+	h(ctx)
+	utils.AssertEqual(t, 403, ctx.Response.StatusCode())
+
+	// Generate CSRF token
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodGet)
+	h(ctx)
+	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
+	token = strings.Split(strings.Split(token, ";")[0], "=")[1]
+
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.Header.Set("X-CSRF-Token", token)
+	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
+	h(ctx)
+	newToken := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
+	newToken = strings.Split(strings.Split(newToken, ";")[0], "=")[1]
+	utils.AssertEqual(t, 200, ctx.Response.StatusCode())
+
+	// Check if the token is not a dummy value
+	utils.AssertEqual(t, token, newToken)
+}
+
 // go test -run Test_CSRF_SingleUseToken
 func Test_CSRF_SingleUseToken(t *testing.T) {
 	t.Parallel()
 	app := fiber.New()
 
@@ -258,10 +423,12 @@ func Test_CSRF_From_Form(t *testing.T) {
 	ctx.Request.Header.SetMethod(fiber.MethodGet)
 	h(ctx)
 	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
 	token = strings.Split(strings.Split(token, ";")[0], "=")[1]
 
+	ctx.Request.Reset()
+	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
 	ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMEApplicationForm)
 	ctx.Request.SetBodyString("_csrf=" + token)
 	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
 	h(ctx)
@@ -391,11 +558,11 @@ func Test_CSRF_From_Custom(t *testing.T) {
 		body := string(c.Body())
 		// Generate the correct extractor to get the token from the correct location
 		selectors := strings.Split(body, "=")
 
 		if len(selectors) != 2 || selectors[1] == "" {
-			return "", errMissingParam
+			return "", ErrMissingParam
 		}
 		return selectors[1], nil
 	}
 
 	app.Use(New(Config{Extractor: extractor}))
@@ -419,10 +586,12 @@ func Test_CSRF_From_Custom(t *testing.T) {
 	ctx.Request.Header.SetMethod(fiber.MethodGet)
 	h(ctx)
 	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
 	token = strings.Split(strings.Split(token, ";")[0], "=")[1]
 
+	ctx.Request.Reset()
+	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
 	ctx.Request.Header.Set(fiber.HeaderContentType, fiber.MIMETextPlain)
 	ctx.Request.SetBodyString("_csrf=" + token)
 	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
 	h(ctx)
@@ -445,21 +614,71 @@ func Test_CSRF_Referer(t *testing.T) {
 	ctx.Request.Header.Set(fiber.HeaderXForwardedProto, "https")
 	h(ctx)
 	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
 	token = strings.Split(strings.Split(token, ";")[0], "=")[1]
 
-	// Test Correct Referer
+	// Test Correct Referer with port
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.URI().SetScheme("https")
+	ctx.Request.URI().SetHost("example.com:8443")
+	ctx.Request.Header.SetProtocol("https")
+	ctx.Request.Header.SetHost("example.com:8443")
+	ctx.Request.Header.Set(fiber.HeaderReferer, ctx.Request.URI().String())
+	ctx.Request.Header.Set(HeaderName, token)
+	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
+	h(ctx)
+	utils.AssertEqual(t, 200, ctx.Response.StatusCode())
+
+	// Test Correct Referer with ReverseProxy
+	ctx.Request.Reset()
+	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.URI().SetScheme("https")
+	ctx.Request.URI().SetHost("10.0.1.42.com:8443")
+	ctx.Request.Header.SetProtocol("https")
+	ctx.Request.Header.SetHost("10.0.1.42:8443")
 	ctx.Request.Header.Set(fiber.HeaderXForwardedProto, "https")
 	ctx.Request.Header.Set(fiber.HeaderXForwardedHost, "example.com")
+	ctx.Request.Header.Set(fiber.HeaderXForwardedFor, `192.0.2.43, "[2001:db8:cafe::17]"`)
+	ctx.Request.Header.Set(fiber.HeaderReferer, "https://example.com")
+	ctx.Request.Header.Set(HeaderName, token)
+	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
+	h(ctx)
+	utils.AssertEqual(t, 200, ctx.Response.StatusCode())
+
+	// Test Correct Referer with ReverseProxy Missing X-Forwarded-* Headers
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.URI().SetScheme("https")
+	ctx.Request.URI().SetHost("10.0.1.42:8443")
+	ctx.Request.Header.SetProtocol("https")
+	ctx.Request.Header.SetHost("10.0.1.42:8443")
+	ctx.Request.Header.Set(fiber.HeaderXUrlScheme, "https") // We need to set this header to make sure c.Protocol() returns https
 	ctx.Request.Header.Set(fiber.HeaderReferer, "https://example.com")
 	ctx.Request.Header.Set(HeaderName, token)
 	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
 	h(ctx)
+	utils.AssertEqual(t, 403, ctx.Response.StatusCode())
+
+	// Test Correct Referer with path
+	ctx.Request.Reset()
+	ctx.Response.Reset()
+	ctx.Request.Header.SetMethod(fiber.MethodPost)
+	ctx.Request.Header.Set(fiber.HeaderXForwardedProto, "https")
+	ctx.Request.Header.Set(fiber.HeaderXForwardedHost, "example.com")
+	ctx.Request.Header.Set(fiber.HeaderReferer, "https://example.com/action/items?gogogo=true")
+	ctx.Request.Header.Set(HeaderName, token)
+	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
+	h(ctx)
 	utils.AssertEqual(t, 200, ctx.Response.StatusCode())
 
 	// Test Wrong Referer
+	ctx.Request.Reset()
+	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
 	ctx.Request.Header.Set(fiber.HeaderXForwardedProto, "https")
 	ctx.Request.Header.Set(fiber.HeaderXForwardedHost, "example.com")
 	ctx.Request.Header.Set(fiber.HeaderReferer, "https://csrf.example.com")
 	ctx.Request.Header.Set(HeaderName, token)
@@ -488,11 +707,10 @@ func Test_CSRF_DeleteToken(t *testing.T) {
 	h(ctx)
 	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
 	token = strings.Split(strings.Split(token, ";")[0], "=")[1]
 
 	// Delete the CSRF token
-	ctx.Request.Header.SetMethod(fiber.MethodGet)
 	ctx.Request.Reset()
 	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
 	ctx.Request.Header.Set(HeaderName, token)
 	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
@@ -501,11 +719,10 @@ func Test_CSRF_DeleteToken(t *testing.T) {
 			t.Fatal(err)
 		}
 	}
 	h(ctx)
 
-	ctx.Request.Header.SetMethod(fiber.MethodGet)
 	ctx.Request.Reset()
 	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
 	ctx.Request.Header.Set(HeaderName, token)
 	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
@@ -557,11 +774,10 @@ func Test_CSRF_DeleteToken_WithSession(t *testing.T) {
 	h(ctx)
 	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
 	token = strings.Split(strings.Split(token, ";")[0], "=")[1]
 
 	// Delete the CSRF token
-	ctx.Request.Header.SetMethod(fiber.MethodGet)
 	ctx.Request.Reset()
 	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
 	ctx.Request.Header.Set(HeaderName, token)
 	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
@@ -617,11 +833,11 @@ func Test_CSRF_ErrorHandler_InvalidToken(t *testing.T) {
 func Test_CSRF_ErrorHandler_EmptyToken(t *testing.T) {
 	t.Parallel()
 	app := fiber.New()
 
 	errHandler := func(ctx *fiber.Ctx, err error) error {
-		utils.AssertEqual(t, errMissingHeader, err)
+		utils.AssertEqual(t, ErrMissingHeader, err)
 		return ctx.Status(419).Send([]byte("empty CSRF token"))
 	}
 
 	app.Use(New(Config{ErrorHandler: errHandler}))
 
@@ -669,10 +885,12 @@ func Test_CSRF_ErrorHandler_MissingReferer(t *testing.T) {
 	ctx.Request.Header.Set(fiber.HeaderXForwardedProto, "https")
 	h(ctx)
 	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
 	token = strings.Split(strings.Split(token, ";")[0], "=")[1]
 
+	ctx.Request.Reset()
+	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
 	ctx.Request.Header.Set(fiber.HeaderXForwardedProto, "https")
 	ctx.Request.Header.Set(fiber.HeaderXForwardedHost, "example.com")
 	ctx.Request.Header.Set(HeaderName, token)
 	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
diff --git a/middleware/csrf/extractors.go b/middleware/csrf/extractors.go
index ab669bb4..a9260e89 100644
--- a/middleware/csrf/extractors.go
+++ b/middleware/csrf/extractors.go
@@ -5,66 +5,66 @@ import (
 
 	"github.com/gofiber/fiber/v2"
 )
 
 var (
-	errMissingHeader = errors.New("missing csrf token in header")
-	errMissingQuery  = errors.New("missing csrf token in query")
-	errMissingParam  = errors.New("missing csrf token in param")
-	errMissingForm   = errors.New("missing csrf token in form")
-	errMissingCookie = errors.New("missing csrf token in cookie")
+	ErrMissingHeader = errors.New("missing csrf token in header")
+	ErrMissingQuery  = errors.New("missing csrf token in query")
+	ErrMissingParam  = errors.New("missing csrf token in param")
+	ErrMissingForm   = errors.New("missing csrf token in form")
+	ErrMissingCookie = errors.New("missing csrf token in cookie")
 )
 
 // csrfFromParam returns a function that extracts token from the url param string.
 func CsrfFromParam(param string) func(c *fiber.Ctx) (string, error) {
 	return func(c *fiber.Ctx) (string, error) {
 		token := c.Params(param)
 		if token == "" {
-			return "", errMissingParam
+			return "", ErrMissingParam
 		}
 		return token, nil
 	}
 }
 
 // csrfFromForm returns a function that extracts a token from a multipart-form.
 func CsrfFromForm(param string) func(c *fiber.Ctx) (string, error) {
 	return func(c *fiber.Ctx) (string, error) {
 		token := c.FormValue(param)
 		if token == "" {
-			return "", errMissingForm
+			return "", ErrMissingForm
 		}
 		return token, nil
 	}
 }
 
 // csrfFromCookie returns a function that extracts token from the cookie header.
 func CsrfFromCookie(param string) func(c *fiber.Ctx) (string, error) {
 	return func(c *fiber.Ctx) (string, error) {
 		token := c.Cookies(param)
 		if token == "" {
-			return "", errMissingCookie
+			return "", ErrMissingCookie
 		}
 		return token, nil
 	}
 }
 
 // csrfFromHeader returns a function that extracts token from the request header.
 func CsrfFromHeader(param string) func(c *fiber.Ctx) (string, error) {
 	return func(c *fiber.Ctx) (string, error) {
 		token := c.Get(param)
 		if token == "" {
-			return "", errMissingHeader
+			return "", ErrMissingHeader
 		}
 		return token, nil
 	}
 }
 
 // csrfFromQuery returns a function that extracts token from the query string.
 func CsrfFromQuery(param string) func(c *fiber.Ctx) (string, error) {
 	return func(c *fiber.Ctx) (string, error) {
 		token := c.Query(param)
 		if token == "" {
-			return "", errMissingQuery
+			return "", ErrMissingQuery
 		}
 		return token, nil
 	}
 }
diff --git a/middleware/csrf/helpers.go b/middleware/csrf/helpers.go
index 7604b975..708762c7 100644
--- a/middleware/csrf/helpers.go
+++ b/middleware/csrf/helpers.go
@@ -5,5 +5,9 @@ import (
 )
 
 func compareTokens(a, b []byte) bool {
 	return subtle.ConstantTimeCompare(a, b) == 1
 }
+
+func compareStrings(a, b string) bool {
+	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
+}

Vulnerable code: 				rawToken := getTokenFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
				if rawToken != nil {
					token = string(rawToken)
			if !isCsrfFromCookie(cfg.Extractor) && extractedToken != c.Cookies(cfg.CookieName) {
			rawToken := getTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
			if rawToken == nil {
				token = string(rawToken)
// getTokenFromStorage returns the raw token from the storage
func getTokenFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
	if referer != c.Protocol()+"://"+c.Hostname() {
			return "", errMissingParam
	// Test Correct Referer
	ctx.Request.Header.SetMethod(fiber.MethodGet)
	ctx.Request.Header.SetMethod(fiber.MethodGet)
	ctx.Request.Header.SetMethod(fiber.MethodGet)
		utils.AssertEqual(t, errMissingHeader, err)
	errMissingHeader = errors.New("missing csrf token in header")
	errMissingQuery  = errors.New("missing csrf token in query")
	errMissingParam  = errors.New("missing csrf token in param")
	errMissingForm   = errors.New("missing csrf token in form")
	errMissingCookie = errors.New("missing csrf token in cookie")
			return "", errMissingParam
			return "", errMissingForm
			return "", errMissingCookie
			return "", errMissingHeader
			return "", errMissingQuery
Fixed code: :::note
Cookie prefixes __Host- and __Secure- can be used to further secure the cookie. However, these prefixes are not supported by all browsers and there are some other limitations. See [MDN#Set-Cookie#cookie_prefixes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes) for more information.

To use these prefixes, set the `CookieName` option to `__Host-csrf_` or `__Secure-csrf_`.
:::

:::warning
Referer checking is required for https requests protected by CSRF. All modern browsers will automatically include the Referer header in requests, including those made with the JS Fetch API. However, if you are using this middleware with a custom client you must ensure that the client sends a valid Referer header.
:::

	"net/url"
				raw := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
				if raw != nil {
					token = cookieToken // Token is valid, safe to set it
			if !isCsrfFromCookie(cfg.Extractor) && !compareStrings(extractedToken, c.Cookies(cfg.CookieName)) {
			raw := getRawFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
			if raw == nil {
				token = extractedToken // Token is valid, safe to set it
// getRawFromStorage returns the raw value from the storage for the given token
func getRawFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {

	refererURL, err := url.Parse(referer)
	if err != nil {

	if refererURL.Scheme+"://"+refererURL.Host != c.Protocol()+"://"+c.Hostname() {
		return ErrBadReferer
	}

	"time"
// go test -run Test_CSRF_ExpiredToken
func Test_CSRF_ExpiredToken(t *testing.T) {
	t.Parallel()
	app := fiber.New()

	app.Use(New(Config{
		Expiration: 1 * time.Second,
	}))

	app.Post("/", func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	h := app.Handler()
	ctx := &fasthttp.RequestCtx{}

	// Generate CSRF token
	ctx.Request.Header.SetMethod(fiber.MethodGet)
	h(ctx)
	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
	token = strings.Split(strings.Split(token, ";")[0], "=")[1]

	// Use the CSRF token
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.Header.Set(HeaderName, token)
	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
	h(ctx)
	utils.AssertEqual(t, 200, ctx.Response.StatusCode())

	// Wait for the token to expire
	time.Sleep(1 * time.Second)

	// Expired CSRF token
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.Header.Set(HeaderName, token)
	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
	h(ctx)
	utils.AssertEqual(t, 403, ctx.Response.StatusCode())
}

// go test -run Test_CSRF_ExpiredToken_WithSession
func Test_CSRF_ExpiredToken_WithSession(t *testing.T) {
	t.Parallel()

	// session store
	store := session.New(session.Config{
		KeyLookup: "cookie:_session",
	})

	// fiber instance
	app := fiber.New()

	// fiber context
	ctx := &fasthttp.RequestCtx{}
	defer app.ReleaseCtx(app.AcquireCtx(ctx))

	// get session
	sess, err := store.Get(app.AcquireCtx(ctx))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, true, sess.Fresh())

	// get session id
	newSessionIDString := sess.ID()
	app.AcquireCtx(ctx).Request().Header.SetCookie("_session", newSessionIDString)

	// middleware config
	config := Config{
		Session:    store,
		Expiration: 1 * time.Second,
	}

	// middleware
	app.Use(New(config))

	app.Post("/", func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	h := app.Handler()

	// Generate CSRF token
	ctx.Request.Header.SetMethod(fiber.MethodGet)
	ctx.Request.Header.SetCookie("_session", newSessionIDString)
	h(ctx)
	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
	for _, header := range strings.Split(token, ";") {
		if strings.Split(strings.TrimSpace(header), "=")[0] == ConfigDefault.CookieName {
			token = strings.Split(header, "=")[1]
			break
		}
	}

	// Use the CSRF token
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.Header.Set(HeaderName, token)
	ctx.Request.Header.SetCookie("_session", newSessionIDString)
	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
	h(ctx)
	utils.AssertEqual(t, 200, ctx.Response.StatusCode())

	// Wait for the token to expire
	time.Sleep(1 * time.Second)

	// Expired CSRF token
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.Header.Set(HeaderName, token)
	ctx.Request.Header.SetCookie("_session", newSessionIDString)
	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
	h(ctx)
	utils.AssertEqual(t, 403, ctx.Response.StatusCode())
}

// go test -run Test_CSRF_MultiUseToken
func Test_CSRF_MultiUseToken(t *testing.T) {
	t.Parallel()
	app := fiber.New()

	app.Use(New(Config{
		KeyLookup: "header:X-CSRF-Token",
	}))

	app.Post("/", func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	h := app.Handler()
	ctx := &fasthttp.RequestCtx{}

	// Invalid CSRF token
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.Header.Set("X-CSRF-Token", "johndoe")
	h(ctx)
	utils.AssertEqual(t, 403, ctx.Response.StatusCode())

	// Generate CSRF token
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodGet)
	h(ctx)
	token := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
	token = strings.Split(strings.Split(token, ";")[0], "=")[1]

	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.Header.Set("X-CSRF-Token", token)
	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
	h(ctx)
	newToken := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
	newToken = strings.Split(strings.Split(newToken, ";")[0], "=")[1]
	utils.AssertEqual(t, 200, ctx.Response.StatusCode())

	// Check if the token is not a dummy value
	utils.AssertEqual(t, token, newToken)
}

	ctx.Request.Reset()
	ctx.Response.Reset()
			return "", ErrMissingParam
	ctx.Request.Reset()
	ctx.Response.Reset()
	// Test Correct Referer with port
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.URI().SetScheme("https")
	ctx.Request.URI().SetHost("example.com:8443")
	ctx.Request.Header.SetProtocol("https")
	ctx.Request.Header.SetHost("example.com:8443")
	ctx.Request.Header.Set(fiber.HeaderReferer, ctx.Request.URI().String())
	ctx.Request.Header.Set(HeaderName, token)
	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
	h(ctx)
	utils.AssertEqual(t, 200, ctx.Response.StatusCode())

	// Test Correct Referer with ReverseProxy
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.URI().SetScheme("https")
	ctx.Request.URI().SetHost("10.0.1.42.com:8443")
	ctx.Request.Header.SetProtocol("https")
	ctx.Request.Header.SetHost("10.0.1.42:8443")
	ctx.Request.Header.Set(fiber.HeaderXForwardedFor, `192.0.2.43, "[2001:db8:cafe::17]"`)
	ctx.Request.Header.Set(fiber.HeaderReferer, "https://example.com")
	ctx.Request.Header.Set(HeaderName, token)
	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
	h(ctx)
	utils.AssertEqual(t, 200, ctx.Response.StatusCode())

	// Test Correct Referer with ReverseProxy Missing X-Forwarded-* Headers
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.URI().SetScheme("https")
	ctx.Request.URI().SetHost("10.0.1.42:8443")
	ctx.Request.Header.SetProtocol("https")
	ctx.Request.Header.SetHost("10.0.1.42:8443")
	ctx.Request.Header.Set(fiber.HeaderXUrlScheme, "https") // We need to set this header to make sure c.Protocol() returns https
	utils.AssertEqual(t, 403, ctx.Response.StatusCode())

	// Test Correct Referer with path
	ctx.Request.Reset()
	ctx.Response.Reset()
	ctx.Request.Header.SetMethod(fiber.MethodPost)
	ctx.Request.Header.Set(fiber.HeaderXForwardedProto, "https")
	ctx.Request.Header.Set(fiber.HeaderXForwardedHost, "example.com")
	ctx.Request.Header.Set(fiber.HeaderReferer, "https://example.com/action/items?gogogo=true")
	ctx.Request.Header.Set(HeaderName, token)
	ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
	h(ctx)
	ctx.Request.Reset()
	ctx.Response.Reset()
		utils.AssertEqual(t, ErrMissingHeader, err)
	ctx.Request.Reset()
	ctx.Response.Reset()
	ErrMissingHeader = errors.New("missing csrf token in header")
	ErrMissingQuery  = errors.New("missing csrf token in query")
	ErrMissingParam  = errors.New("missing csrf token in param")
	ErrMissingForm   = errors.New("missing csrf token in form")
	ErrMissingCookie = errors.New("missing csrf token in cookie")
			return "", ErrMissingParam
			return "", ErrMissingForm
			return "", ErrMissingCookie
			return "", ErrMissingHeader
			return "", ErrMissingQuery

func compareStrings(a, b string) bool {
	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}