Flask模拟实现CSRF攻击

CSRF

  • CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造。
  • CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求。
    • 包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......
  • 造成的问题:个人隐私泄露以及财产安全。

CSRF攻击示意图

  • 客户端访问服务器时没有同服务器做安全验证

防止 CSRF 攻击

步骤

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

代码演示

未进行 csrf 校验的 WebA

  • 后端代码实现

    1from flask import Flask, render_template, make_response 2from flask import redirect 3from flask import request 4from flask import url_for 5 6app = Flask(__name__) 7 8 9@app.route('/', methods=["POST", "GET"]) 10def index(): 11 if request.method == "POST": 12 # 取到表单中提交上来的参数 13 username = request.form.get("username") 14 password = request.form.get("password") 15 16 if not all([username, password]): 17 print('参数错误') 18 else: 19 print(username, password) 20 if username == 'laowang' and password == '1234': 21 # 状态保持,设置用户名到cookie中表示登录成功 22 response = redirect(url_for('transfer')) 23 response.set_cookie('username', username) 24 return response 25 else: 26 print('密码错误') 27 28 return render_template('temp_login.html') 29 30 31@app.route('/transfer', methods=["POST", "GET"]) 32def transfer(): 33 # 从cookie中取到用户名 34 username = request.cookies.get('username', None) 35 # 如果没有取到,代表没有登录 36 if not username: 37 return redirect(url_for('index')) 38 39 if request.method == "POST": 40 to_account = request.form.get("to_account") 41 money = request.form.get("money") 42 print('假装执行转操作,将当前登录用户的钱转账到指定账户') 43 return '转账 %s 元到 %s 成功' % (money, to_account) 44 45 # 渲染转换页面 46 response = make_response(render_template('temp_transfer.html')) 47 return response 48 49if __name__ == '__main__': 50 app.run(debug=True, port=9000) 
  • 前端登录页面代码

    1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>登录</title> 6</head> 7<body> 8 9<h1>我是网站A,登录页面</h1> 10 11<form method="post"> 12 <label>用户名:</label><input type="text" name="username" placeholder="请输入用户名"><br/> 13 <label>密码:</label><input type="password" name="password" placeholder="请输入密码"><br/> 14 <input type="submit" value="登录"> 15</form> 16 17</body> 18</html>
  • 前端转账页面代码

    1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>转账</title> 6</head> 7<body> 8<h1>我是网站A,转账页面</h1> 9 10<form method="post"> 11 <label>账户:</label><input type="text" name="to_account" placeholder="请输入要转账的账户"><br/> 12 <label>金额:</label><input type="number" name="money" placeholder="请输入转账金额"><br/> 13 <input type="submit" value="转账"> 14</form> 15 16</body> 17</html>

    运行测试,如果在未登录的情况下,不能直接进入转账页面,测试转账是成功的

攻击网站B的代码

  • 后端代码实现

    1from flask import Flask 2from flask import render_template 3 4app = Flask(__name__) 5 6@app.route('/') 7def index(): 8 return render_template('temp_index.html') 9 10if __name__ == '__main__': 11 app.run(debug=True, port=8000)  
  • 前端代码实现

    1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6</head> 7<body> 8 9<h1>我是网站B</h1> 10 11<form method="post" action="http://127.0.0.1:9000/transfer"> 12 <input type="hidden" name="to_account" value="999999"> 13 <input type="hidden" name="money" value="190000" hidden> 14 <input type="submit" value="点击领取优惠券"> 15</form> 16 17</body> 18</html>

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

在网站A中模拟实现 csrf_token 校验的流程

  • 添加生成 csrf_token 的函数

    1# 生成 csrf_token 函数 2def generate_csrf(): 3 return bytes.decode(base64.b64encode(os.urandom(48)))
  • 在渲染转账页面的,做以下几件事情:

  • 生成 csrf_token 的值

  • 在返回转账页面的响应里面设置 csrf_token 到 cookie 中

  • 将 csrf_token 保存到表单的隐藏字段中

    1@app.route('/transfer', methods=["POST", "GET"]) 2def transfer(): 3 ... 4 # 生成 csrf_token 的值 5 csrf_token = generate_csrf() 6 7 # 渲染转换页面,传入 csrf_token 到模板中 8 response = make_response(render_template('temp_transfer.html', csrf_token=csrf_token)) 9 # 设置csrf_token到cookie中,用于提交校验 10 response.set_cookie('csrf_token', csrf_token) 11 return response
  • 在转账模板表单中添加 csrf_token 隐藏字段

    1<form method="post"> 2 <input type="hidden" name="csrf_token" value="{{ csrf_token }}"> 3 <label>账户:</label><input type="text" name="to_account" placeholder="请输入要转账的账户"><br/> 4 <label>金额:</label><input type="number" name="money" placeholder="请输入转账金额"><br/> 5 <input type="submit" value="转账"> 6</form>  
  • 运行测试,进入到转账页面之后,查看 cookie 和 html 源代码

  • 在执行转账逻辑之前进行 csrf_token 的校验

    1if request.method == "POST": 2 to_account = request.form.get("to_account") 3 money = request.form.get("money") 4 # 取出表单中的 csrf_token 5 form_csrf_token = request.form.get("csrf_token") 6 # 取出 cookie 中的 csrf_token 7 cookie_csrf_token = request.cookies.get("csrf_token") 8 # 进行对比 9 if cookie_csrf_token != form_csrf_token: 10 return 'token校验失败,可能是非法操作' 11 print('假装执行转操作,将当前登录用户的钱转账到指定账户') 12 return '转账 %s 元到 %s 成功' % (money, to_account)

  运行测试,用户直接在网站 A 操作没有问题,再去网站B进行操作,发现转账不成功,因为网站 B 获取不到表单中的 csrf_token 的隐藏字段,而且浏览器有同源策略,网站B是获取不到网站A的 cookie 的,所以就解决了跨站请求伪造的问题

在 Flask 项目中解决 CSRF 攻击

在 Flask 中, Flask-wtf 扩展有一套完善的 csrf 防护体系,对于我们开发者来说,使用起来非常简单

在 FlaskForm 中实现校验

  • 设置应用程序的 secret_key

    • 用于加密生成的 csrf_token 的值

      app.secret_key = "#此处可以写随机字符串#"
      
  • 在模板的表单中添加以下代码

    <form method="post"> {{ form.csrf_token() }} {{ form.username.label }} {{ form.username }}<br/> {{ form.password.label }} {{ form.password }}<br/> {{ form.password2.label }} {{ form.password2 }}<br/> {{ form.submit }} </form>
  • 渲染出来的前端页面为:

  设置完毕,cookie 中的 csrf_token 不需要我们关心,会自动帮我们设置

单独使用

  • 设置应用程序的 secret_key

    • 用于加密生成的 csrf_token 的值

      app.secret_key = "#此处可以写随机字符串#"
      
  • 导入 flask_wtf.csrf 中的 CSRFProtect 类,进行初始化,并在初始化的时候关联 app

    1from flask.ext.wtf import CSRFProtect 2CSRFProtect(app)
  • 如果模板中有表单,不需要做任何事。与之前一样:

    1<form method="post"> 2 {{ form.csrf_token }} 3 ... 4</form>
  • 但如果模板中没有表单,你仍需要 CSRF 令牌:

    1<form method="post" action="/"> 2 <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" /> 3</form>
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Flask模拟实现CSRF攻击 - HelloWorld