Python中的CSRF攻击与表单重复提交

2023-04-17 00:00:00 提交 表单 重复

CSRF攻击(Cross-Site Request Forgery)是一种常见的网络攻击方式,也叫做“跨站请求伪造”。攻击者利用用户已登录的身份,在用户不知情的情况下,向指定网站发送恶意请求,以达到攻击目的。通常,这种攻击会利用浏览器对cookie的自动管理机制,实现对用户账号的控制。

在Python中,可以使用Flask框架的Flask-WTF扩展库来防止CSRF攻击。Flask-WTF提供了CSRF保护机制,可以自动为表单添加验证token。

相比之下,表单重复提交指的是用户再次提交同一份表单,而不是恶意攻击。如果一个表单可以被多次提交并重复执行同一操作,可能会导致数据不一致或重复操作等问题。

为了防止表单重复提交,可以在表单中添加一个唯一的token值,每次提交表单时都验证该token值是否有效,如果无效则拒绝重复提交。可以通过UUID等方式生成唯一的token值。

下面是一个使用Flask-WTF实现CSRF保护的例子:

from flask import Flask, render_template, request
from flask_wtf.csrf import CSRFProtect
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'
csrf = CSRFProtect(app)

class MyForm(FlaskForm):
    name = StringField('Name')
    submit = SubmitField('Submit')

@app.route('/', methods=['GET', 'POST'])
def index():
    form = MyForm()
    if form.validate_on_submit():
        name = form.name.data
        return f'Hello {name}!'
    return render_template('index.html', form=form)

if __name__ == '__main__':
    app.run(debug=True)

在上面的例子中,我们首先导入了Flask、Flask-WTF和WTForms等库,然后定义了一个表单类MyForm,其中包含一个文本框和一个提交按钮。在路由函数中,我们创建了一个表单实例,并判断它是否通过验证,如果通过了就输出欢迎信息,并使用render_template函数将表单呈现到模板中。

在模板文件(index.html)中,我们使用Flask-WTF提供的宏(helpers)来自动为表单添加CSRF保护机制。下面是index.html的代码:

<!DOCTYPE html>
<html>
<head>
    <title>Flask-WTF CSRF Protection Example</title>
</head>
<body>
    <h1>Flask-WTF CSRF Protection Example</h1>
    <form method="POST" action="/">
        {{ form.hidden_tag() }} <!-- 添加CSRF保护 -->
        {{ form.name.label }} {{ form.name }}<br>
        {{ form.submit }}
    </form>
</body>
</html>

在模板文件中,我们首先引入了Flask-WTF的helpers,然后在form标签中调用了hidden_tag()函数用于自动添加CSRF保护。注意,hidden_tag()函数会自动为表单添加一个隐藏的input标签,并设置一个token值,用于验证表单提交时的CSRF攻击。最后,我们输出了表单中的两个控件,一个是文本框,一个是提交按钮。

如果不使用Flask-WTF,自己手动实现CSRF保护也是很简单的,可以通过以下步骤来实现:

  1. 生成一个token值,在每次请求时将该token添加到表单或url参数中;
  2. 每次提交表单或处理请求时,验证该token是否有效,如果无效则返回错误;

下面是一个使用手动实现CSRF保护的例子:

from flask import Flask, render_template, request, session
import uuid

app = Flask(__name__)
app.secret_key = 'secret_key'

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        token = str(uuid.uuid4()) # 生成一个token值
        session['token'] = token
        return render_template('index.html', token=token)
    elif request.method == 'POST':
        token = session.pop('token') # 获取并删除token
        if token != request.form.get('token'): # 验证token是否有效
            return 'Invalid token'
        name = request.form.get('name')
        return f'Hello {name}!'

if __name__ == '__main__':
    app.run(debug=True)

在上面的例子中,我们首先创建了一个唯一的token值,并将它保存到session中。在GET请求中,我们将该token值传递并呈现到模板中(index.html),在POST请求中我们取出该token值并验证它是否有效。如果有效,我们取出表单中的name值并输出欢迎信息。

下面是index.html的代码:

<!DOCTYPE html>
<html>
<head>
    <title>Manually CSRF Protection Example</title>
</head>
<body>
    <h1>Manually CSRF Protection Example</h1>
    <form method="POST" action="/">
        <input type="hidden" name="token" value="{{ token }}"> <!-- 添加token -->
        <label for="name">Name:</label>
        <input type="text" name="name" id="name"><br>
        <input type="submit" value="Submit">
    </form>
</body>
</html>

在模板文件中,我们手动为表单添加了一个隐藏的input标签,并设置了一个token值,用于验证表单提交时的CSRF攻击。注意,我们还需要在表单中添加一个文本框和提交按钮。

相关文章