I'm attempting to implement the Ring-Anti-Forgery library via setting the X-CSRF-Token in the header.
Since I am using static html files I found the built-in hiccup helper, which sets the token in the form, to be useless.
This is my first stab at using Clojure for web development so I'm guessing that I am completely missing what should be obvious to someone with experience.
The instructions from the README state:
The middleware also looks for the token in the X-CSRF-Token and X-XSRF-Token header fields. This behavior can be customized further using the :read-token option:
(defn get-custom-token [request]
(get-in request [:headers "x-forgery-token"]))
(def app
(-> handler
(wrap-anti-forgery {:read-token get-custom-token})
(wrap-session)))
I have added the above to handler.clj without any success.
project.clj
(defproject hooktale "0.0.1"
:description "Hooktale iOS App Website"
:url "http://www.hooktale.com"
:repositories {"sonartype releases" "https://oss.sonatype.org/content/repositories/releases/"}
:source-paths ["src/clj" "src/cljs"]
:dependencies [[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-2080"]
[org.clojure/java.jdbc "0.3.0-beta2"]
[compojure "1.1.6"]
[com.mchange/c3p0 "0.9.5-pre5"]
[org.postgresql/postgresql "9.3-1100-jdbc4"]
[ring-anti-forgery "0.3.0"]]
:plugins [[lein-ring "0.8.8"]
[lein-cljsbuild "1.0.1-SNAPSHOT"]]
:ring {:handler hooktale.handler/app}
:profiles {:dev {:plugins [[javax.servlet/servlet-api "2.5"]
[ring-mock "0.1.5"]]
:cljsbuild {:builds [{:source-paths ["src/cljs"]
:compiler {:optimizations :advanced
:pretty-print false
:output-to "resources/public/js/trout.js"}}]}}})
handler.clj
(ns hooktale.handler
(:require [compojure.core :refer [defroutes GET POST]]
[compojure.handler :refer [site]]
[compojure.route :refer [resources not-found]]
[clojure.java.io :refer [resource]]
[ring.middleware.anti-forgery :refer :all]
[ring.middleware.session :refer [wrap-session]]
[hooktale.controllers.prospect :refer [create-prospect]]))
(defn get-custom-token [request]
(get-in request [:headers "x-forgery-token"]))
(defroutes app-routes
(GET "/" [] (resource "public/index.html"))
(POST "/" [email] (create-prospect email))
(resources "/")
(not-found "Not Found"))
(def app
(->
(site app-routes)
(wrap-anti-forgery {:read-token get-custom-token})
(wrap-session)))
Sending a request to the page returns the following info:
curl -I localhost:3000
HTTP/1.1 200 OK
Date: Fri, 06 Dec 2013 16:30:45 GMT
Set-Cookie: ring-session=0b2a477f-9352-4fd8-a3c3-a6b6f8d9e063;Path=/
Content-Length: 0
Server: Jetty(7.6.8.v20121106)
curl -X POST -d '{:email "[email protected]"}' localhost:3000
<h1>Invalid anti-forgery token</h1>
The function in ring.middleware.anti-forgery that I thought would allow me to set the token in the header without having to set the hidden token value inside the form field.
(defn- default-request-token [request]
(or (-> request form-params (get "__anti-forgery-token"))
(-> request :headers (get "x-csrf-token"))
(-> request :headers (get "x-xsrf-token"))))
If I am reading it correctly, it will check for the token in the form, if not there it will check for the x-csrf-token then the x-xsrf-token in the header.
I seem to be having difficulty in actually setting the value of x-csrf-token or x-xsrf-token in the header.
Curl responses
View the Cookie set by ring-session:
curl -I localhost:3000
HTTP/1.1 200 OK
Date: Fri, 06 Dec 2013 19:52:22 GMT
Set-Cookie: ring-session=b02dd6f8-74b8-4ce0-a1d6-07251dadb9aa;Path=/
Content-Length: 0
Server: Jetty(7.6.8.v20121106)
Setting the X-CSRF-Token:
curl -v --header "X-CSRF-Token: b02dd6f8-74b8-4ce0-a1d6-07251dadb9aa;Path=/" -X POST -d '{:email "[email protected]"}' localhost:3000
* Adding handle: conn: 0x7fd3ab004000
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7fd3ab004000) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 3000 (#0)
* Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> POST / HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:3000
> Accept: */*
> X-CSRF-Token: b02dd6f8-74b8-4ce0-a1d6-07251dadb9aa;Path=/
> Content-Length: 27
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 27 out of 27 bytes
< HTTP/1.1 403 Forbidden
< Date: Fri, 06 Dec 2013 19:54:52 GMT
< Content-Type: text/html;charset=ISO-8859-1
< Content-Length: 35
* Server Jetty(7.6.8.v20121106) is not blacklisted
< Server: Jetty(7.6.8.v20121106)
<
* Connection #0 to host localhost left intact
<h1>Invalid anti-forgery token</h1>
The automatic generation of antiforgery tokens for HTML form elements happens when the <form> tag contains the method="post" attribute and either of the following are true: The action attribute is empty ( action="" ). The action attribute isn't supplied ( <form method="post"> ).
Anti-Forgery TokensOne token is sent as a cookie. The other is placed in a hidden form field. The tokens are generated randomly so that an adversary cannot guess the values. When the client submits the form, it must send both tokens back to the server.
Cross-site request forgery (CSRF) is an attack that tricks an end user into executing undesirable actions while logged into a web application. Taking advantage of the authenticated user's permissions, a CSRF attack dupes the victim into performing specific actions that benefit the attacker.
The basic purpose of ValidateAntiForgeryToken attribute is to prevent cross-site request forgery attacks. A cross-site request forgery is an attack in which a harmful script element, malicious command, or code is sent from the browser of a trusted user.
I created a repository https://github.com/edbond/CSRF with example. Readme describes process needed to POST requests with CSRF token.
In short (for API calls, curl):
Get CSRF Token and session cookie from server (server will store CSRF token inside your session which identified by cookie)
Send X-CSRF-Token and cookie along with POST request (server will compare CSRF token with that stored inside your session identified by cookie)
cookie -> session -> CSRF-Token
For HTML, form POSTing it should be enough to include (anti-forgery-field) to forms. Note, you can also send form field instead of header using curl.
HTH
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With