v0.332.0 (live rc.2 + security review): the pass redirect is absolute; no pass for a cross-site request; no dot-segments in to (R-1100)
gates / gates (push) Successful in 47s
gates / gates (push) Successful in 47s
This commit is contained in:
@@ -184,7 +184,8 @@ func (s *Server) filesCookieKey(r *http.Request) (present, ok bool) {
|
||||
return true, s.filesSessionAlive(parts[0])
|
||||
}
|
||||
|
||||
// safeFilesPath accepts a FileBrowser path to go to after the pass: "/" or under /files/ — never another host.
|
||||
// safeFilesPath accepts a FileBrowser path to go to after the pass: "/" or under /files/ — never another host, and never
|
||||
// a dot-segment that a browser would resolve out of /files/ (onto FileBrowser's API, say).
|
||||
func safeFilesPath(p string) string {
|
||||
if p == "" || p == "/" {
|
||||
return "/"
|
||||
@@ -192,6 +193,16 @@ func safeFilesPath(p string) string {
|
||||
if !strings.HasPrefix(p, "/files/") || strings.HasPrefix(p, "//") || strings.ContainsAny(p, "\\\r\n") {
|
||||
return "/"
|
||||
}
|
||||
path, _, _ := strings.Cut(p, "?")
|
||||
dec, err := url.PathUnescape(path)
|
||||
if err != nil {
|
||||
return "/"
|
||||
}
|
||||
for _, seg := range strings.Split(dec, "/") {
|
||||
if seg == ".." || seg == "." {
|
||||
return "/"
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -229,6 +240,14 @@ func (s *Server) ServeFilesOpen(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
// A pass is minted only for the household's own press (the dashboard page: same-origin) or a typed or bookmarked
|
||||
// address (none) — never for a request another site started. A browser that sends no Sec-Fetch-Site (an old one) is
|
||||
// let through: the pass opens only the household's own files on this box. Pinned by TestFilesSSO_R1100_CrossSiteNoPass.
|
||||
if sfs := r.Header.Get("Sec-Fetch-Site"); sfs == "cross-site" {
|
||||
s.logger.Printf("[WARN] [web] files sign-in: a pass asked for from another site — refused, visitor %s", clientIP(r))
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
c, err := r.Cookie(sessionCookieName)
|
||||
if err != nil || !s.isValidSession(c.Value) {
|
||||
http.Redirect(w, r, "/login?"+url.Values{"next": {filesOpenPath + "?" + url.Values{"to": {to}}.Encode()}}.Encode(), http.StatusFound)
|
||||
@@ -291,7 +310,10 @@ func (s *Server) ServeFilesAuth(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, &http.Cookie{Name: fileBrowserJWTCookie, Value: "", Path: "/", Domain: host, MaxAge: -1, HttpOnly: true, Secure: true})
|
||||
s.logger.Printf("[INFO] [web] files sign-in: a signed-in household browser passed — visitor %s", clientIP(r))
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
http.Redirect(w, r, safeFilesPath(rd), http.StatusFound)
|
||||
// ABSOLUTE, like the setup gate's: traefik makes a relative Location in a forwardAuth answer absolute against
|
||||
// the AUTH server's address — measured on 9201, "/files/" became http://felhom-controller:8080/files/.
|
||||
// Pinned by TestFilesSSO_R1100_HouseholdOpensTheFolderWithoutALogin.
|
||||
http.Redirect(w, r, "https://"+host+safeFilesPath(rd), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user