如何在Django中使用curl、csrf令牌和POST请求

65
我正在使用curl测试我的Django表单。这是我尝试过的调用(每个调用都有错误,并且为了易读性而跨越多行):

(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/

(带有HTTP头和cookie中的csrftoken)会导致400错误,且没有返回任何数据。

(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/

(就像(1)一样,但标题属性声明中没有空格,并且cookie中包含sessionid),会导致相同的400错误,而没有返回任何数据。

(3):

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

只有带有X-CSRFToken的HTTP头,而没有cookie会导致错误代码403,错误消息为:CSRF cookie not set。

我如何使用curl测试我的表单?除了cookie值和HTTP头之外,我没有考虑哪些因素?


1
你能告诉我如何获取X-CSRF-Token吗? --header "X-CSRFToken: [从我已经从Django应用程序加载的页面的源代码中三重检查的值]" - Maria Ines Parnisari
6个回答

39

结合Damien的回答和你提供的第二个示例,我成功了。我使用了一个简单的登录页面进行测试,我预期你的注册页面也类似。虽然Damien的回答基本可以工作,但是缺少sessionid cookie。

我建议采用更健壮的方法。不要手动输入来自其他请求的cookie,尝试使用curl的内置cookie管理系统模拟完整的用户交互。这样,您可以降低出错的几率:

$ 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/

第一个 curl 模拟用户使用 GET 请求首次访问页面,所有必要的 cookie 被保存。第二个 curl 模拟填写表单字段并将它们作为 POST 发送。请注意,在 POST 数据中必须包括 csrfmiddlewaretoken 字段,正如 Damien 所建议的那样。


2
我已经发布了一个完全基于Kevin在https://dev59.com/mmEi5IYBdhLWcg3wZ7q8#24376188的回答编写的答案。Django还要求您指定一个referer(`-e`)。 - Peterino

16

试试这个:

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

特别注意 -d 参数的格式。

然而,这可能行不通,因为您的视图很可能需要一个 POST 请求而不是 GET 请求。因为它将修改数据,而不仅仅返回信息。

CSRF 保护仅针对“不安全”请求(POST、PUT、DELETE)需要。它通过检查“csrftoken” cookie 与“csrfmiddlewaretoken”表单字段或“X-CSRFToken” HTTP 头进行匹配来工作。

所以:

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

你也可以使用 --header "X-CSRFToken: {token}" 来代替在表单数据中包括它。


2
-d 表示POST,因此额外的 -X 是完全多余的。 - Daniel Stenberg
2
我必须将sessionid添加到cookie中,以便在我的情况下它能够正常工作:--cookie "csrftoken=[如上];sessionid=[相应的会话ID]" - yellowcap

10

我用curl像这样工作:

  • 您必须将csrftoken作为X-CSRFToken提交到标头中。
  • 您必须以JSON格式提交表单数据。 演示,

首先,我们将获取csrf_token并将其存储在cookie.txt(或称为cookie.jar)中。

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

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

接下来我们将以JSON格式重新发送用户名和密码(您也可以以普通方式发送)。请检查JSON数据是否转义。

$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": "/"}
$

您可以将新的csrf_token会话cookie存储在同一文件或新文件中(我使用-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

-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

当您将新的csrf_token和会话id cookie存储在cookie.txt中时,您可以在整个网站上使用相同的cookie.txt。

您可以从cookie.txt(--cookie)中读取先前请求的cookie,并在同一cookie.txt中从响应中编写新的cookie。

现在,使用csrf_token和会话id读取并提交表单已经可行。

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

如果从命令行调用,curl -c cookie.txt http://localhost.com:8000/不会保存文件在我的情况下。 - Timo
获取 sessioncsrf cookie,参考这里:https://askubuntu.com/a/161814/321926 - Timo

6
这是我使用 REST 框架教程的方法:
1. 打开浏览器(例如 Chrome),按 F12 键打开开发者选项卡,监视网络情况。 2. 使用用户凭据登录,并从监控 POST 获取您的 CRSF 令牌。 3. 然后在 curl 中执行:
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"}' 

我认为将令牌放在标头而不是主体中,使用 X-CSRFToken 更加清晰。

为了让这个工作起来,我需要使用-H "X-CSRFToken: the_token_value"属性。完整代码如下:首先获取cookie curl -v -c cookies.txt -b cookies.txt http://url.com。然后从cookies.txt中打开并复制出csrftoken,最后发送请求:curl -v -c cookies.txt -b cookies.txt -H "X-CSRFToken:< the token >" --data "foo=bar&csrftoken=< the token >" http://url.com - Kinsa

1

curl-auth-csrf 是一个基于 Python 的开源工具,可以为您完成以下操作:"模仿 cURL 的 Python 工具,但可以登录并处理任何跨站请求伪造(CSRF)令牌。对于爬取通常只有在登录后才能访问的 HTML 很有用。"

以下是该工具的语法:

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/

这将传递列出的POST数据,但也包括通过stdin传递的密码。我假设“登录”后访问的页面与之前相同。
完全披露:我是curl-auth-csrf的作者。

你有办法将生成的cookie存储到文件中吗,类似于curl的方式,以便其他curl命令可以在此cookie之后运行?我查看了你的代码,但没有找到如何实现。如果有这个功能,那将非常有用,因为我需要在登录后进行POST请求,而不是GET请求。 - James Oravec
curl使用旧的Netscape格式的cookies。您可以使用Python的http.cookiejar.MozillaCookieJar将cookies保存为该格式。有关示例,请查看此答案。通常,这可能比所需的更困难。对于任何在抓取cookies后想要进行的curl请求,您只需将这些URL附加到列表中,就像调用curl-auth-csrf.py一样。脚本将代表您应用会话cookie。 - j0nam1el

0
为使 Curl-Django 通信正常工作,我必须提供以下内容:
  • X-CSRFToken 头字段中提供 CSRF 令牌;
  • Cookie 头字段中提供 CSRF 令牌;
  • Cookie 头字段中提供会话标识符。
$ 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

尝试失败

如果我在X-CSRFToken头字段中省略CSRF令牌,则会收到403(禁止)状态码:

$ 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"}]}

如果我在Cookie头字段中省略CSRF令牌,我会得到403(禁止)状态码:
$ 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"}]}

如果我在Cookie头字段中省略会话标识符,那么我会收到401(未经授权)状态码:
$ 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"}]}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接