Posted by NeptLiang on February 22, 2024

第4章 跨站点请求伪造(CSRF)

CSRF的全名是Cross Site Request Forgery,翻译成中文就是跨站点请求伪造。

4.1 CSRF简介

仅仅诱使用户访问了一个页面,就以该用户身份在第三方站点里执行了一次操作。这个请求是攻击者所伪造的,所以这种攻击就叫做“跨站点请求伪造”。

4.2 CSRF进阶

4.2.1 浏览器的Cookie策略

  • 在上节提到的例子里,伪造的请求之所以能够被服务器验证通过,是因为用户的浏览器成功发送了Cookie的缘故。

  • 浏览器所持有的Cookie分为两种:

    • 一种是“Session Cookie”,又称“临时Cookie”;
    • 另一种是“Third-party Cookie”,也称为“本地Cookie”。
  • 两者的区别在于,

    • Third-party Cookie是服务器在Set-Cookie时指定了Expire时间,只有到了Expire时间后Cookie才会失效,所以这种Cookie会保存在本地;
    • 而Session Cookie则没有指定Expire时间,所以浏览器关闭后,Session Cookie就失效了。

      在浏览网站的过程中,若是一个网站设置了Session Cookie,那么在浏览器进程的生命周期内,即使浏览器新打开了Tab页,Session Cookie也都是有效的。

    • Session Cookie保存在浏览器进程的内存空间中;
    • 而Third-party Cookie则保存在本地
  • 如果浏览器从一个域的页面中,要加载另一个域的资源,由于安全原因,某些浏览器会阻止Third-party Cookie的发送

    IE出于安全考虑,默认禁止了浏览器在<img><iframe><script><link>等标签中发送第三方Cookie。

    在Firefox中,默认策略是允许发送第三方Cookie的。

    对于IE浏览器,攻击者则需要精心构造攻击环境,比如诱使用户在当前浏览器中先访问目标站点,使得Session Cookie有效,再实施CSRF攻击。

    在当前的主流浏览器中,默认会拦截Third-party Cookie的有:IE 6、IE 7、IE 8、Safari;不会拦截的有:Firefox 2、Firefox 3、Opera、Google Chrome、Android等。

    但若CSRF攻击的目标并不需要使用Cookie,则也不必顾虑浏览器的Cookie策略了。

4.2.2 P3P头的副作用

  • 浏览器拦截第三方Cookie的发送,在某种程度上来说降低了CSRF攻击的威力。可是这一情况在“P3P头”介入后变得复杂起来。

    P3P Header是W3C制定的一项关于隐私的基础。全称是The Platform for Privacy Preferences。

    如果网站返回给浏览器的HTTP头中包含有P3P头,则在某种程度上说,将允许浏览器发送第三方Cookie。 在IE下即使是<iframe><script>等标签也将不再拦截第三方Cookie的发送。

    在网站的业务中,P3P头主要用于类似广告等需要跨域访问的页面。

    但是很遗憾的是,P3P头设置后,对于Cookie的影响将扩大到整个域中的所有页面,因为Cookie是以域和path为单位的,这并不符合“最小权限”原则。

    正因为P3P头目前在网站的应用中被广泛应用,因此在CSRF的防御中不能依赖于浏览器对第三方Cookie的拦截策略,不能心存侥幸。

    很多时候,如果测试CSRF时发现<iframe>等标签在IE中居然能发送Cookie,而又找不到原因,那么很可能就是因为P3P头在作怪。

4.2.3 GET?POST?

  • 在CSRF攻击流行之初,曾经有一种错误的观点,认为CSRF攻击只能由GET请求发起。因此很多开发者都认为只要把重要的操作改成只允许POST请求,就能防止CSRF攻击。

    这种错误的观点形成的原因主要在于,大多数CSRF攻击发起时,使用的HTML标签都是<img><iframe><script>等带“src”属性的标签,这类标签只能够发起一次GET请求,而不能发起POST请求。而对于很多网站的应用来说,一些重要操作并未严格地区分GET与POST,攻击者可以使用GET来请求表单的提交地址。比如在PHP中,如果使用的是 $_REQUEST,而非 $_POST 获取变量,则会存在这个问题。

    如果服务器端已经区分了GET与POST,那么攻击者有什么方法呢?对于攻击者来说,有若干种方法可以构造出一个POST请求。

    最简单的方法,就是在一个页面中构造好一个form表单,然后使用JavaScript自动提交这个表单。

    攻击者甚至可以将这个页面隐藏在一个不可见的iframe窗口中,那么整个自动提交表单的过程,对于用户来说也是不可见的。

    在2007年的Gmail CSRF漏洞攻击过程中,安全研究者pdp展示了这一技巧。

    Google在不久后即修补了这个漏洞。

