使用 CSRF token 避免 CSRF 攻击

跨站请求伪造 (CSRF),又称会话劫持,发生在敏感的 Web 服务没有保护机制来防止攻击者在用户信任的网站上任意提交数据和命令时。例如,攻击者可能能够导致授权用户提交表单数据到执行管理功能的 Web 服务,或者修改个人设置。

错误

在典型的攻击场景中,拥有合法网站现有会话的受害者会被诱骗访问恶意网站,该网站利用此会话触发某些操作。下面的代码片段提供了一个示例,说明攻击者如何对没有 CSRF token 保护的网站执行此类攻击。下面的代码将托管在攻击者的网站上,但会在合法用户的网站上执行转账。

<!-- bank.evil.com site -->
<html>
    <head>
        <title>Malicious website</title>
    </head>
    <body>
        <!-- hehe i'm gonna be rich -->
        <h1>Enter your name to find out your funny nickname:</h1>
        <form action="http://legitimate-site.com/transferfunds" method="POST">
            Your name: <input type="text"><br/>
            <input type="submit">
            <input type="hidden" name="amount" value="10000">
            <input type="hidden" name="recipient" value="evil_hacker">
        </form>
    </body>
</html>

为了防止此问题,必须在每个请求中包含一个密码安全的随机数或哈希值,并且必须在执行敏感功能之前对其进行验证。

正确

大多数 Web 框架将提供中间件,能够为您生成安全的 CSRF token。以下总结了一些主要的框架。

Django

1. 将 ‘django.middleware.csrf.CsrfViewMiddleware’ 添加到配置中的 MIDDLEWARE_CLASSES 中,任何假设 CSRF 攻击已被处理的其他中间件之前

2. 在任何用户可以提交 POST 数据的模板中,您需要添加一个特殊的 csrf_token 标签。

<form action="." method="POST">
    {% csrf_token %}
    <!-- other form fields -->
</form>

3. 确保将 csrf 上下文传递到正在渲染的任何视图中。如果您没有使用 RequestContext,则需要手动执行此操作。

请参阅 Django 文档 <https://docs.django.ac.cn/en/1.8/ref/csrf/#using-csrf> 以获取更详细的说明。

Flask-WTF

在没有任何配置的情况下,flask_wtf.Form 将是一个具有 CSRF 保护的会话安全表单。

给定以下表单定义

# form.py
from flask_wtf import Form
from wtforms import StringField
from wtforms.validators import DataRequired

class PersonForm(Form):
    name = StringField('name', validators=[DataRequired()])

您只需要在模板定义中包含 form.hidden_tag()

<!-- view.html -->
<form method="POST" action="{{ url_for('submit') }}">
    {{ form.hidden_tag() }}
    {{ form.name.label }} {{ form.name(size=20 }}
    <input type="submit" value="ok">
</form>

CSRF token 将在您验证表单提交的任何地方自动验证。

# view.py
from form import PersonForm

@app.route("/submit", methods=("GET", "POST"))
def submit():
    f = PersonForm()
    if form.validate_on_submit():
        # csrf token also validated
        return redirect("/")
    return render_template("view.html", form=f)

后果

  • 合法的用户会话可能被劫持

  • 可以访问特权服务和功能

  • 受保护的数据可以被修改

参考