本文主要通过分析 django 源码 介绍 django 在 csrf (跨站请求伪造) 的防御措施, 默认大家对 csrf 有一定的了解
0x01
一般问到如何防御 csrf, 很多人都会给这么个答案,请求的时候带一个 token,到服务器验证 token 的有效性。原理是没错,但是如果让你设计这个防御系统,需要考虑哪些呢,我觉得有这么几点
- token 如何存储
- token 如何验证
- token 何时刷新
存储和验证相关联,有这么一种做法,每次用户登录生成随机值,存在数据库或者缓存中,请求的时候带上 token,每次去查询 token 有效性,这种方法其实就跟 session 验证用户是否登录一样。但问题是如果用户很多,验证的效率怎么保证。所以这种方法可行,但是成本不划算。第二种就是现在大多数的做法,cookie 里面带一个 csrf_token, 每次请求的时候也带上 token ,然后只要比较两者是否相同就行了,这样服务器没存任何东西,保证了效率。当然如果你的站点有 xss 之类的漏洞,cookie 也会泄露,同样不能保证这种方案的有效性。
token 刷新其实可以只要登录的时候刷新下,因为除非泄露,一般情况下 token 被暴力破解的概率不大。
0x02
下面看 django 如何实现的
django 对于 csrf 的防御主要在 middleware 实现,默认是开始 csrf 防御的,当然也可以关闭,单独对一些 view 开启,不过不建议这么做
try:
csrf_token = _sanitize_token(
request.COOKIES[settings.CSRF_COOKIE_NAME])
# Use same token next time
request.META['CSRF_COOKIE'] = csrf_token
except KeyError:
csrf_token = None
这部分取得 cookie 里面的 csrf_token, 如果不存在则生成一个新的 csrf_token
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
这个是取得 post 请求的 request_csrf_token
request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
对于 put delete 或者 ajax 请求来说,需要把 token 放到 headers 里面, 名字为 X-CSRFToken
最后只要比较 csrf_token 和 request_csrf_token 就能有效的防御 csrf
这里面有个特殊的地方,对于 http 来说,有可能存在中间人攻击,所以没法信任 HTTP_REFERER 这个 header ,但如果是 https, 做了下 HTTP_REFERER 的检查,只要 referer 不在信任域,一律拒绝。
referer = force_text(
request.META.get('HTTP_REFERER'),
strings_only=True,
errors='replace'
)
if referer is None:
return self._reject(request, REASON_NO_REFERER)
# Here we generate a list of all acceptable HTTP referers,
# including the current host since that has been validated
# upstream.
good_hosts = list(settings.CSRF_TRUSTED_ORIGINS)
# Note that request.get_host() includes the port.
good_hosts.append(request.get_host())
good_referers = ['https://{0}/'.format(host) for host in good_hosts]
if not any(same_origin(referer, host) for host in good_referers):
reason = REASON_BAD_REFERER % referer
return self._reject(request, reason)
在登录的时候需要加上 rotate_token, 来刷新 token
def rotate_token(request):
"""
Changes the CSRF token in use for a request - should be done on login
for security purposes.
"""
request.META.update({
"CSRF_COOKIE_USED": True,
"CSRF_COOKIE": _get_new_csrf_key(),
})
0x03
总结下,django 框架本身对于安全性这块做的挺好的,对于工程师来说是福也是祸,能够更专注逻辑实现,但是有时候也因为忽视导致安全事故的发生。所以安全研发意识其实很重要,形成一些好的习惯,防患于未然。
我认为对于研发工程师而言,需要明白主流漏洞的原理,知道如何防御。比如就 web 安全而言, xss, csrf 之类。并且能够及时获取到最新一些安全信息,关注自己用的语言,框架的安全补丁。
如果对 web 安全感兴趣,想入门的话,知乎上有一些安全界的前辈, 比如 cos @余弦, 或者这些问答