切换主题
三、CSRF攻击
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种利用用户在已登录的情况下,被攻击网站的认证信息(如 Cookie、Session)来发起恶意请求的攻击方式。攻击者利用受害者的身份,向受害者已经认证过的网站发送伪造的请求,以执行某些恶意操作。
攻击原理如下:
- 用户已认证登录:受害者在浏览器中访问了一个已认证过的网站 A,并登录成功,生成了相应的认证信息,如 Cookie 或 Session。()
- 攻击者构造恶意页面:攻击者创建一个恶意网页(通常是一个精心设计的网页表单),将这个网页地址发送给受害者,或者通过其他方式诱导受害者访问。
- 受害者访问恶意页面:受害者在已经登录过网站 A 的情况下,访问了攻击者提供的恶意页面。
- 恶意请求被发送:恶意页面中包含了对网站 A 的请求,利用了受害者在网站 A 的认证信息(如 Cookie),浏览器会在后台自动发送这个请求到网站 A。
- 网站 A 执行恶意请求:由于请求中包含了受害者的认证信息,网站 A 会将这个请求当作是受害者的合法请求来处理,并执行其中的操作,比如修改用户信息、发表言论、转账等。
一、防御CSRF攻击原理
CSRF 攻击的要点,就是在向服务器发送请求时,之前访问过且浏览器保存过的,相应的cookie会自动地被发送给对应的服务器,而服务器不知道这个请求是用户发起的还是伪造的
可以在用户每次访问有表单的网页时,在表单中加入一个随机的字符串,如
csrf_token
,同时在cookie
中也加入一个具有相同值的csrf_token
键值对以服务器只有在检测到cookie中的csrf token和表单中的csrf token 相同时,才认为这个请求是正常的,否则就认为请求是伪造的,服务器就会进行防御
二、Flask-WTF防御CSRF攻击
三种防御方式
- 全局防御
- 单表单防御
- 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 令牌,可以通过以下方式之一来实现:
- 排除登录接口:在 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
是处理登录请求的视图函数。
- 自定义中间件:在请求中间件中添加逻辑以排除登录请求。可以通过检查请求路径或请求方法来确定是否需要 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 保护。