64

I'm using curl to test one of my Django forms. The calls I've tried (with errors from each, and over multiple lines for readability):

(1):

curl
-d "{\"email\":\"test@test.com\"}"
--header "X-CSRFToken: [triple checked value from the source code of a page I already loaded from my Django app]"
--cookie "csrftoken=[same csrf value as above]"
http://127.0.0.1:8083/registrations/register/

(with http header and csrftoken in cookie) results in a 400 error with no data returned.

(2):

curl
-d "{a:1}"
--header "X-CSRFToken:[as above]"
--cookie "csrftoken=[as above];sessionid=[from header inspection in Chrome]"
http://127.0.0.1:8083/registrations/register/

(as in (1) but no spaces in header property declaration, and with sessionid in cookie too) results in the same 400 error with no data returned.

(3):

curl
-d "{a:1}"
--header "X-CSRFToken:[as above]"
http://127.0.0.1:8083/registrations/register/

(only http header with X-CSRFToken, no cookie) results in error code 403, with message: CSRF cookie not set.

How can I test my form with curl? What factors am I not considering besides cookie values and http headers?

Sergey Weiss
  • 5,944
  • 8
  • 31
  • 40
Trindaz
  • 17,029
  • 21
  • 82
  • 111
  • 1
    can you tell me how you get the X-CSRF-Token? `--header "X-CSRFToken: [triple checked value from the source code of a page I already loaded from my Django app]"` – Maria Ines Parnisari Oct 13 '13 at 22:00

6 Answers6

37

A mixture of Damien's response and your example number 2 worked for me. I used a simple login page to test, I expect that your registration view is similar. Damien's response almost works, but is missing the sessionid cookie.

I recommend a more robust approach. Rather than manually entering the cookies from other requests, try using curl's built in cookie management system to simulate a complete user interaction. That way, you reduce the chance of making an error:

$ curl -v -c cookies.txt -b cookies.txt host.com/registrations/register/
$ curl -v -c cookies.txt -b cookies.txt -d "email=user@site.com&a=1&csrfmiddlewaretoken=<token from cookies.txt>" host.com/registrations/register/

The first curl simulates the user first arriving at the page with a GET request, and all the necessary cookies are saved. The second curl simulates filling in the form fields and sending them as a POST. Note that you have to include the csrfmiddlewaretoken field in the POST data, as suggested by Damien.

Kevin S.
  • 628
  • 6
  • 9
  • 2
    I've posted a fully coded answer based on Kevin's response in http://stackoverflow.com/questions/21306515/how-to-curl-an-authenticated-django-app/24376188#24376188. Django also requires you to specify a referer (`-e`). – Peterino Jun 23 '14 at 23:35
16

Try:

curl
 -d "email=test@test.com&a=1"
 http://127.0.0.1:8083/registrations/register/

Notice especially the format of the -d argument.

However, this probably won't work, as your view likely needs a POST request instead of a GET request. Since it will be modifying data, not just returning information.

CSRF protection is only required for 'unsafe' requests (POST, PUT, DELETE). It works by checking the 'csrftoken' cookie against either the 'csrfmiddlewaretoken' form field or the 'X-CSRFToken' http header.

So:

curl
 -X POST
 -d "email=test@test.com&a=1&csrfmiddlewaretoken={inserttoken}"
 --cookie "csrftoken=[as above]"
 http://127.0.0.1:8083/registrations/register/

It's also possible to use --header "X-CSRFToken: {token}" instead of including it in the form data.

Damien Ayers
  • 1,551
  • 10
  • 17
10

I worked with curl like this

  • You have to submit csrftoken in header as X-CSRFToken.
  • You have to submit form data in JSON format. Demo,

First we will fetch csrf_token & store in cookie.txt (or cookie.jar as they call it)

$ curl -c cookie.txt http://localhost.com:8000/ 

cookie.txt content

# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
localhost.com  FALSE   /   FALSE   1463117016  csrftoken   vGpifQR12BxT07moOohREGmuKp8HjxaE

Next we resend the username, password in json format. (you may send it in normal way). Check the json data escape.

$curl --cookie cookie.txt http://localhost.com:8000/login/   -H "Content-Type: application/json" -H "X-CSRFToken: vGpifQR12BxT07moOohREGmuKp8HjxaE" -X POST -d "{\"username\":\"username\",\"password\":\"password\"}" 
{"status": "success", "response_msg": "/"}
$

you can store the returns new csrf_token session cookie in same file or new file (I have stored in same file using option -c.)

$curl --cookie cookie.txt http://localhost.com:8000/login/   -H "Content-Type: application/json" -H "X-CSRFToken: kVgzzB6MJk1RtlVnyzegEiUs5Fo3VRqF" -X POST -d "{\"username\":\"username\",\"password\":\"password\"}" -c cookie.txt

-Content of cookie.txt

# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

localhost.com  FALSE   /   FALSE   1463117016  csrftoken   vGpifQR12BxT07moOohREGmuKp8HjxaE
#HttpOnly_localhost.com    FALSE   /   FALSE   1432877016  sessionid   cg4ooly1f4kkd0ifb6sm9p

