Skip to content

Commit 8391faf

Browse files
authored
Merge pull request #776 from metosin/feat/337-fix-external-redirect
fix: redirect-trailing-slash-handler won't make external redirects
2 parents 248200a + 71a777b commit 8391faf

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

modules/reitit-ring/src/reitit/ring.cljc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@
173173
(letfn [(maybe-redirect [{:keys [query-string] :as request} path]
174174
(if (and (seq path) (r/match-by-path (::r/router request) path))
175175
{:status (if (= (:request-method request) :get) 301 308)
176-
:headers {"Location" (if query-string (str path "?" query-string) path)}
176+
:headers {"Location" (let [path (str/replace-first path #"^/+" "/")] ; Locations starting with // redirect to another hostname. Avoid these due to security implications.
177+
(if query-string (str path "?" query-string) path))}
177178
:body ""}))
178179
(redirect-handler [request]
179180
(let [uri (:uri request)]

test/cljc/reitit/ring_test.cljc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,31 @@
416416
:get "/slash-less//" "/slash-less?kikka=kukka"
417417
:post "/with-slash" "/with-slash/?kikka=kukka"
418418
:post "/slash-less/" "/slash-less?kikka=kukka"
419-
:post "/slash-less//" "/slash-less?kikka=kukka"))))))
419+
:post "/slash-less//" "/slash-less?kikka=kukka"))))
420+
421+
;; See issue #337
422+
(testing "Avoid external redirects"
423+
(let [app (ring/ring-handler
424+
(ring/router [["*" {:get (constantly nil)}]])
425+
(ring/redirect-trailing-slash-handler))
426+
resp (fn [uri & [query-string]]
427+
(let [r (app {:request-method :get :uri uri :query-string query-string})]
428+
{:status (:status r)
429+
:Location (get-in r [:headers "Location"])}))]
430+
(testing "without query params"
431+
(is (= {:status 301 :Location "/malicious.com/foo/"} (resp "//malicious.com/foo")))
432+
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "//malicious.com/foo/")))
433+
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "//malicious.com/foo//")))
434+
(is (= {:status 301 :Location "/malicious.com/foo/"} (resp "///malicious.com/foo")))
435+
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "///malicious.com/foo/")))
436+
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "///malicious.com/foo//"))))
437+
(testing "with query params"
438+
(is (= {:status 301 :Location "/malicious.com/foo/?bar=quux"} (resp "//malicious.com/foo" "bar=quux")))
439+
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "//malicious.com/foo/" "bar=quux")))
440+
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "//malicious.com/foo//" "bar=quux")))
441+
(is (= {:status 301 :Location "/malicious.com/foo/?bar=quux"} (resp "///malicious.com/foo" "bar=quux")))
442+
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "///malicious.com/foo/" "bar=quux")))
443+
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "///malicious.com/foo//" "bar=quux"))))))))
420444

421445
(deftest async-ring-test
422446
(let [promise #(let [value (atom ::nil)]

0 commit comments

Comments
 (0)