4.2.4 Flash CSRF

  • Flash也有多种方式能够发起网络请求,包括POST。

    在IE 6、IE 7中,Flash发送的网络请求均可以带上本地Cookie;但是从IE 8起,Flash发起的网络请求已经不再发送本地Cookie了。

4.2.5 CSRF Worm

  • 2008年9月,国内的安全组织80sec公布了一个百度的CSRF Worm。

    漏洞出现在百度用户中心的发送短消息功能中,只需要修改参数sn,即可对指定的用户发送短消息。而百度的另外一个接口则能查询出某个用户的所有好友。

    将两者结合起来,可以组成一个CSRF Worm——让一个百度用户查看恶意页面后,将给他的所有好友发送一条短消息,然后这条短消息中又包含一张图片,其地址再次指向CSRF页面,使得这些好友再次将消息发给他们的好友,这个Worm因此得以传播。

    这个蠕虫很好地展示了CSRF的破坏性——即使没有ⅩSS漏洞,仅仅依靠CSRF,也是能够发起大规模蠕虫攻击的。

4.3 CSRF的防御

4.3.1 验证码

  • 验证码被认为是对抗CSRF最简洁而有效的防御方法。

    但是验证码并非万能。很多时候,出于用户体验考虑,网站不能给所有的操作都加上验证码。因此,验证码只能作为防御CSRF的一种辅助手段,而不能作为最主要的解决方案

4.3.2 Referer Check

  • Referer Check在互联网中最常见的应用就是“防止图片盗链”。同理,Referer Check也可以被用于检查请求是否来自合法的“源”

    比如一个“论坛发帖”的操作,在正常情况下需要先登录到用户后台,或者访问有发帖功能的页面。在提交“发帖”的表单时,Referer的值必然是发帖表单所在的页面。如果Referer的值不是这个页面,甚至不是发帖网站的域,则极有可能是CSRF攻击。

  • Referer Check的缺陷在于,服务器并非什么时候都能取到Referer

    很多用户出于隐私保护的考虑,限制了Referer的发送。在某些情况下,浏览器也不会发送Referer,比如从HTTPS跳转到HTTP,出于安全的考虑,浏览器也不会发送Referer。

    在Flash的一些版本中,曾经可以发送自定义的Referer头。虽然Flash在新版本中已经加强了安全限制,不再允许发送自定义的Referer头,但是难免不会有别的客户端插件允许这种操作。

    出于以上种种原因,我们还是无法依赖于Referer Check作为防御CSRF的主要手段。但是通过Referer Check来监控CSRF攻击的发生,倒是一种可行的方法。

4.3.3 Anti CSRF Token

现在业界针对CSRF的防御,一致的做法是使用一个Token。在介绍此方法前,先了解一下CSRF的本质。

4.3.3.1 CSRF的本质

  • CSRF为什么能够成功?其本质原因是重要操作的所有参数都是可以被猜测到的

    只有预测出URL的所有参数与参数值,才能成功地构造一个伪造的请求。

  • 出于这个原因,可以想到一个解决方案:把参数加密,或者使用一些随机数,从而无法猜测到参数值。这是“不可预测性原则”的一种应用(参考“我的安全世界观”一章)。

    但是这个方法也存在一些问题。首先,加密或混淆后的URL将变得非常难读,对用户非常不友好。其次,如果加密的参数每次都改变,则某些URL将无法再被用户收藏。最后,普通的参数如果也被加密或哈希,将会给数据分析工作带来很大的困扰,因为数据分析工作常常需要用到参数的明文。

  • 因此,我们需要一个更加通用的解决方案来帮助解决这个问题。这个方案就是使用Anti CSRF Token

    Token需要足够随机,必须使用足够安全的随机数生成算法,或者采用真随机数生成器(物理随机,请参考“加密算法与随机数”一章)。

    Token应该作为一个“秘密”,为用户与服务器所共同持有,不能被第三者知晓。在实际应用时,Token可以放在用户的Session中,或者浏览器的Cookie中。

    由于Token的存在,无法再构造出一个完整的URL实施CSRF。

    Token需要同时放在表单和Session中。在提交请求时,服务器只需验证表单中的Token,与用户Session(或Cookie)中的Token是否一致,如果一致,则认为是合法请求;如果不一致,或者有一个为空,则认为请求不合法,可能发生了CSRF。

