CVE-2023-45128 (published 2023-10-16T21:15Z)

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.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         
cwe_id: CWE-20
cwe_name: Improper Input Validation
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.
extended_description: 
	   Input validation is a frequently-used technique
	   for checking potentially dangerous inputs in order to
	   ensure that the inputs are safe for processing within the
	   code, or when communicating with other components.  When
	   software does not validate input properly, an attacker is
	   able to craft the input in a form that is not expected by
	   the rest of the application. This will lead to parts of the
	   system receiving unintended input, which may result in
	   altered control flow, arbitrary control of a resource, or
	   arbitrary code execution.
	   Input validation is not the only technique for
	   processing input, however.  Other techniques attempt to
	   transform potentially-dangerous input into something safe, such
	   as filtering (CWE-790) - which attempts to remove dangerous
	   inputs - or encoding/escaping (CWE-116), which attempts to
	   ensure that the input is not misinterpreted when it is included
	   in output to another component. Other techniques exist as well
	   (see CWE-138 for more examples.)
	   Input validation can be applied to:
	   
	     raw data - strings, numbers, parameters, file contents, etc.
	     metadata - information about the raw data, such as headers or size
	   
	   Data can be simple or structured.  Structured data
	   can be composed of many nested layers, composed of
	   combinations of metadata and raw data, with other simple or
	   structured data.
	   Many properties of raw data or metadata may need
	   to be validated upon entry into the code, such
	   as:
	   
	     specified quantities such as size, length, frequency, price, rate, number of operations, time, etc.
	     implied or derived quantities, such as the actual size of a file instead of a specified size
	     indexes, offsets, or positions into more complex data structures
	     symbolic keys or other elements into hash tables, associative arrays, etc.
	     well-formedness, i.e. syntactic correctness - compliance with expected syntax 
	     lexical token correctness - compliance with rules for what is treated as a token
	     specified or derived type - the actual type of the input (or what the input appears to be)
	     consistency - between individual data elements, between raw data and metadata, between references, etc.
	     conformance to domain-specific rules, e.g. business logic 
	     equivalence - ensuring that equivalent inputs are treated the same
	     authenticity, ownership, or other attestations about the input, e.g. a cryptographic signature to prove the source of the data
	   
	   Implied or derived properties of data must often
	   be calculated or inferred by the code itself.  Errors in
	   deriving properties may be considered a contributing factor
	   to improper input validation.
	   
	   Note that "input validation" has very different
	   meanings to different people, or within different
	   classification schemes.  Caution must be used when
	   referencing this CWE entry or mapping to it.  For example,
	   some weaknesses might involve inadvertently giving control
	   to an attacker over an input when they should not be able
	   to provide an input at all, but sometimes this is referred
	   to as input validation.
	   Finally, it is important to emphasize that the
	   distinctions between input validation and output escaping
	   are often blurred, and developers must be careful to
	   understand the difference, including how input validation
	   is not always sufficient to prevent vulnerabilities,
	   especially when less stringent data types must be
	   supported, such as free-form text. Consider a SQL injection
	   scenario in which a person's last name is inserted into a
	   query. The name "O'Reilly" would likely pass the validation
	   step since it is a common last name in the English
	   language. However, this valid name cannot be directly
	   inserted into the database because it contains the "'"
	   apostrophe character, which would need to be escaped or
	   otherwise transformed. In this case, removing the
	   apostrophe might reduce the risk of SQL injection, but it
	   would produce incorrect behavior because the wrong name
	   would be recorded.
       
         
cwe_id: CWE-565
cwe_name: Reliance on Cookies without Validation and Integrity Checking
description: The product relies on the existence or values of cookies when performing security-critical operations, but it does not properly ensure that the setting is valid for the associated user.
extended_description: Attackers can easily modify cookies, within the browser or by implementing the client-side code outside of the browser. Reliance on cookies without detailed validation and integrity checking can allow attackers to bypass authentication, conduct injection attacks such as SQL injection and cross-site scripting, or otherwise modify inputs in unexpected ways.
         
