GHSA-p64j-f4x9-wq66 - Ech0's OAuth redirect URI validation ignores path component, enables exchange-co
GHSA-p64j-f4x9-wq66 - Ech0's OAuth redirect URI validation ignores path component, enables exchange-co
GHSA-p64j-f4x9-wq66 HIGH go/github.com/lin-snow/Ech0
CVE:
Summary
parseAndValidateClientRedirect at internal/service/auth/auth.go:448 validates OAuth client-redirect URIs by comparing only scheme and host against the admin-configured allowlist. Path, query, and fragment are ignored. The initiator at /oauth/:provider/login embeds the caller-supplied redirect_uri verbatim into the signed state JWT without any validation at login time. Alice submits a crafted redirect_uri whose host matches an allowed origin but whose path points to any page on that host. After the provider exchange, Ech0 redirects the victim to the attacker-chosen path with the one-time exchange code in the query string. If the chosen path leaks the URL via Referer, analytics, or an open redirect, the attacker trades the code at POST /api/auth/exchange for the victim's access and refresh tokens. RFC 6749 §3.1.2 requires exact redirect URI matching.
Details
Validation at internal/service/auth/auth.go:448:
matched := false
for _, item := range allowed {
allowURL, parseErr := url.Parse(strings.TrimSpace(item))
if parseErr != nil || allowURL == nil || allowURL.Host == "" {
continue
}
if strings.EqualFold(redirectURL.Scheme, allowURL.Scheme) &&
strings.EqualFold(redirectURL.Host, allowURL.Host) {
matched = true
break
}
}Scheme and host compared via EqualFold. Path, query, fragment all ignored. An allowlist entry of https://myecho.example.com/dashboard matches every https://myecho.example.com/<anything> the attacker sends.
Login flow at internal/service/auth/auth.go:141 (GetOAuthLoginURL) and the handler at internal/handler/auth/oauth.go:43:
redirectURI := ctx.Query("redirect_uri")
redirectURL, err := h.authService.GetOAuthLoginURL(provider, redirectURI)
// ...
ctx.Redirect(302, redirectURL)No validation at login. The raw redirect_uri query parameter is passed to GetOAuthLoginURL, which encodes it into the signed state JWT alongside the provider nam
📌 来源: GitHub-Advisory | 📅 2026-05-07