When you store new csrf_token & session id cookie in cookie.txt, you can use same cookie.txt across the website.

You am reading cookies from previous request from cookie.txt (--cookie) and writing new cookies from response in same cookie.txt (-c).

Reading & submitting form now works with csrf_token & session id.

$curl --cookie cookie.txt http://localhost.com:8000/home/
Timo
  • 2,922
  • 3
  • 29
  • 28
Netro
  • 7,119
  • 6
  • 40
  • 58
  • `curl -c cookie.txt http://localhost.com:8000/` does not save the file in my case if invoked from cmd. – Timo Mar 14 '18 at 14:08
  • grab the `session` and `csrf` `cookie` like [here](https://askubuntu.com/a/161814/321926) – Timo Mar 14 '18 at 14:38
6

Here is how i did it, using the rest framework tutorial

open a browser e.g. chrome then pressing F12 open the developer tab and monitor the Network, login using your user credentials and get your CRSF token from monitoring the POST

then in curl execute:

curl http://127.0.0.1:8000/snippets/ \
 -X POST \
 -H "Content-Type: application/json" \
 -H "Accept: text/html,application/json" \
 -H "X-CSRFToken: the_token_value" \
 -H "Cookie: csrftoken=the_token_value" \
 -u your_user_name:your_password \
 -d '{"title": "first cookie post","code": "print hello world"}' 

I think its cleaner to not put the token in the body but rather the header using X-CSRFToken

Dr Manhattan
  • 13,537
  • 6
  • 45
  • 41
  • The `-H "X-CSRFToken: the_token_value"` attribute was required for me to get this to work. Full code, first to get the cookie `curl -v -c cookies.txt -b cookies.txt http://url.com`. I opened and copied out the `csrftoken` from `cookies.txt` then posted: `curl -v -c cookies.txt -b cookies.txt -H "X-CSRFToken:< the token >" --data "foo=bar&csrftoken=< the token >" http://url.com` – Kinsa Jan 26 '15 at 23:53
1

curl-auth-csrf is a Python-based open-source tool capable of doing this for you: "Python tool that mimics cURL, but performs a login and handles any Cross-Site Request Forgery (CSRF) tokens. Useful for scraping HTML normally only accessible when logged in."

This would be your syntax:

echo -n YourPasswordHere | ./curl-auth-csrf.py -i http://127.0.0.1:8083/registrations/register/ -d 'email=test@test.com&a=1' http://127.0.0.1:8083/registrations/register/

This will pass along the POST data as listed, but also to include the password passed via stdin. I assume that the page you visit after "login" is the same page.

Full disclosure: I'm the author of curl-auth-csrf.

j0nam1el
  • 181
  • 1
  • 3
  • do you have a way to store the cookie generated to a file, similar to how curl does it, so that other curl commands can run against that cookie after? I looked through your code and didn't see how to do that. If that was there, it would be very useful, as I need to do POSTs after the login, not GETs. – James Oravec Oct 19 '16 at 19:33
  • curl uses cookies in the old Netscape format. You can use Python's http.cookiejar.MozillaCookieJar to save() cookies in the format. For an example, check out [this answer](http://stackoverflow.com/a/25858635/3376504). Usually, however, this might be more difficult than required. For any curl requests that you'd like to make after having grabbed the cookies, you can simply append those URLs to the list as you invoke `curl-auth-csrf.py`. The script will apply the session cookies on your behalf. – j0nam1el Oct 23 '16 at 17:04
0

To make the Curl–Django communication work, I had to provide

  • the CSRF token in the X-CSRFToken header field;
  • the CSRF token in the Cookie header field;
  • the session identifier in the Cookie header field.
$ curl -v -X PUT -H "X-CSRFToken: {csrf_token}" --cookie "csrftoken={csrf_token};sessionid={session_id}" http://localhost:{port}{path}?{query}
*   Trying 127.0.0.1:{port}...
* Connected to localhost (127.0.0.1) port {port} (#0)
> PUT {path}?{query} HTTP/1.1
> Host: localhost:{port}
> User-Agent: curl/7.79.1
> Accept: */*
> Cookie: csrftoken={csrf_token};sessionid={session_id}
> X-CSRFToken: {csrf_token}
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 204 No Content
< Vary: Accept, Accept-Language, Cookie
< Allow: DELETE, PUT, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: fr-fr
< Content-Length: 0
< Server-Timing: TimerPanel_utime;dur=159.20299999999975;desc="User CPU time", TimerPanel_stime;dur=70.73100000000032;desc="System CPU time", TimerPanel_total;dur=229.93400000000008;desc="Total CPU time", TimerPanel_total_time;dur=212.03255653381348;desc="Elapsed time", SQLPanel_sql_time;dur=7.846832275390625;desc="SQL 7 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< X-Content-Type-Options: nosniff
< Referrer-Policy: origin,origin-when-cross-origin
< Cross-Origin-Opener-Policy: same-origin
< Server: Werkzeug/2.0.0 Python/3.9.13
< Date: Wed, 14 Sep 2022 16:27:04 GMT
< 
* Closing connection 0

Failed attempts

If I omit the CSRF token in the X-CSRFToken header field, I get a 403 (Forbidden) status code:

$ curl -v -X PUT --cookie "csrftoken={csrf_token};sessionid={session_id}" http://localhost:{port}{path}?{query}
*   Trying 127.0.0.1:{port}...
* Connected to localhost (127.0.0.1) port {port} (#0)
> PUT {path}?{query} HTTP/1.1
> Host: localhost:{port}
> User-Agent: curl/7.79.1
> Accept: */*
> Cookie: csrftoken={csrf_token};sessionid={session_id}
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 403 Forbidden
< Content-Type: application/json
< Vary: Accept, Accept-Language, Cookie
< Allow: DELETE, PUT, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: fr-fr
< Content-Length: 116
< Server-Timing: TimerPanel_utime;dur=79.28900000000283;desc="User CPU time", TimerPanel_stime;dur=10.49199999999928;desc="System CPU time", TimerPanel_total;dur=89.78100000000211;desc="Total CPU time", TimerPanel_total_time;dur=111.31906509399414;desc="Elapsed time", SQLPanel_sql_time;dur=4.807949066162109;desc="SQL 3 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< X-Content-Type-Options: nosniff
< Referrer-Policy: origin,origin-when-cross-origin
< Cross-Origin-Opener-Policy: same-origin
< Server: Werkzeug/2.0.0 Python/3.9.13
< Date: Wed, 14 Sep 2022 16:49:13 GMT
< 
* Closing connection 0
{"detail":[{"location":"non_field_errors","message":"CSRF Failed: CSRF token missing.","type":"permission_denied"}]}

If I omit the CSRF token in the Cookie header field, I get a 403 (Forbidden) status code:

$ curl -v -X PUT -H "X-CSRFToken: {csrf_token}" --cookie "sessionid={session_id}" http://localhost:{port}{path}?{query}
*   Trying 127.0.0.1:{port}...
* Connected to localhost (127.0.0.1) port {port} (#0)
> PUT {path}?{query} HTTP/1.1
> Host: localhost:{port}
> User-Agent: curl/7.79.1
> Accept: */*
> Cookie: sessionid={session_id}
> X-CSRFToken: {csrf_token}
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 403 Forbidden
< Content-Type: application/json
< Vary: Accept, Accept-Language, Cookie
< Allow: DELETE, PUT, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: fr-fr
< Content-Length: 117
< Server-Timing: TimerPanel_utime;dur=81.76699999999926;desc="User CPU time", TimerPanel_stime;dur=10.824999999996976;desc="System CPU time", TimerPanel_total;dur=92.59199999999623;desc="Total CPU time", TimerPanel_total_time;dur=112.99705505371094;desc="Elapsed time", SQLPanel_sql_time;dur=5.406379699707031;desc="SQL 3 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< X-Content-Type-Options: nosniff
< Referrer-Policy: origin,origin-when-cross-origin
< Cross-Origin-Opener-Policy: same-origin
< Server: Werkzeug/2.0.0 Python/3.9.13
< Date: Wed, 14 Sep 2022 16:53:39 GMT
< 
* Closing connection 0
{"detail":[{"location":"non_field_errors","message":"CSRF Failed: CSRF cookie not set.","type":"permission_denied"}]}

If I omit the session identifier in the Cookie header field, I get a 401 (Unauthorized) status code:

$ curl -v -X PUT -H "X-CSRFToken: {csrf_token}" --cookie "csrftoken={csrf_token}" http://localhost:{port}{path}?{query}
*   Trying 127.0.0.1:{port}...
* Connected to localhost (127.0.0.1) port {port} (#0)
> PUT {path}?{query} HTTP/1.1
> Host: localhost:{port}
> User-Agent: curl/7.79.1
> Accept: */*
> Cookie: csrftoken={csrf_token}
> X-CSRFToken: {csrf_token}
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 401 Unauthorized
< Content-Type: application/json
< Vary: Accept, Accept-Language, Cookie
< Allow: DELETE, PUT, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: fr-fr
< Content-Length: 129
< Server-Timing: TimerPanel_utime;dur=21.655999999993014;desc="User CPU time", TimerPanel_stime;dur=4.543999999995663;desc="System CPU time", TimerPanel_total;dur=26.199999999988677;desc="Total CPU time", TimerPanel_total_time;dur=41.02301597595215;desc="Elapsed time", SQLPanel_sql_time;dur=0;desc="SQL 0 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< X-Content-Type-Options: nosniff
< Referrer-Policy: origin,origin-when-cross-origin
< Cross-Origin-Opener-Policy: same-origin
< Server: Werkzeug/2.0.0 Python/3.9.13
< Date: Wed, 14 Sep 2022 16:58:33 GMT
< 
* Closing connection 0
{"detail":[{"location":"non_field_errors","message":"Informations d'authentification non fournies.","type":"not_authenticated"}]}
Géry Ogam
  • 6,336
  • 4
  • 38
  • 67