RSTR-RDR-003 — Go http.Redirect with request input

Summary

Go counterpart of RSTR-RDR-002. A handler redirects to a URL read from r.FormValue or r.URL.Query().Get without checking the host or scheme.

Severity

Medium.

Languages

Go.

What rastray flags

func postLogin(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r,
        r.URL.Query().Get("next"),                       // ← flagged
        http.StatusFound)
}

What rastray deliberately does not flag

  • Redirects to literal paths.
  • Redirects whose target was assembled via url.URL{Path: ...} from validated components.

How to fix it

Reject absolute URLs and enforce an allow-list:

var allowedHosts = map[string]struct{}{
    "app.example.com": {},
}

func safeRedirect(w http.ResponseWriter, r *http.Request, raw string) {
    u, err := url.Parse(raw)
    if err != nil {
        http.Redirect(w, r, "/", http.StatusFound)
        return
    }
    // Relative paths only? Or allow-listed external hosts.
    if u.Host == "" || allowedHosts[u.Host] != struct{}{} {
        // host empty => relative; or explicitly trusted
    } else {
        http.Redirect(w, r, "/", http.StatusFound)
        return
    }
    http.Redirect(w, r, raw, http.StatusFound)
}

For most apps the simpler rule — only allow redirects whose target starts with / and is not // (protocol-relative) — is enough:

target := r.URL.Query().Get("next")
if !strings.HasPrefix(target, "/") || strings.HasPrefix(target, "//") {
    target = "/"
}
http.Redirect(w, r, target, http.StatusFound)

References