CSRF
文章目录
- CSRF
- 1. CSRF是什么?
- 2. CSRF可以做什么?
- 3. CSRF漏洞现状
- 4. CSRF的原理
- 5. 举例说明
- 6. CSRF的防御
- Python示例
1. CSRF是什么?
- CSRF(Cross-Site Request Forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
2. CSRF可以做什么?
- 你这可以这么理解CSRF攻击:**攻击者盗用了你的身份,以你的名义发送恶意请求。**CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。
3. CSRF漏洞现状
- CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI…而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。
4. CSRF的原理
- 下图简单阐述了CSRF攻击的思想:
-
从上图可以看出,要完成一次 CSRF攻击,受害者必须依次完成两个步骤:
- 登录受信任网站A,并在本地生成Cookie。
- 在不登出A的情况下,访问危险网站B。
-
看到这里,你也许会说:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。是的,确实如此,但你不能保证以下情况不会发生:
- 你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。
- 你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了…)
- 上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。
5. 举例说明
-
上面大概地讲了一下CSRF攻击的思想,这里用个例子详细说说具体的CSRF攻击,这里我以一个银行转账的操作作为例子(仅仅是例子,真实的银行网站没这么傻)
-
银行网站A,它以
GET
请求来完成银行转账的操作:如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
-
危险网站B,它里面有一段HTML的代码如下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
-
首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块…
为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的<img>
以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源http://www.mybank.com/Transfer.php?toBankId=11&money=1000
,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作… -
理解了例子中的攻击模式,其实可以看出,CSRF攻击是源于WEB的隐式身份验证机制!WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的!
6. CSRF的防御
- Python 不提供针对 CSRF 攻击的内置built-in保护;开发人员必须通过检查 anti-CSRF tokens 或使用许多经过良好测试的库和框架中的一个来手动实现这一功能。
Python示例
以下步骤展示了如何使用登录时生成的每个会话 token 手动保护 Flask 端点 /delete_user
免受 CSRF 攻击。
-
身份验证成功后,会以安全方式生成一个随机 token,并作为用户的会话变量存储起来。
@login.route("/login", methods=['POST'])def login():username = request.form.get("username")password = request.form.get("password")if validate_credentials(username, password):session['anti_crf_token'] = get_random_token()# ...
函数
get_random_token()
必须返回用户会话唯一的强随机字符串。请参阅uuid
或 Python3secrets
库生成加密安全令牌。 -
为了保护任何改变状态的 endpoint,表单必须将 anti-CSRF token 设置为隐藏值。在下面的代码中,
/users
endpoint将渲染users.html
Jinja2 template。@login.route("/users", methods=['GET']) def users():if session_is_authenticated():return render_template('users.html', anti_crf_token=session['anti_crf_token'])
users.html
template 定义了包含标记anti_crf_token
的 HTML 表单。<form action="/delete_user" method="post"><!-- ... --><input type="hidden" name="anti_crf_token" value="{{anti_crf_token}}"></form>
-
现在,可以通过检查表单 POST 参数中发送的
anti_csrf_token
是否与session['anti_crf_token']
是否匹配来确保/delete_user
的安全。@login.route("/delete_user", methods=['POST']) def delete_user():anti_csrf_token = request.form.get("anti_csrf_token")if session['anti_crf_token'] != anti_csrf_token:return "Error, wrong anti CSRF token", 401# Continue with a valid token
还可以使用其他库(如 WTForms 和 Flask-WTF)来自动执行部分反计算机资源框架保护。