Wie verwende ich die Verhinderung von Cross-Site Request Forgery in einem Clojure-HTTP-Client, der eine Ring-REST-Route aufruft, korrekt?

Dec 30 2020

Ich lerne immer noch Clojure (und all die dazugehörigen Bibliotheken ...). Wenn ich also etwas Dummes in meiner Unwissenheit tue, kann ich darauf hinweisen :-)

Ich habe Probleme beim Aufrufen von REST-Endpunkten über die POSTMethode aus dem Clientcode. Meine Routen werden mit verpackt (ring.middleware.defaults/wrap-defaults <my-routes> site-defaults)(ich glaube, dies ist eine ziemlich gute Idee, wenn jemals ein solcher Code in der Produktion ausgeführt werden soll). Dieser Wrapper wendet verschiedene andere Wrapper an, darunter ring.middleware.anti-forgery/wrap-anti-forgeryunter anderem ein CSRF- oder XSRF-Präventionsschema (Cross-Site Request Forgery). Die Standardeinstellung (die ich verwende) ist eine Synchronizer-Token- (oder Sitzungs-) Strategie .

Das Aufrufen desselben REST-Endpunkts über GETfunktioniert einwandfrei (da der CSRF-Schutz nicht angewendet GETwird HEADund OPTIONSAufrufe - siehe ring.middleware.anti-forgery/get-request?), aber die Verwendung POST(oder einer der anderen Methoden) führt zu einer 403 - Ungültigen Anti-Fälschungs-Token- Antwort.

(Wie im folgenden Beispielcode zu sehen) Ich weiß, wie man der HTTP-Anforderung einen "X-CSRF-Token" - oder "X-XSRF-Token" -Header hinzufügt. (Da dies ein REST-Aufruf ist, füge ich kein verstecktes "__anti-forgery-token" -Feld hinzu, wie in dieser Frage und Antwort vorgeschlagen , obwohl entweder einer der Header oder das Formularfeld für den Wrapper ausreichen würde - siehe ring.middleware.anti-forgery/default-request-token.) Eher Wenn ich den Code richtig verstehe, ergibt sich mein Problem aus der Tatsache, dass die Standardstrategie das obige Token mit einem Sitzungs-Token-Wert vergleicht, der abgerufen wird durch ring.middleware.anti-forgery.session/session-token:

(defn- session-token [request]
  (get-in request [:session :ring.middleware.anti-forgery/anti-forgery-token]))

Ich habe keine Ahnung, wie die Sitzungsinformationen im HTTP-Clientaufruf korrekt eingerichtet werden sollen. (Jeder HTTP-Client ist ausreichend, da das oben erwähnte 403-Ergebnis vom Middleware-Wrapper generiert wird. Für die folgende Demo verwende ich daher die einfache ring.mock.request.)

Hier ist ein minimaler Code, der zeigt, was ich bisher habe. Es definiert die Routen und Handler und versucht dann, sie aus einem Komponententest aufzurufen.

(ns question.rest
  (:require [compojure.core :refer :all]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults secure-site-defaults]]
            [ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
            [clojure.test :refer :all]
            [ring.mock.request :as mock]))

(defroutes
  exmpl-routes
  (ANY "/" [] "Site up OK.")
  (GET "/aft" [] (force *anti-forgery-token*)))

(def exmpl (wrap-defaults exmpl-routes site-defaults))

(deftest test-mock-fail
  (testing "POST to root route"
    (let [
          ; In a normal web app, the view/page would be GET'ed from the server, which would
          ; include the Anti-Forgery Token in it, and have the POST as an action on it. Hence
          ; the way atf is done here...
          aft (:body (exmpl (mock/request :get "https://localhost:8443/aft")))
          request (-> (mock/request :post "https://localhost:8443/")
                      (mock/header "X-CSRF-Token" aft))
          _ (println request)
          response (exmpl request)
          _ (println response)
          ]
      (is (= 200 (:status response)))                       ;;403
      (is (= "Site up OK." (:body response))))))            ;;Invalid anti-forgery token

Die (println)Aufrufe zeigen Folgendes (einige Formatierungen angewendet):

Anfrage:

{  :protocol "HTTP/1.1", 
   :server-port 8443, 
   :server-name "localhost", 
   :remote-addr "localhost", 
   :uri "/post", 
   :scheme :https, 
   :request-method :post, 
   :headers {  "host" "localhost:8443",
               "x-csrf-token" "<long token value here>" }  }

Antwort:

{  :status 403, 
   :headers {  "Content-Type" "text/html; charset=utf-8",
               "X-XSS-Protection" "1; mode=block",
               "X-Frame-Options" "SAMEORIGIN",
               "X-Content-Type-Options" "nosniff" }, 
   :body "<h1>Invalid anti-forgery token</h1>"  }

Ich konnte feststellen GET, dass sich die Tutorials hauptsächlich auf die Methode konzentrieren und davon ausgehen, dass die Endpunkte / Routen aus HTML aufgerufen werden, das vom Server bereitgestellt wird (einschließlich Sitzungsinformationen). Ich fühle mich im Moment ein bisschen festgefahren.

Antworten

2 SeanCorfield Dec 30 2020 at 06:38

Für eine REST-API sollten Sie api-defaultsoder secure-api-defaults NICHT verwenden site-defaults .

Die Fälschungsschutzmaschine wurde für Websites entwickelt, auf denen die App eine generiert, formund kann das generierte Token enthalten, das als Teil der Formularübermittlung zurückgesendet werden soll. POSTSie ist nicht für die Verwendung mit einer REST-API vorgesehen.