4.3.3.2 Token的使用原则

  • Anti CSRF Token在使用时,有若干注意事项

    • 防御CSRF的Token,是根据“不可预测性原则”设计的方案,所以Token的生成一定要足够随机,需要使用安全的随机数生成器生成Token。

    • 此外,这个Token的目的不是为了防止重复提交。所以为了使用方便,可以允许在一个用户的有效生命周期内,在Token消耗掉前都使用同一个Token。但是如果用户已经提交了表单,则这个Token已经消耗掉,应该再次重新生成一个新的Token。

      如果Token保存在Cookie中,而不是服务端的Session中,则会带来一个新的问题。如果一个用户打开几个相同的页面同时操作,当某个页面消耗掉Token后,其他页面的表单内保存的还是被消耗掉的那个Token,因此其他页面的表单再次提交时,会出现Token错误。在这种情况下,可以考虑生成多个有效的Token,以解决多页面共存的场景。

    • 最后,使用Token时应该注意Token的保密性

      Token如果出现在某个页面的URL中,则可能会通过Referer的方式泄露。

      因此在使用Token时,应该尽量把Token放在表单中。把敏感操作由GET改为POST,以form表单(或者AJAX)的形式提交,可以避免Token泄露。

      此外,还有一些其他的途径可能导致Token泄露。比如XSS或者一些跨域漏洞,都可能窃取到Token的值。

      CSRF的Token仅仅用于对抗CSRF,当网站还同时存在XSS时,这个方案就会变得无效,因为XSS可以模拟客户端浏览器执行任意操作。攻击者完全可以请求页面后,读出页面内容里的Token值,然后再构造出一个合法的请求。这个过程可以称之为XSRF,和CSRF以示区分。

      XSS带来的问题,应该使用XSS的防御方案予以解决,否则CSRF的Token防御就是空中楼阁。安全防御的体系是相辅相成、缺一不可的。


第9章 认证与会话管理

9.1 Who am I?

  • 认证的目的是为了认出用户是谁,而授权的目的是为了决定用户能够做什么。

9.2 密码的那些事儿

  • 密码是最常见的一种认证手段
    • 密码的优点是使用成本低,认证过程实现起来很简单
    • 缺点是密码认证是一种比较弱的安全方案,可能会被猜解
  • 密码强度”是设计密码认证方案时第一个需要考虑的问题

    一般在用户注册时,网站告知用户其所使用密码的复杂度

  • 目前并没有一个标准的密码策略,但是根据OWASP推荐的一些最佳实践,我们可以对密码策略稍作总结。

    • 密码长度方面:
      • 普通应用要求长度为6位以上
      • 重要应用要求长度为8位以上,并考虑双因素认证
    • 密码复杂度方面:
      • 密码区分大小写字母
      • 密码为
        • 大写字母
        • 小写字母
        • 数字
        • 特殊符号 中两种以上的组合
      • 不要有连续性的字符,比如1234abcd,这种字符顺着人的思路,所以很容易猜解;
      • 尽量避免出现重复的字符,比如1111。
  • 除了OWASP推荐的策略外,还需要注意,不要使用用户的公开数据,或者是于个人隐私相关的数据作为密码,比如不要使用
    • QQ号
    • 身份证号码
    • 昵称
    • 电话号码(含手机号码)
    • 生日
    • 英文名
    • 公司名 等作为密码
  • 密码必须以不可逆的加密算法,或者是单向散列函数算法,加密后存储在数据库中。

  • 为了避免密码哈希值泄露后能够直接通过彩虹表查询出密码明文,再计算密码明文的哈希值时,增加一个“Salt”。

    “Salt”是一个随机字符串,它的作用是为了增加明文的复杂度,并能使得彩虹表一类的攻击失效。

    Salt应该保存在服务器端的配置文件中,并妥善保管。

9.3 多因素认证

  • 支付宝就提供很多种不同的认证手段:

    • 支付密码
    • 手机动态口令
    • 数字证书
    • 宝令
    • 支付盾
    • 第三方证书

    等都可用于用户认证。

9.4 Session与认证

  • 为了告诉服务器应该使用哪一个Session,浏览器需要把当前用户持有的SessionID告知服务器。

    • 最常见的做法就是把SessionID加密后保存在Cooike中,因为Cookie会随着HTTP请求头发送,且受到浏览器同源策略的保护
  • Session劫持就是一种窃取用户SessionID后,使用该SessionID登录进目标账户,此时实际上是使用了目标账户的有效Session。

    如果SessionID是保存在Cookie中的,则可以称为Cookie劫持。

  • Cookie泄露的途径有很多,最常见的有XSS、网络Sniff,以及本地木马窃取。

    通过给Cookie标记httponly,可以有效地缓解CSS窃取Cookie的问题。

    但是其他的泄露途径,比如网络被嗅探,或者Cookie文件被窃取,则会设计客户端的环境安全,需要从客户端着手解决。

  • SessionID除了可以保存在Cookie中外,还可以保存在URL中,作为请求的一个参数。但是这种方式的安全性难以经受考验。

    在手机操作系统中,由于很多手机浏览器暂不支持Cookie,所以只能将SessionID作为URL的一个参数用于认证。

    安全研究者kxlzx曾经在博客上列出过一些无线WAP中因为sid泄露所导致的安全漏洞。其中一个典型的场景就是通过Referer泄露URL中的sid,QQ的WAP邮箱曾经出过此漏洞。

  • 在生成SessionID时,需要保证足够的随机性,比如采用足够强的伪随机数生成算法。

