TL; DR
我在Django网站上发现了一个看起来很讨厌的bug。登录表单使用{% csrf_token %}
,我还设置了<meta name="referrer" content="same-origin">
。此登录页面工作正常。但是,在尝试编写生产站点测试时,我注意到如果未发送CSRF令牌或引用,则站点会记录此错误,向我发送电子邮件,但仍然允许测试用户进入。我可以使用python-requests或Chrome的Servistate HTTP Editor & REST API client扩展程序重现它 - 但由于某种原因不能使用cURL命令行工具,因为访问似乎被正确阻止。我迷失了如何调试它,并且非常感谢任何提示。
全文
我是Django的新手,刚刚开始编写我的第一个测试,但我无法理解我发现的情况。我的网站有一个登录页面,它使用CSRF令牌:
<form id="login-form" method="post" action="/login">
<input type='hidden' name='csrfmiddlewaretoken' value='2QXxnB5yTqOnEdsFVwgWfCbBam6JOvl49mnXk29mBgxVP1tvf7VWy0VvxzYtR3OT' />
<table>
<tr>
<td><input id="id_username" name="username" type="text"></td>
</tr>
<tr>
<td><input id="id_password" name="password" type="password"></td>
</tr>
<tr><td colspan="2">
<input type="submit" value="Enter" class="btn" />
<input type="hidden" name="next" value="/home" />
</td></tr>
</table>
</form>
此表单正常。没有通过它我无法访问“秘密”数据,登录后我可以访问它。
在我的测试中,我创建了一个login()
函数,用于测试对已部署网站上的受限数据的访问。
from django.test import TestCase
import requests
from rodichi import settings
PRFX = 'https://rodichi.net'
class ProductionServerTestCase(TestCase):
def setUp(self):
self.session = requests.Session()
def login(self):
url = PRFX + '/login'
login_data = {'username': settings.TEST_USERNAME, 'password': settings.TEST_PASSWORD}
headers = {'referer': url}
r = self.session.post(url, data=login_data, headers=headers)
self.assertEqual(r.status_code, 200)
return r
这里开始有趣的部分。尽管CSRF令牌未在此函数中传递,但它仍然可以正常工作:我的站点告诉我测试用户已登录(我收到有关它的电子邮件,并且Axes记录每个登录事件)。同时,我收到有关CSRF验证错误的电子邮件:
[Django] WARNING (EXTERNAL IP): Forbidden (CSRF token missing or incorrect.): /login
但该网站仍然允许用户进入!
此外,如果我没有使用POST
传递引用,我会收到另一个错误:[Django] WARNING (EXTERNAL IP): Forbidden (Referer checking failed - no Referer.): /login
并且没有CSRF错误 - 但测试用户仍然会进入。
可能是什么原因?我该怎么调试呢?
以下是完整的工作示例(使用修改后的用户名和密码):
import requests
PRFX = 'https://rodichi.net'
TEST_PASSWORD = '51x6bJlH',
TEST_USERNAME = 'test'
if __name__ == '__main__':
session = requests.Session()
url = PRFX + '/login'
login_data = {'username': TEST_USERNAME, 'password': TEST_PASSWORD}
headers = {'referer': url}
r = session.post(url, data=login_data, headers=headers)
print(r.status_code)
print("\nSecret-page\n")
url = PRFX + '/people/50'
r = session.get(url)
print(r.status_code)
print(r.content.decode('utf-8'))
它使用最后一次打印输出正确的机密数据。如果登录失败(例如密码无效),它将重定向到登录页面。因此,CSRF的缺乏不会过多地干扰Django,尽管它确实向我发送了一封通知电子邮件,如上所述。但为什么??