Skip to content

三、CSRF攻击

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种利用用户在已登录的情况下,被攻击网站的认证信息(如 Cookie、Session)来发起恶意请求的攻击方式。攻击者利用受害者的身份,向受害者已经认证过的网站发送伪造的请求,以执行某些恶意操作。

攻击原理如下:

  1. 用户已认证登录:受害者在浏览器中访问了一个已认证过的网站 A,并登录成功,生成了相应的认证信息,如 Cookie 或 Session。(
  2. 攻击者构造恶意页面:攻击者创建一个恶意网页(通常是一个精心设计的网页表单),将这个网页地址发送给受害者,或者通过其他方式诱导受害者访问。
  3. 受害者访问恶意页面:受害者在已经登录过网站 A 的情况下,访问了攻击者提供的恶意页面。
  4. 恶意请求被发送:恶意页面中包含了对网站 A 的请求,利用了受害者在网站 A 的认证信息(如 Cookie),浏览器会在后台自动发送这个请求到网站 A。
  5. 网站 A 执行恶意请求:由于请求中包含了受害者的认证信息,网站 A 会将这个请求当作是受害者的合法请求来处理,并执行其中的操作,比如修改用户信息、发表言论、转账等。

一、防御CSRF攻击原理

CSRF 攻击的要点,就是在向服务器发送请求时,之前访问过且浏览器保存过的,相应的cookie会自动地被发送给对应的服务器,而服务器不知道这个请求是用户发起的还是伪造的

可以在用户每次访问有表单的网页时,在表单中加入一个随机的字符串,如csrf_token,同时在cookie中也加入一个具有相同值的csrf_token键值对

以服务器只有在检测到cookie中的csrf token和表单中的csrf token 相同时,才认为这个请求是正常的,否则就认为请求是伪造的,服务器就会进行防御

二、Flask-WTF防御CSRF攻击

三种防御方式

  1. 全局防御
  2. 单表单防御
  3. AJAX

1、全局防御

后端

python
from flask_wtf import CSRFProject

app=Flask(__nane__)

app.secret_key="自定义的app秘钥"

CSRFProject(app)

前端

python
......
<form action="{{url_for("register")}}" method="POST">
	<table>
    	<tr>
        	<td></td>
            <td>
            	<input type="hidden" name="csrf_token" value="{{csrf_token()}}"/>
            </td>
        </tr>
    </table>
</form>

2、单表单防御

python
form=LoginForm(meta={"csrf":Flase})

通过form.validate()函数判断是否验证通过

3、使用AJAX

使用ajax提交表单时,前提是要开区全局CSRF保护

为了方便,在父模板的head里加入meta标签,从而子模板都能通过js获取meta里的csrf_token

python
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>{% block title %}{% endblock %}</title>
	<meta name="csrf_token" content="{{ csrf_token() }}">
</head>
...

js

javascript
var csrftoken =$('meta[name=csrf_token]').attr('content')
$.ajaxSetup({
	beforeSend:function(xhr,settings){
		if(!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain){
			xhr.setRequestHeader("X-CSRFToken",csrftoken)
		}
	}
})

三、前后端分离解决csrf_token问题

前后端分离实现过程: 后端写入令牌 为了能够让所有的视图函数受到 CSRF 保护,需要开启CSRFProtect 模块:

python
from flask_wtf import CSRFProtect
csrf = CSRFProtect()
# 加载
csrf.init_app(app)
需要为 CSRF 保护设置一个秘钥。通常下,同 Flask 应用的 SECRET_KEY 是一样的。

生成token值并利用请求钩子设置cookie,然后前端就能获取到cookie值

python
from flask_wtf.csrf import 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

在前端请求时带上 csrf_token 值 根据登录和注册的业务逻辑,当前采用的是 ajax 请求 所以在提交登录或者注册请求时,需要在请求头中添加 X-CSRFToken 的键值对

js
function getCookie(name) {
    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[1] : undefined;
}
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    config.headers['X-Requested-With'] = 'XMLHttpRequest';
    config.headers['X-CSRFToken'] = getCookie("csrf_token")
 return config
}

1、排除登录接口的csrf_token

要让登录接口不需要 CSRF 令牌,可以通过以下方式之一来实现:

  1. 排除登录接口:在 CSRF 保护中排除登录接口。在 Flask 中,你可以使用 exempt 装饰器来排除特定的视图函数或蓝图。
python
pythonCopy codefrom flask_wtf.csrf import CSRFProtect, CSRFError

csrf = CSRFProtect()

# 初始化应用
csrf.init_app(app)

# 排除登录接口
csrf.exempt(login_view_function)

在上面的代码中,login_view_function 是处理登录请求的视图函数。

  1. 自定义中间件:在请求中间件中添加逻辑以排除登录请求。可以通过检查请求路径或请求方法来确定是否需要 CSRF 保护。
python
pythonCopy codefrom flask import request

@app.before_request
def before_request():
    if request.path == '/login' and request.method == 'POST':
        # 不进行 CSRF 保护的登录请求
        return
    else:
        # 其他请求,执行 CSRF 保护逻辑
        csrf.protect()

在上面的代码中,如果请求路径为 /login 且请求方法为 POST,则跳过 CSRF 保护。