9.5 Session Fixation

  • 在用户登录网站的过程中,如果登录前后用户的SessionID没有发生变化,则会存在Session Fixation问题。

    用户Ⅹ先获取到一个未经认证的SessionID,然后将这个SessionID交给用户Y去认证,Y完成认证后,服务器并未更新此SessionID的值(注意是未改变SessionID,而不是未改变Session),所以Ⅹ可以直接凭借此SessionID登录进Y的账户。

    Ⅹ如何才能让Y使用这个SessionID呢?如果SessionID保存在Cookie中,比较难做到这一点。但若是SessionID保存在URL中,则Ⅹ只需要诱使Y打开这个URL即可。在上一节中提到的sid,就需要认真考虑Session Fixation。

    在discuz 7.2 的WAP版本中,就存在这样的一个Session Fixation攻击。

  • 解决Session Fixation的正确做法是,在登录完成后,重写SessionID

9.6 Session保持

  • 一般的应用都会给session设置一个失效时间,当到达失效时间后,Session将被销毁。

    但有一些系统,出于用户体验的考虑,只要这个用户还“活着”,就不会让这个用户的Session失效。从而可以通过不停地发起访问请求,让Session一直“活”下去。

    而Cookie是可以完全由客户端控制的,通过发送带有自定义Cookie头的HTTP包,也能实现同样的效果。

    在Web开发中,网站访问量如果比较大,维护Session可能会给网站带来巨大的负担。因此,有一种做法,就是服务器端不维护Session,而把Session放在Cookie中加密保存。当浏览器访问网站时,会自动带上Cookie,服务器端只需要解密Cookie即可得到当前用户的Session了。这样的Session如何使其过期呢?很多应用都是利用Cookie的Expire标签来控制Session的失效时间,这就给了攻击者可乘之机。

    Cookie的Expire时间是完全可以由客户端控制的。篡改这个时间,并使之永久有效,就有可能获得一个永久有效的Session,而服务器端是完全无法察觉的。

    攻击者甚至可以为Session Cookie增加一个Expire时间,使得原本浏览器关闭就会失效的Cookie持久化地保存在本地,变成一个第三方Cookie(third-party cookie)。

  • 如何对抗这种Session保持攻击呢?

    • 常见的做法是在一定时间后,强制销毁Session,这个时间可以是从用户登录的时间算起,设定一个阈值,比如3天后就强制Session过期。

    • 但强制销毁Session可能会影响到一些正常的用户,还可以选择的方法是当用户客户端发生变化时,要求用户重新登录。比如用户的IP、UserAgent等信息发生了变化,就可以强制销毁当前的Session,并要求用户重新登录。

    • 还需要考虑的是同一用户可以同时拥有几个有效Session

      若每个用户只允许拥有一个Session,则攻击者想要一直保持一个Session也是不太可能的。

9.7 单点登录(SSO)

  • 单点登录的英文全称是Single Sign On,简称SSO。它希望用户只需要登录一次,就可以访问所有的系统。

    从用户体验的角度看,SSO无疑让用户的使用更加地方便;从安全的角度看,SSO把风险集中在单点上,这样做是有利有弊的。

    • SSO的优点在于
      • 风险集中化,就只需要保护好这一个点。
      • 在单点处设计安全方案,甚至可以考虑使用一些较“重”的方法,比如双因素认证。
      • 此外对于一些中小网站来说,维护一份用户名、密码也是没有太大必要的开销,所以如果能将这个工作委托给一个可以信任的第三方,就可以将精力集中在业务上。
    • SSO的缺点同样也很明显,因为风险集中了,所以单点一旦被攻破的话,后果会非常严重,影响的范围将涉及所有使用单点登录的系统。

      降低这种风险的办法是在一些敏感的系统里,再单独实现一些额外的认证机制。比如网上支付平台,在付款前要求用户再输入一次密码,或者通过手机短信验证用户身份等。


//未完待Xu


参考文献

公众号二维码