Repository navigation
Closed
Description
The redirect middlewares in middleware/redirect.go (HTTPSRedirect, HTTPSWWWRedirect, HTTPSNonWWWRedirect, WWWRedirect, NonWWWRedirect) construct the target Location URL by directly concatenating scheme, req.Host, and req.RequestURI without validating the host or sanitizing the URI.
This introduces CWE-601: URL Redirection to Untrusted Site ('Open Redirect') and related redirect construction issues:
- Open Redirect (CWE-601) via bare
www.or emptyHostheader:
InNonWWWRedirectandHTTPSNonWWWRedirect, stripping"www."from a request withHost: www.(orHost: www.:80, or an emptyHostheader inHTTPSRedirect) leaves an empty host (""). Combined with a request path like/attacker.com/login, the middleware emitsLocation: https:///attacker.com/login(orhttp:///attacker.com/login), which browsers normalize tohttps://attacker.com/login. Similarly, aHostheader containing authority delimiters (e.g.attacker.com@example.comorattacker.com/path) is concatenated unvalidated into the redirect URL. - Open Redirect / Path Manipulation via unsanitized
req.RequestURI:
UnlikeAddTrailingSlash/RemoveTrailingSlashinmiddleware/slash.go,redirect.godoes not pass the URI throughsanitizeURI()(leaving double slashes like//attacker.comand control characters unescaped) and does not handle cases wherereq.RequestURIis empty or in absolute-form (e.g.,GET http://example.com/path HTTP/1.1). - Case-sensitive
www.prefix matching:
strings.HasPrefix(host, "www.")is case-sensitive, soHost: WWW.example.comis redirected tohttps://www.WWW.example.combyHTTPSWWWRedirect/WWWRedirectand is not stripped byNonWWWRedirect. - WebSocket scheme propagation (
ws/wss):
c.Scheme()can return"ws"or"wss"when extracted from forwarding headers (X-Forwarded-Proto: ws), causingWWWRedirect/NonWWWRedirectto emitLocation: ws://...instead ofhttp://orhttps://.
How to Reproduce?
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
)
func TestRedirectOpenRedirectCWE601(t *testing.T) {
e := echo.New()
e.Pre(middleware.HTTPSNonWWWRedirect())
e.GET("/*", func(c *echo.Context) error {
return c.String(http.StatusOK, "OK")
})
req := httptest.NewRequest(http.MethodGet, "/attacker.com/login", nil)
req.Host = "www."
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
// Redirects to "https:///attacker.com/login", which browsers resolve to https://attacker.com/login (CWE-601)
if loc := rec.Header().Get(echo.HeaderLocation); loc == "https:///attacker.com/login" {
t.Errorf("vulnerable to CWE-601 open redirect, got Location: %s", loc)
}
}Proposed Solution
- Validate
req.Hostbefore redirecting (ensure non-empty host after strippingwww., disallow leading:or., control characters, and/\@?#delimiters). - Match and strip the
www.prefix case-insensitively. - Fall back to
req.URL.RequestURI()whenreq.RequestURIis empty or does not start with/, and sanitize withsanitizeURI(). - Normalize
"ws"->"http"and"wss"->"https"in the redirect middleware.
References
Version/commit
v5.4.4
Metadata
Metadata
Assignees
Labels
No labels
Activity
fix(middleware): harden redirect URL construction in v4
fix(middleware): harden redirect URL construction in v4 (#3224)