Flask

CSRF

2018-06-23  本文已影响3人  梦醒家先生

CSRF攻击示意图

防止CSRF攻击

步骤

  1. 在客户端向后端请求界面数据的时候,后端会往响应中的cookie中设置csrf_token的值
  2. 在Form表单中添加一个隐藏的字段,值也是csrf_token
  3. 在用户点击提交的时候,会带上这两个值向后台发起请求
  4. 后端接收到请求,会以此啊几件事件:
    • 从cookie中取出csrf_token的值
    • 从表单数据中取出隐藏的csrf_token的值
    • 进行对比
  5. 如果比较之后两个值一样,那么代表是正常的请求,如果没有取到或者比较不一样,代表不是正常的请求,不会执行下一步操作

代码演示

未进行 csrf 校验的 WebA
from flask import Flask, render_template, make_response
from flask import redirect
from flask import request
from flask import url_for

app = Flask(__name__)


@app.route('/', methods=["POST", "GET"])
def index():
    if request.method == "POST":
        # 取到表单中提交上来的参数
        username = request.form.get("username")
        password = request.form.get("password")

        if not all([username, password]):
            print('参数错误')
        else:
            print(username, password)
            if username == 'laowang' and password == '1234':
                # 状态保持,设置用户名到cookie中表示登录成功
                response = redirect(url_for('transfer'))
                response.set_cookie('username', username)
                return response
            else:
                print('密码错误')

    return render_template('temp_login.html')


@app.route('/transfer', methods=["POST", "GET"])
def transfer():
    # 从cookie中取到用户名
    username = request.cookies.get('username', None)
    # 如果没有取到,代表没有登录
    if not username:
        return redirect(url_for('index'))

    if request.method == "POST":
        to_account = request.form.get("to_account")
        money = request.form.get("money")
        print('假装执行转操作,将当前登录用户的钱转账到指定账户')
        return '转账 %s 元到 %s 成功' % (money, to_account)

    # 渲染转换页面
    response = make_response(render_template('temp_transfer.html'))
    return response

if __name__ == '__main__':
    app.run(debug=True, port=9000)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>

<h1>我是网站A,登录页面</h1>

<form method="post">
    <label>用户名:</label><input type="text" name="username" placeholder="请输入用户名"><br/>
    <label>密码:</label><input type="password" name="password" placeholder="请输入密码"><br/>
    <input type="submit" value="登录">
</form>

</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账</title>
</head>
<body>
<h1>我是网站A,转账页面</h1>

<form method="post">
    <label>账户:</label><input type="text" name="to_account" placeholder="请输入要转账的账户"><br/>
    <label>金额:</label><input type="number" name="money" placeholder="请输入转账金额"><br/>
    <input type="submit" value="转账">
</form>

</body>
</html>
攻击网站B代码
from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('temp_index.html')

if __name__ == '__main__':
    app.run(debug=True, port=8000)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>我是网站B</h1>

<form method="post" action="http://127.0.0.1:9000/transfer">
    <input type="hidden" name="to_account" value="999999">
    <input type="hidden" name="money" value="190000" hidden>
    <input type="submit" value="点击领取优惠券">
</form>

</body>
</html>

--运行测试,在用户登录网站A的情况下,点击网站B的按钮,可以实现伪造访问

在网站A中模拟实现csrf_token校验的流程
# 生成csrf_token的函数
def generate_csrf():
  return  bytes.decode(base64.b64encode(os.urandom(48)))
@app.route('/transfer', methods=["POST", "GET"])
def transfer():
    ...
    # 生成 csrf_token 的值
    csrf_token = generate_csrf()

    # 渲染转换页面,传入 csrf_token 到模板中
    response = make_response(render_template('temp_transfer.html', csrf_token=csrf_token))
    # 设置csrf_token到cookie中,用于提交校验
    response.set_cookie('csrf_token', csrf_token)
    return response
<form method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
    <label>账户:</label><input type="text" name="to_account" placeholder="请输入要转账的账户"><br/>
    <label>金额:</label><input type="number" name="money" placeholder="请输入转账金额"><br/>
    <input type="submit" value="转账">
</form>
if request.method == "POST":
    to_account = request.form.get("to_account")
    money = request.form.get("money")
    # 取出表单中的 csrf_token
    form_csrf_token = request.form.get("csrf_token")
    # 取出 cookie 中的 csrf_token
    cookie_csrf_token = request.cookies.get("csrf_token")
    # 进行对比
    if cookie_csrf_token != form_csrf_token:
        return 'token校验失败,可能是非法操作'
    print('假装执行转操作,将当前登录用户的钱转账到指定账户')
    return '转账 %s 元到 %s 成功' % (money, to_account)

在 Flask 项目中解决 CSRF 攻击

CSRF

from flask_wtf.csrf import CSRFProtect
...
app.config.from_object(Config)
...
CSRFProtect(app)
思路分析

一、在 前端发起的POST请求 中没有进行csrf_token校验,根据csrf_token校验原理,具体操作步骤有以下几步:

  1. 后端生成csrf_token的值,在前端请求界面的时候将值传给前端,传给前端的方式可能有以下两种方式:
    • 在模版中的From表单中的添加隐藏的字段
    • 将csrf_token使用cookie的方式传给前端
  2. 在前端 发起请求时,在表单或者请求头中带上指定的csrf_token
  3. 后端在接收请求之后,取到前端发送过来的csrf_token,与第一步生成的csrf_token值进行校验
  4. 如果校验对csrf_token一致,则代表是正常的请求,否则是伪造的请求,不予通过

二、在Flask中,CSRFProtect这个类专门只对指定的app进行csrf_token校验操作,所以开发者需要做以下的操作:
- 生成csrf_token的值
- 将csrf_token的值传给前端浏览器

- 在前端请求是带上csrf_token值 CSRF机制流程图.png
完成代码逻辑
# 导入生成 csrf_token 值的函数
from flask_wtf.csrf import generate_csrf
# 调用函数生成 csrf_token
csrf_token = generate_csrf()
@app.after_request
def after_request(response):
    # 调用函数生成 csrf_token
    csrf_token = generate_csrf()
    # 通过 cookie 将值传给前端
    response.set_cookie("csrf_token", csrf_token)
    return response
$.ajax({
    url:"/passport/register",
    type: "post",
    headers: {
        "X-CSRFToken": getCookie("csrf_token")
    },
    data: JSON.stringify(params),
    contentType: "application/json",
    success: function (resp) {
        if (resp.errno == "0"){
            // 刷新当前界面
        location.reload()
        }else {
            $("#register-password-err").html(resp.errmsg)
            $("#register-password-err").show()
        }
    }
})

打开 CSRFProtect 的代码,并运行测试 注:X-CSRFToken 这个 key 是 CSRFProtect 这个类里面指定的,具体请点击进入到此类查看源代码

上一篇下一篇

猜你喜欢

热点阅读