CSRF攻击

CSRF(Cross Site Request Forgery,跨站请求伪造)是一种近年来才逐渐被大众了解的网络攻击方式,又被称为One-Click Attack或Session Riding。在OWASP上一次(2013)的TOP 10Web程序安全风险中,它位列第8。随着大部分程序的完善,各种框架都内置了对CSRF保护的支持,但目前仍有5%的程序受到威胁。

1)攻击原理

CSRF攻击的大致方式如下:某用户登录了A网站,认证信息保存在cookie中。当用户访问攻击者创建的B网站时,攻击者通过在B网站发送一个伪造的请求提交到A网站服务器上,让A网站服务器误以为请求来自于自己的网站,于是执行相应的操作,该用户的信息便遭到了篡改。总结起来就是,攻击者利用用户在浏览器中保存的认证信息,向对应的站点发送伪造请求。在前面学习cookie时,我们介绍过用户认证通过保存在cookie中的数据实现。在发送请求时,只要浏览器中保存了对应的cookie,服务器端就会认为用户已经处于登录状态,而攻击者正是利用了这一机制。为了更便于理解,下面我们举一个实例。

2)攻击示例

假设我们网站是一个社交网站(example.com),简称网站A;攻击者的网站可以是任意类型的网站,简称网站B。在我们的网站中,删除账户的操作通过GET请求执行,由使用下面的delete_account视图处理:

@app.route('/account/delete')
def delete_account():
    if not current_user.authenticated:
        abort(401)
    current_user.delete()
    return 'Deleted!'

当用户登录后,只要访问http://example.com/account/delete 就会删除账户。那么在攻击者的网站上,只需要创建一个显示图片的img标签,其中的src属性加入删除账户的URL:

<img src="http://example.com/account/delete">

当用户访问B网站时,浏览器在解析网页时会自动向img标签的src属性中的地址发起请求。此时你在A网站的登录信息保存在cookie中,因此,仅仅是访问B网站的页面就会让你的账户被删除掉。

当然,现实中很少有网站会使用GET请求来执行包含数据更改的敏感操作,这里只是一个示例。现在,假设我们吸取了教训,改用POST请求提交删除账户的请求。尽管如此,攻击者只需要在B网站中内嵌一个隐藏表单,然后设置在页面加载后执行提交表单的JavaScript函数,攻击仍然会在用户访问B网站时发起。
虽然CSRF攻击看起来非常可怕,但我们仍然可以采取一些措施来进行防御。下面我们来介绍防范CSRF攻击的两种主要方式。

3)主要防范措施

a.正确使用HTTP方法
防范CSRF的基础就是正确使用HTTP方法。在前面我们介绍过HTTP中的常用方法。在普通的Web程序中,一般只会使用到GET和POST方法。而且,目前在HTML中仅支持GET和POST方法(借助AJAX则可以使用其他方法)。在使用HTTP方法时,通常应该遵循下面的原则:

·GET方法属于安全方法,不会改变资源状态,仅用于获取资源,因此又被称为幂等方法(idempotent method)。页面中所有可以通过链接发起的请求都属于GET请求。

·POST方法用于创建、修改和删除资源。在HTML中使用form标签创建表单并设置提交方法为POST,在提交时会创建POST请求。附注 在GET请求中,查询参数用来传入过滤返回的资源,但是在某些特殊情况下,也可以通过查询参数传递少量非敏感信息。

虽然在实际开发中,通过在“删除”按钮中加入链接来删除资源非常方便,但安全问题应该作为编写代码时的第一考量,应该将这些按钮内嵌在使用了POST方法的form元素中。正确使用HTTP方法后,攻击者就无法通过GET请求来修改用户的数据,下面我们会介绍如何保护GET之外的请求。

b.CSRF令牌校验
当处理非GET请求时,要想避免CSRF攻击,关键在于判断请求是否来自自己的网站。在前面我们曾经介绍过使用HTTP referer获取请求来源,理论上说,通过referer可以判断源站点从而避免CSRF攻击,但因为referer很容易被修改和伪造,所以不能作为主要的防御措施。

除了在表单中加入验证码外,一般的做法是通过在客户端页面中加入伪随机数来防御CSRF攻击,这个伪随机数通常被称为CSRF令牌(token)。附注 在计算机语境中,令牌(token)指用于标记、验证和传递信息的字符,通常是通过一定算法生成的伪随机数,我们在本书后面会频繁接触到这个词。

在HTML中,POST方法的请求通过表单创建。我们把在服务器端创建的伪随机数(CSRF令牌)添加到表单中的隐藏字段里和session变量(即签名cookie)中,当用户提交表单时,这个令牌会和表单数据一起提交。在服务器端处理POST请求时,我们会对表单中的令牌值进行验证,如果表单中的令牌值和session中的令牌值相同,那么就说明请求发自自己的网站。因为CSRF令牌在用户向包含表单的页面发起GET请求时创建,并且在一定时间内过期,一般情况下攻击者无法获取到这个令牌值,所以我们可以有效地区分出请求的来源是否安全。
附注 对于AJAX请求,我们可以在XMLHttpRequest请求首部添加一个自定义字段X-CSRFToken来保存CSRF令牌。

我们通常会使用扩展实现CSRF令牌的创建和验证工作,比如Flask-SeaSurf( https://github.com/maxcountryman/flask-seasurf )、Flask-WTF内置的CSRFProtect( https://github.com/lepture/flask-wtf )等,在后面我们会详细介绍具体的实践内容。

注意 如果程序包含XSS漏洞,那么攻击者可以使用跨站脚本攻破可能使用的任何跨站请求伪造(CSRF)防御机制,比如使用JavaScript窃取cookie内容,进而获取CSRF令牌。

附注 可以访问OWASP的CSRF页面( https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) )了解详细的攻击原理介绍的防范措施。

作者:admin  创建时间:2025-02-10 18:34
最后编辑:admin  更新时间:2025-05-15 17:13