Come si utilizza correttamente la prevenzione della contraffazione di richieste tra siti in un client HTTP Clojure che chiama una route REST Ring?

Dec 30 2020

Sto ancora imparando Clojure (e tutte quelle biblioteche di accompagnamento ...), quindi se faccio qualcosa di stupido nella mia ignoranza, sentiti libero di segnalarlo :-)

Ho problemi a chiamare gli endpoint REST tramite il POSTmetodo dal codice client. I miei percorsi sono avvolti usando (ring.middleware.defaults/wrap-defaults <my-routes> site-defaults)(credo che questa sia un'idea abbastanza buona se mai tale codice deve essere eseguito in produzione). Questo wrapper applica vari altri wrapper, tra cui ring.middleware.anti-forgery/wrap-anti-forgery, che implementa (tra gli altri) uno schema di prevenzione Cross-Site Request Forgery (CSRF o XSRF): l'impostazione predefinita (che sto usando) è una strategia di token di sincronizzazione (o sessione) .

Chiamando lo stesso endpoint REST tramite GETfunziona bene (perché la protezione CSRF non viene applicato a GET, HEADe OPTIONSle chiamate - See ring.middleware.anti-forgery/get-request?), ma utilizzando POST(o uno degli altri metodi) si traduce in un 403 - anti-contraffazione di token non valido di risposta.

(Come si vede nel codice di esempio sotto) So come aggiungere un'intestazione "X-CSRF-Token" o "X-XSRF-Token" alla richiesta HTTP. (Poiché questa è una chiamata REST, non aggiungo un campo nascosto "__anti-contraffazione-token" come suggerito da questa domanda e risposta , sebbene una delle intestazioni o il campo del modulo sarebbe sufficiente per il wrapper - vedere ring.middleware.anti-forgery/default-request-token.) Piuttosto , se capisco correttamente il codice, il mio problema deriva dal fatto che la strategia predefinita confronta il token di cui sopra con un valore di token di sessione, che viene recuperato da ring.middleware.anti-forgery.session/session-token:

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

Non ho idea di come impostare correttamente le informazioni sulla sessione nella chiamata del client HTTP. (Qualsiasi client HTTP è sufficiente, poiché il risultato 403 sopra menzionato è generato dal wrapper middleware. Per la demo di seguito utilizzo quindi il semplice ring.mock.request.)

Ecco un po 'di codice minimo che mostra quello che ho fino ad ora. Definisce le route e i gestori, quindi prova a chiamarli da uno unit test.

(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

Le (println)chiamate mostrano quanto segue (alcune formattazioni applicate):

Richiesta:

{  :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>" }  }

Risposta:

{  :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>"  }

I tutorial che ho potuto trovare si concentrano principalmente sul GETmetodo e sembrano presumere che gli endpoint / rotte sarebbero chiamati da HTML, che è servito dal server (che include informazioni sulla sessione). Quindi mi sento un po 'bloccato in questo momento.

Risposte

2 SeanCorfield Dec 30 2020 at 06:38

Per un'API REST dovresti usare api-defaultso secure-api-defaults NOT site-defaults .

Il meccanismo antifalsificazione è progettato per i siti Web in cui l'app sta generando un forme può includere il token generato da restituire come parte dell'invio del modulo POST- non è destinato all'uso con un'API REST.