由于缺少 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: &lt;SecureCookieSession 'csrf_token': '2182effc89ce180a53622272d88d4466679920cd'&gt; 我遇到这个问题很久了,意识到这是我的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() 替换为 &lt;input type="hidden" name="csrf_token" value=" csrf_token() "/&gt;.

您现在必须通过添加 CsrfProtect(app) 来明确启用 CSRF 保护。

documentation现在明显反映了这一点,但我不知道这已经改变了,正在追鬼。

在不以某种方式通知开发人员的情况下,不推荐使用的功能是一个大问题。现在升级到最新版本的任何人都会像我一样追鬼。但这也是我没有拍摄我的依赖关系的快照的错。以艰难的方式吸取了教训。

【讨论】:

您不必替换hidden_tag(),但肯定需要CsrfProtect(app) 另外,self.SECRET_KEY 是 per-form secret,当它设置为 None 时,使用的是应用程序密码。 感谢 Martijn,我发现了另一个问题。我将它部署在 GAE 上。似乎flask_wtfFlask-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,表单验证失败的主要内容,如果未能解决你的问题,请参考以下文章

yii2表单提交CSRF验证

django种表单post出现CSRF verification failed( CSRF验证失败 ) 的两种解决方案

Django - 403 禁止 CSRF 验证失败

禁止 403:Firefox 中的 CSRF 验证失败错误,而不是 chrome

django CSRF 验证失败。请求中止

Django 1.2.4 CSRF 验证失败