由于缺少 CSRF,表单验证失败
Posted
技术标签:
【中文标题】由于缺少 CSRF,表单验证失败【英文标题】:Form validation fails due missing CSRF 【发布时间】:2014-02-25 09:16:44 【问题描述】:几天前,我重置了本地烧瓶环境,但在删除之前没有通过pip freeze
捕获依赖项。因此我不得不重新安装整个堆栈的最新版本。
现在出乎意料的是,我不再能够使用表单进行验证。 Flask 声称 CSRF 会丢失。
def register():
form = RegisterForm()
if form.validate_on_submit():
...
return make_response("register.html", form=form, error=form.errors)
第一次发送Get
时,我按预期检索了一个空的form.errors
。
现在我填写表格并提交,form.errors
显示:'csrf_token': [u'CSRF token missing']
这太奇怪了。我想知道 Flask-WTF 是不是变了,我用错了。
我可以清楚地看到form.CSRF_token
存在,为什么它声称它丢失了?
CSRFTokenField: <input id="csrf_token" name="csrf_token" type="hidden" value="1391278044.35##3f90ec8062a9e91707e70c2edb919f7e8236ddb5">
我从未接触过工作模板,但我还是把它贴在这里:
% from "_formhelpers.html" import render_field %
% extends "base.html" %
% block body %
<div class="center simpleform">
<h2>Register</h2>
% if error %<p class=error><strong>Error:</strong> error % endif %
<form class="form-signin" action=" url_for('register') " method=post>
form.hidden_tag()
<dl>
render_field(form.name)
render_field(form.email)
render_field(form.password)
render_field(form.confirm)
<dd><input type=submit value=Register class='btn btn-primary'>
</dl>
</form>
</div>
% endblock %
这是一个新错误吗?
更新:
我已重新安装所有内容,但问题仍然存在。
正如 Martijn 建议的那样,我正在flask_wtf
中调试以下方法:
def validate_csrf_token(self, field):
if not self.csrf_enabled:
return True
if hasattr(request, 'csrf_valid') and request.csrf_valid:
# this is validated by CsrfProtect
return True
if not validate_csrf(field.data, self.SECRET_KEY, self.TIME_LIMIT):
raise ValidationError(field.gettext('CSRF token missing'))
最后一个条件是引发验证错误。
field.data = "1391296243.8##1b02e325eb0cd0c15436d0384f981f06c06147ec"
self.SECRET_KEY = None (? Is this the problem)
self.TIME_LIMIT = 3600
你是对的,HMAC 比较失败了……这两个值每次都不同。
return hmac_compare == hmac_csrf
我在我的配置中定义了 SECRET_KEY 和 CSRF_SESSION_KEY。
【问题讨论】:
您接受 cookie 吗? CSRF 架构要求csrf_token
值在会话中存在且有效;它是一个随机值,用于对令牌进行签名,并在发布时用于使用表单验证 CSRF 令牌(连同服务器端密钥)。
是的,Firefox 和 Chrome 都不会阻止 cookie。我不明白。
所以,为了验证,您确实看到了一个名为 session
的 cookie 集(前提是您没有将 SESSION_COOKIE_NAME
设置为其他值)?
是的。在调试监视列表下的 Eclipse 中,当我输入 session
时,我得到这个:LocalProxy: <SecureCookieSession 'csrf_token': '2182effc89ce180a53622272d88d4466679920cd'>
我遇到这个问题很久了,意识到这是我的SESSION_COOKIE_SECURE = True
应用程序设置。希望这对其他人有帮助。
【参考方案1】:
Flask-WTF CSRF 基础设施在以下情况下拒绝令牌:
令牌丢失。这里不是这样,你可以在表单中看到token。
它太旧了(默认过期设置为 3600 秒或一小时)。在表单上设置TIME_LIMIT
属性以覆盖它。可能不是这里的情况。
如果在当前会话中没有找到 'csrf_token'
键。您显然可以看到会话令牌,所以它也出来了。
如果 HMAC 签名不匹配;签名基于会话中在'csrf_token'
密钥下设置的随机值、服务器端密钥和令牌中的到期时间戳。
排除了前三种可能性后,您需要验证第四步失败的原因。您可以在flask_wtf/csrf.py
文件中的validate_csrf()
函数中调试验证。
对于您的设置,您需要验证会话设置是否正确(特别是如果您不使用默认会话配置),并且您使用的是正确的服务器端密钥。表单本身可能具有SECRET_KEY
属性集,但在请求之间不稳定,或者应用程序WTF_CSRF_SECRET_KEY
键已更改(后者默认为app.secret_key
value)。
CSRF 支持是在 0.9.0 版本中添加的,如果您升级了,请查看特定的 CSRF protection documentation。标准的 Flask-WTF Form
类包含 CSRF 令牌作为隐藏字段,渲染隐藏字段足以包含它:
form.hidden_tag()
【讨论】:
感谢 Martijn,非常感谢您的帮助。您可以看看我更新的问题吗? 就我而言,该应用程序在本地计算机上运行良好,但在托管时崩溃了。这是由于作者描述的不正确的会话设置。如果您遇到同样的问题,请在烧瓶应用程序中设置一个恒定的密钥。【参考方案2】:经过将近一天的工作,我终于找到了问题所在。 :( 非常感谢 Martijn 的帮助。
实际问题在于最新的flask_wtf.csrf
的工作方式。制造商对其进行了彻底的改造。
您必须将模板中的所有 form.hidden_tag()
替换为
<input type="hidden" name="csrf_token" value=" csrf_token() "/>
.
您现在必须通过添加 CsrfProtect(app)
来明确启用 CSRF 保护。
documentation现在明显反映了这一点,但我不知道这已经改变了,正在追鬼。
在不以某种方式通知开发人员的情况下,不推荐使用的功能是一个大问题。现在升级到最新版本的任何人都会像我一样追鬼。但这也是我没有拍摄我的依赖关系的快照的错。以艰难的方式吸取了教训。
【讨论】:
您不必替换hidden_tag()
,但肯定需要CsrfProtect(app)
。
另外,self.SECRET_KEY
是 per-form secret,当它设置为 None
时,使用的是应用程序密码。
感谢 Martijn,我发现了另一个问题。我将它部署在 GAE 上。似乎flask_wtf
与Flask-DebugToolbar
发生冲突。当我禁用toolbar = DebugToolbarExtension(app)
时,CSRF 正在开发人员环境中工作。但只有那些使用flask-appengine-template
的开发人员会受到影响。 :)
有趣。我目前的项目是在 GAE 上使用 Flask,但我没有使用任何模板,也没有使用 Flask-DebugToolbar。如果我这样做,我会注意任何问题。
我现在有几个带有调试工具栏和 CSRF 保护表单的项目。如果您只使用 FlaskForm 表单,则根本不需要 CSRFProtect,您在模板中只需要form.hidden_tag()
输出即可。换一种说法;对于不使用 FlaskForm 表单对象的视图,您只需要使用 " csrf_token()
和 CSRFProtect!【参考方案3】:
在创建应用时:
from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()
app = Flask(__name__)
...
csrf.init_app(app)
...
【讨论】:
【参考方案4】:对我来说,问题不在于 Flask-WTF 配置不当或缺少令牌。它来自环境变量。
如果您的 Flask 服务器没有在 localhost 上运行,那么为了让 Flask 正常工作,您需要设置一个 SERVER_NAME
环境变量。您可能忘记在某处修改 SERVER_NAME
值。
例如,您可以在config/settings.py
中有这样的内容:
SERVER_NAME = 'my-domain.com'
欲了解更多信息,请查看this great resource
【讨论】:
【参考方案5】:使用字段列表?
使用FieldList
时,不幸的是,我在调试数小时后发现了另一个错误来源:
class Subform(FlaskForm):
"""Parent form."""
text = StringField("Text")
class Maniform(FlaskForm):
"""Parent form."""
laps = FieldList(
FormField(Subform),
min_entries=1,
max_entries=30
)
这将无法正确处理 CSRF,因为 Subform
应该继承自 wtforms.Form
class Subform(Form):
"""Parent form."""
text = StringField("Text")
修复这个错误解决了我的问题。
【讨论】:
我就是在找这个。除了使用 FieldList 实现的表单外,其他所有表单都运行良好。非常感谢!以上是关于由于缺少 CSRF,表单验证失败的主要内容,如果未能解决你的问题,请参考以下文章
django种表单post出现CSRF verification failed( CSRF验证失败 ) 的两种解决方案