Commit ab95b83440
Verified · cmc
Layout: unified · split
internal/httpd/flash.go +9 −3
| @@ -60,11 +60,17 @@ func (s *Server) reauthNotice(w http.ResponseWriter, notice, path string) bool { | ||
| 60 | 60 | |
| 61 | 61 | const nextCookie = "gitbay_next" |
| 62 | 62 | |
| 63 | // localPath reports whether p is a path on this host. Browsers read a | |
| 64 | // leading `/\` like "//", so it is refused too. | |
| 65 | func localPath(p string) bool { | |
| 66 | return strings.HasPrefix(p, "/") && !strings.HasPrefix(p, "//") && !strings.HasPrefix(p, "/\\") | |
| 67 | } | |
| 68 | ||
| 63 | 69 | // setNext remembers the local path an anonymous visitor asked for, so |
| 64 | 70 | // the login that follows can return there. Only a GET path is stored: |
| 65 | 71 | // a POST must not be replayed. |
| 66 | 72 | func (s *Server) setNext(w http.ResponseWriter, path string) { |
| 67 | if !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") || len(path) > 300 { | |
| 73 | if !localPath(path) || len(path) > 300 { | |
| 68 | 74 | return |
| 69 | 75 | } |
| 70 | 76 | http.SetCookie(w, &http.Cookie{ |
| @@ -83,7 +89,7 @@ func (s *Server) takeNext(w http.ResponseWriter, r *http.Request) string { | ||
| 83 | 89 | } |
| 84 | 90 | http.SetCookie(w, s.clearCookie(nextCookie, http.SameSiteLaxMode)) |
| 85 | 91 | p, err := url.QueryUnescape(c.Value) |
| 86 | if err != nil || !strings.HasPrefix(p, "/") || strings.HasPrefix(p, "//") { | |
| 92 | if err != nil || !localPath(p) { | |
| 87 | 93 | return "" |
| 88 | 94 | } |
| 89 | 95 | return p |
| @@ -97,7 +103,7 @@ func (s *Server) peekNext(r *http.Request) string { | ||
| 97 | 103 | return "" |
| 98 | 104 | } |
| 99 | 105 | p, err := url.QueryUnescape(c.Value) |
| 100 | if err != nil || !strings.HasPrefix(p, "/") || strings.HasPrefix(p, "//") { | |
| 106 | if err != nil || !localPath(p) { | |
| 101 | 107 | return "" |
| 102 | 108 | } |
| 103 | 109 | return p |
internal/httpd/flash_test.go added +19
| @@ -0,0 +1,19 @@ | ||
| 1 | package httpd | |
| 2 | ||
| 3 | import "testing" | |
| 4 | ||
| 5 | func TestLocalPath(t *testing.T) { | |
| 6 | for p, want := range map[string]bool{ | |
| 7 | "/settings": true, | |
| 8 | "/a/b?c=d": true, | |
| 9 | "": false, | |
| 10 | "settings": false, | |
| 11 | "//evil.example": false, | |
| 12 | `/\evil.example`: false, | |
| 13 | "https://x.test/": false, | |
| 14 | } { | |
| 15 | if got := localPath(p); got != want { | |
| 16 | t.Errorf("localPath(%q) = %v, want %v", p, got, want) | |
| 17 | } | |
| 18 | } | |
| 19 | } | |