1、什么是CSRF
其目标是在用户不知情的情况下,以用户身份执行未经授权的操作。攻击者通过引诱用户访问恶意网站或点击包含恶意代码的链接,来伪造一个请求发送给服务器,来触发 CSRF 攻击。一旦用户被攻击,他们的登录凭据将被用于执行可能涉及敏感操作的请求,例如更改密码、发表评论、转账等。
2、原理
前提:用户登录网站后,浏览器会记录网站的cookie,当用户再次请求网站的时候,浏览器会把网站下的cookie带上发给服务器。
1、假如用户使用浏览器登录了淘宝网,并且浏览器已经保存了淘宝网的cookie
2、用户又打开一个标签登录了邮箱,打开邮件里的链接,该链接就是伪造的攻击链接
3、用户点击后会访问淘宝店铺的好评链接,此时浏览器已经有了用户的淘宝网的cookie,这次请求服务器就会认为是用户正常发送的,就会执行好评请求,这样就造成CSRF。
3、防护
1、检查Referer和Origin字段
HTTP请求头会默认带上Referer字段和Origin字段,Referer这个字段用以标明请求来源于哪个地址。Origin它表示请求的来源,即请求来自于哪个站点,Origin只包含服务器名。一般情况下,Origin和Referer字段应和请求的地址位于同一域名下。如果不是同一域名下,服务器就会识别为恶意访问。
2、使用CSRF令牌(CSRF Token)
在每个用户会话中生成一个随机的CSRF令牌,并将其嵌入到每个表单或敏感操作的请求中。这个令牌是服务器生成的,并与用户的会话相关联。服务器在接收到请求时会验证这个令牌的有效性,确保请求来自合法的源。攻击者无法获得有效的CSRF令牌,因此无法成功发起CSRF攻击。
3、Cookie的SameSite属性
在设置Cookie时,可以将SameSite属性设置为"Strict"或"Lax",以限制跨站请求的Cookie传递。这有助于减少CSRF攻击的成功率,Strict完全禁止,Lax相对宽松,None不做限制
4、django中的防护
1、首先在settings.py中启用CSRF保护
MIDDLEWARE = [
# ...
'django.middleware.csrf.CsrfViewMiddleware', # 开启CSRF中间件
# ...
]
2、在所有包含表单的HTML模板中,确保包含{% csrf_token %}标签,会隐藏一个CSRF令牌
<form method="post" action="/example/">
{% csrf_token %}
<!-- 其他表单字段 -->
<input type="submit" value="提交">
</form>
<!--会变成下面-->
<form method="post" action="/example/">
<input type="hidden" name="csrfmiddlewaretoken" value="cxIrGQrJOzVN3NcleAjFEbYZfSE8LbIJuuPW6Vx7H3IliRg26FCQHgzMjwWWQp9u">
<!-- 其他表单字段 -->
<input type="submit" value="提交">
</form>
3、用户第一层访问网站时,django会随机生成一个csrftoken,放在浏览器的cookie中,后面每次请求的时候都会带上这个csrftoken
4、当用户提交表单的时候,CsrfViewMiddleware中间件会自动校验cookie和表单中的csrftoken是否一致,来判定是否是合法请求。
5、每次刷新表单的时候,csrfmiddlewaretoken会更新,而cookie中的csrftoken没变,如何校验呢?csrftoken只比较secret,token前32位是salt,后面是加密的token,通过salt能解密出唯一的secret,其实最终比较的是secret。
# CsrfViewMiddleware/process_view中
csrf_token = self._get_token(request) # 从cookie中获取csrftoken
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') # 从表单中获取csrfmiddlewaretoken
_compare_masked_tokens(request_csrf_token, csrf_token) # 比较
def _compare_masked_tokens(request_csrf_token, csrf_token):
# Assume both arguments are sanitized -- that is, strings of
# length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.
return constant_time_compare(
_unmask_cipher_token(request_csrf_token),
_unmask_cipher_token(csrf_token),
)
def _unmask_cipher_token(token):
"""
Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
CSRF_TOKEN_LENGTH, and that its first half is a mask), use it to decrypt
the second half to produce the original secret.
"""
mask = token[:CSRF_SECRET_LENGTH]
token = token[CSRF_SECRET_LENGTH:]
chars = CSRF_ALLOWED_CHARS
pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in mask))
return ''.join(chars[x - y] for x, y in pairs) # Note negative values are ok
6、django后端实现,@csrf_exempt 装饰器用于关闭 CSRF 保护,即使在全局中间件中已经启用了 CSRF 保护。@csrf_protect 装饰器用于强制启用 CSRF 保护,即使在全局中间件中已经启用了 CSRF 保护。
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt
def index(request):
return render(request, 'index.html', locals())
@csrf_protect
def pay(request):
pass