cwe_id: CWE-807
cwe_name: Reliance on Untrusted Inputs in a Security Decision
description: The product uses a protection mechanism that relies on the existence or values of an input, but the input can be modified by an untrusted actor in a way that bypasses the protection mechanism.
extended_description: 
            Developers may assume that inputs such as cookies, environment variables, and hidden form fields cannot be modified. However, an attacker could change these inputs using customized clients or other attacks. This change might not be detected. When security decisions such as authentication and authorization are made based on the values of these inputs, attackers can bypass the security of the software.
            Without sufficient encryption, integrity checking, or other mechanism, any input that originates from an outsider cannot be trusted.
         
         

Fix information:
repo_url: https://github.com/gofiber/fiber
commit_details: repo_url: https://github.com/gofiber/fiber
author: Jason McNeil
author_date: 2023-10-16 04:06:30-03:00
msg: 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.
repository: repo_url: https://github.com/gofiber/fiber
repo_name: gofiber/fiber
description: ⚡️ Express inspired web framework written in Go
repo_language: Go
owner: gofiber
file_changes: filename: csrf.md
change_type: ModificationType.MODIFY
diff: @@ -42,10 +42,20 @@ When using this method, pre-sessions are required and will be created if a sessi
 
 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.

programming_language: Markdown
filename: csrf.go
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,7 @@ package csrf
 
 import (
 	"errors"
+	"net/url"
 	"reflect"
 	"time"
 
@@ -63,10 +64,10 @@ func New(config ...Config) fiber.Handler {
 			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:
@@ -92,13 +93,13 @@ 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
@@ -108,7 +109,7 @@ func New(config ...Config) fiber.Handler {
 				// 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
 			}
 		}
 
@@ -137,9 +138,9 @@ func New(config ...Config) fiber.Handler {
 	}
 }
 
-// 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)
 	}
@@ -223,8 +224,15 @@ func refererMatchesHost(c *fiber.Ctx) error {
 	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
 }

programming_language: Go
filename: csrf_test.go
change_type: ModificationType.MODIFY
diff: @@ -4,6 +4,7 @@ import (
 	"net/http/httptest"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/gofiber/fiber/v2"
 	"github.com/gofiber/fiber/v2/middleware/session"
@@ -153,6 +154,170 @@ func Test_CSRF_WithSession(t *testing.T) {
 	}
 }
 
+// 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()
@@ -260,6 +425,8 @@ func Test_CSRF_From_Form(t *testing.T) {
 	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)
@@ -393,7 +560,7 @@ func Test_CSRF_From_Custom(t *testing.T) {
 		selectors := strings.Split(body, "=")
 
 		if len(selectors) != 2 || selectors[1] == "" {
-			return "", errMissingParam
+			return "", ErrMissingParam
 		}
 		return selectors[1], nil
 	}
@@ -421,6 +588,8 @@ func Test_CSRF_From_Custom(t *testing.T) {
 	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)
@@ -447,17 +616,67 @@ func Test_CSRF_Referer(t *testing.T) {
 	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")
@@ -490,7 +709,6 @@ func Test_CSRF_DeleteToken(t *testing.T) {
 	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)
@@ -503,7 +721,6 @@ func Test_CSRF_DeleteToken(t *testing.T) {
 	}
 	h(ctx)
 
-	ctx.Request.Header.SetMethod(fiber.MethodGet)
 	ctx.Request.Reset()
 	ctx.Response.Reset()
 	ctx.Request.Header.SetMethod(fiber.MethodPost)
@@ -559,7 +776,6 @@ func Test_CSRF_DeleteToken_WithSession(t *testing.T) {
 	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)
@@ -619,7 +835,7 @@ func Test_CSRF_ErrorHandler_EmptyToken(t *testing.T) {
 	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"))
 	}
 
@@ -671,6 +887,8 @@ func Test_CSRF_ErrorHandler_MissingReferer(t *testing.T) {
 	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")

programming_language: Go
filename: extractors.go
change_type: ModificationType.MODIFY
diff: @@ -7,11 +7,11 @@ import (
 )
 
 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.
@@ -19,7 +19,7 @@ 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
 	}
@@ -30,7 +30,7 @@ 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
 	}
@@ -41,7 +41,7 @@ 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
 	}
@@ -52,7 +52,7 @@ 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
 	}
@@ -63,7 +63,7 @@ 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
 	}

programming_language: Go
filename: helpers.go
change_type: ModificationType.MODIFY
diff: @@ -7,3 +7,7 @@ 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
+}

programming_language: Go