Web前端常见的安全问题介绍及解决方案(二)

上一篇文章中介绍了前端常见的两种安全问题:SQL注入XSS,这篇要讲的是CSRF超链接新窗口跳转问题。

CSRF 跨站请求伪造

跨站请求伪造(英语:Cross-site request forgery),也被称为one-click attack 或者session riding,通常缩写为CSRF 或者XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。(注:维基百科)

原理

攻击者以某种方式盗用了用户的身份,以用户的名义在用户不知情的情况下向服务器发送恶意请求,对服务器来说这个请求时完全合法的(因为是合法用户发出的请求),从而达到攻击者的目的。

攻击步骤如下:

  1. 用户a打开了浏览器,访问正常的网站A,输入用户名、密码正常发送登录请求到网站A的服务器
  2. 此时网站A的服务器确认用户a的登录信息将cookie信息返回给浏览器,用户a在cookie有效时间内都可以正常的请求网站A的服务器
  3. 用户a没有退出网站A,在同一个浏览器打开了网站B
  4. 网站B向用户返回了一段恶意代码,这段代码向网站A发起请求
  5. 浏览器在发起请求时会自动带上网站A的cookie信息,攻击者便达到了在用户a不知情的情况下以其身份向网站A发起请求

举个栗子

小明在银行中有一笔存款,银行网站通过请求 http://bank.com/withdraw?money=1000&to=username 可以将发起请求的用户账户下的1000元转到username账户下。银行服务端在接收到请求后,会验证请求中的cookie信息是否合法(用户是否存在、是否登录状态),合法则请求有效。

攻击者quincychen在银行中也有账户,通过测试他知道了银行转账的请求格式,于是打起了使用CSRF的念头。首先,他自己上线了一个网站A,网站中包含这样的代码

<img src="http://bank.com/withdraw?money=10000&to=quincychen" />

之后他又写了一个脚本不断的向随机的邮箱发送网站A的链接,其中就有小明的邮箱。此时,小明刚好登录了银行网站查看刚到账的工资,收到邮件在未关闭银行网站的情况下打开了其中的链接。悲剧就这么发生了,小明账户中的10000元就这样不知不自觉的赚到了quincychen账户中。即使后来小明查到了交易记录,那也是一条合法的转账记录,没有任何被攻击的痕迹。

防范CSRF攻击

目前防御CSRF攻击的主要手段有三种

  1. 验证http请求的Referer字段
  2. 在请求中添加token验证
  3. 在http请求中自定义属性并验证

验证HTTP Referer

在HTTP协议的规定中,HTTP请求头带有Referer字段,它记录了HTTP请求的来源地址。正常情况下,安全受限的HTTP请求都来自于自己的网站。比如,栗子中的转账请求应该来自于银行网站http://bank.com,而不是网站A。**CSRF**攻击只能在攻击者自己的网站中发起,因此,银行网站可以通过验证HTTP请求中的Referer字段来确定请求是否合法。

不足之处

然而,这种方法并不完美。首先,Referer字段值是由浏览器设定的,虽然HTTP协议有明确的规定,但还是很难确保每个浏览器的具体实现是标准、安全的。就目前所知,存在一些方法可以篡改IE6FireFox2发起请求中的Referer值。

####请求中添加token验证

CSRF攻击成功的关键在于攻击者可以完全伪造用户的请求,所有的验证信息都存在cookie中,攻击者即使不知道验证信息的验证方式和结构也可以攻击成功,所以可以通过在请求中放入攻击者无法伪造的信息(不能保存在cookie中)来防御CSRF攻击。比如,前端可以在HTTP请求中以参数的形式加入一个随机产生的token,后端建立路由拦截器来验证token,如果缺少token或token错误则认为是危险请求。

不足之处

难以保证token本身的安全。特别是在一些支持用户自己发表内容的网站(博客园、论坛等),黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上 token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击。为了避免这一点,系统可以在添加 token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加。不过,即使这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个 token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。

自定义HTTP头属性并验证

也是使用 token 并进行验证的方式,不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以同意给所有的请求头加上自定义属性(如csrf-token)。

不足之处

然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。

超链接新窗口跳转问题

在网页中,如果我们要实现让浏览器自动在新标签页中打开链接,很普遍的方式是

<a href="http://www.google.com" target="_blank">google</a>

然而,很多同学没有注意到,它其实存在着不安全的因素。

parent与opener

在了解安全问题前,我们先了解一下问题的根源。

前端的同学应该都知道(不知道的去面壁),在iframe标签中提供了一个用于父子页面交互的对象,我们可以通过window.parent从iframe中的页面访问父级页面的window对象(这里涉及到前端安全的另一个问题:使用iframe标签)

openerparent一样,两者的区别在于,opener用于<a target="_blank"></a>。通过window.opener可以从新标签页获取到来源页面的window对象。

那么,这个安全问题到底是什么?

举个栗子

攻击者在自己的网站A上发布了一篇很不错的博客并在各种技术社区中推广,因此很多网站引用了他的文章,这其中就有部分知名的技术周刊:网站B。网站B采用以下代码推荐攻击者的文章

<a href="http://www.test.com/article/1" target="_blank">前端安全问题总结</a>

攻击者查看了网站B的前端代码,确认了其引用方式,首先,上线了一个外观和网站B非常相似的网站C(网址:http://www.attack.com),不同的是网站C上会引导用户输入各种敏感信息。同时,他在原先的博客文章页加上了以下代码

<script>
    window.opener.location.replace("http://www.attack.com")
</script>

当用户浏览网站B,点击了攻击者的推荐文章跳到新标签页开始浏览推荐文章,然而,在不知不觉中,网站B的标签页已经被替换到了非常相似的恶意网站C。

用户在浏览完推荐文章后回到了网站B(但其实是网站C),之后在引导下输入了各种敏感信息,最终导致用户信息泄露。

防范措施

iframe我们可以通过sandbox属性来控制它的各种权限,而对于超链接a,可以使用以下方法:

  1. noopener

    为了安全,现代浏览器都支持在a标签的rel属性中设定rel=noopener,这样在打开的新标签页中将无法再使用opener对象

    <a href="http://www.test.com/article/1" target="_blank" rel="noopener">前端安全问题总结</a>
    

    看着好像很完美?我们打开caniuse看看兼容性

    emmm,这个……我们还是看下一个吧

  2. 使用Javascript跳转

    为了兼容性考虑,可以使用javascript实现新窗口跳转

    function openNewTab (url) {
        var newTab = window.open()
        newTab.opener = null
        newTab.location = url
    }
    
0%