前端之那些真实安全问题的背后

Posted by Melissa Zhou on 2018-12-29

那些遇到的真实安全问题的背后

写在前面:以前总觉得安全问题离自己很遥远,书本上说的问题都没有在实际中遇到。但是随着工作时间的增加,竟然真真实实的遇到了这些问题。每一次遇到安全问题都需要非常谨慎,把背后的原理弄明白。避免以后再次遇到。

问题一

window.open()或者a标签用target=”_balank”打开新页面存在安全问题。

关键词: rel="noopener noreferrer"window.open()target="_balank" , window.opener

问题描述: 新开页面的时候,被打开的页面中window.opener会记录下打开这个页面的引用。也就是说,在a.html中有个链接是b.html,这时候,b.html中的window.opener可能保存了a.html中的引用。这样就会让恶意网站有机可趁。

如果b.html是一个恶意网站,那么b.html中可以通过window.opener将a.html中的页面元素更改,或者直接更换页面的url。这样当用户浏览了b.html再回到a.html,这时候的页面已经是被恶意网站动过手脚的了。用户在这上面的任何操作都有风险。

比如,a.html原本是一个登陆页面,但是由于点击了b.html,原本a.html被替换成了一个一模一样的登陆页面,用户回到a.html时候,进行登陆,这时候,用户名和密码就已经泄露。

当然,上面例子建立在a.html中有那么个链接,链接的地址是一个恶意网站,这种情况在a.html页面中没有xss漏洞的时候是无法实现的。但是还有一种情况,a.html中的连接是正常的链接b.html,但是b.html中存在xss漏洞,有xss漏洞意味着可能执行一些恶意脚本,这时候b.html照样可以利用window.opener这个引用将a.html的url改掉。这个例子告诉我们,自己的页面没有漏铜,跳转到别人有漏洞的页面也有一定风险,真是防不胜防。

所以我们来总结下这个问题,讨论下怎么防范。

相信前端开发过程中打开一个页面是再正常不过的事情。当我们需要打开一个页面,无非有以下一些方法:

经过验证,几种方法打开页面后,目标页面noopener是否有值的情况如下:

window.opener是否有值 跨域 不跨域
1 window.open(‘https://baidu.com');
2 location.href=”https://baidu.com";
3 < a href=”https://baidu.com" target=”_balank”>百度 </a>
4 < a href=”https://baidu.com" >百度 </a>

从上面的表格看出,当前页面的跳转是没有问题的,第2,4行的结果可以说明,因为用这种方法打开的页面window.opener都没有值。问题就出在第1,3情况。这两种是我们最常用的打开新页面的方法。

这里讨论的这个问题最终引起的原因就是window.opener在1,3情况下会指向源头页面,使得恶意网站有机可趁,所以问题的关键就在于,怎么让这个值没有。

解决办法:

  • 1) a 链接解决办法:在a链接中加入rel=”noopener noreferrer”,如下:

    1
    <a href="b.html" target="_blank" rel="noopener noreferrer">进入一个有漏洞的页面</a>

    这样,新打开的页面中window.opener就不会有值。

    但是这个方法有兼容性问题,有的老旧浏览器不支持rel=”noopener noreferrer”。

  • 2) window.open()解决方案,不要直接使用 window.open()方法,进行如下包装。

    1
    2
    3
    4
    5
    function openUrl(url) {
    var newTab = window.open();
    newTab.opener = null;
    newTab.location = url;
    }

    这样打开的页面window.opener也不会有值。

思考: 浏览器中涉及到跳转的时候通常会有一些意想不到的问题,需要特别留意。上面的问题我真实的遇到了,但是好在我们打开的页面都没有安全漏洞。

问题二

URL跳转漏洞

问题描述: 有一个公用的模块,这个模块操作成功以后会根据url中的某个参数进行跳转。这个功能如果没有经过任何处理,是非常危险的。比如:不法分子可能任意修改url中跳转参数,做成一个按钮,然后诱惑用户点击,点击以后用户看到的是我们的页面(只是跳转参数被修改了),用户在这个页面操作完成以后会跳转到一个不法分子修改恶意的页面。由于对我们网站的信任,用户可能没有防范这个网页,这时不法分子又可以为非作歹了。但是我们怎么可能会有这么大的安全问题?我们当然对跳转参数做了限制。跳转的url只能是白名单中的存在的域名,否则不会跳转。

故事就发生在上面的背景下。

基于上面的描述,我们需要正确的获取到url的域名,然后和白名单进行比对。获取url的域名的正则表达式,网上一搜一大堆,正确与否,还需要自己去验证。我们之前的正则表达式就是:

1
2
3
4
5
6
7
8
function getHostname (url) {
var regHostName = /^(.+\/\/)?([\w.-]+)(:\d*)?.*$/,
var hostname = '',
if (!url) {
hostname = url.match(regHostName) ? url.match(regHostName)[2] : '';
}
return hostname ? hostname.split('.').slice(-2).join('.') : '';
}

一切都很正常,直到有一天,出现了一个跳转连接”https://taobao.com@baidu.com “;

复制这个链接到浏览器中尝试,打开的连接是baidu.com。浏览器默认把@符号前面地址都省略了,只读取了后面的部分。这就会出现一个问题,假如taobao.com是我们网站中白名单的域名,而baidu.com不是,那么用上面的正则表达式其实错误的匹配到了taobao.com为url的域名。于是判断出可以跳转。而跳转以后浏览器没有跳转到”https://taobao.com@baidu.com" 这个页面,而是跳转到了baidu.com。这样就出现一个漏洞,不法分子可以通过@把自己正真的域名隐藏起来,这样就可以跳转到任何页面了。只要不法分子多试几次,找到一个白名单以内的域名加一个@再加一个任意自己的恶意网站,就可以伪造一个链接。想想还是很可怕。虽然客户是因为点击了不法分子伪造的链接而导致,但是因为用户是从我们的域跳转出去,用户如果上当,会对我们的网站信誉产生怀疑。

解决办法: 这个漏洞的产生其实是正则表达式判断出错以及浏览器自动屏蔽了@符号前面的地址所导致。正则表达式在判断”https://taobao.com@baidu.com “这种url的时候,第二个捕获括号出错,[\w.-],/w匹配字母数字下划线,.号在方括号中表示匹配“.”号,- 表示匹配“-”号。所以整个捕获到的是“//”之后到“@”符号之间的字符,也就是”taobao.com”,由于“@”符号不匹配,所以后面的没有匹配到。

浏览器自动屏蔽了@符号前面的地址这个问题我们无法做任何修改,但是,要解决这个问题可以将@符号也添加到正则表达式匹配的字符中。最后获取域名的时候对有@字符特殊处理。这样就能获取正确的域名。致使还没有到达浏览器就进行了拦截。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getHostname (url) {
var regHostName = /^(.+\/\/)?([\w.@-]+)(:\d*)?.*$/,
var hostname = '',
if (!url) {
hostname = url.match(regHostName) ? url.match(regHostName)[2] : '';
}
if(hostname) {
if (hostname.indexOf('@') === -1) {
return hostname.split('.').slice(-2).join('.');
} else {
var hostAttr = hostname.split('@').slice(-1);
return hostAttr[0];
}
} else {
return hostname;
}
}

思考: url的跳转如果可以由第三方自己写入,那跳转的链接一定需要白名单的验证,只有在白名单以内的域名才能跳转。浏览器对于某些特殊处理的字符更需要多加注意,有可能会成为漏洞被人利用。

问题三

img xss 漏洞

关键词: xssimgonerror

问题描述: 没想到也会遇上著名的xss问题。网站上有个反馈问题的页面,是一个富文本输入框。可以上传图片。做富文本的时候也想到了可能会出现xss的问题,所以我们对一些关键字和符号进行了处理。但是,百密一疏,我们漏掉了一个关键字onerror 。故事就这样发生了。

onerror是什么?官方网站定义:onerror 事件在加载外部文件(文档或图像)发生错误时触发。支持的HTML标签有\<img>, \<input type=”image”>, \<object>, \<script>, \<style> ,其中图片就是这次的主角。用户写入的图片的src的地址不存在,就会触发onerror事件,比如:

1
<img src="image.gif"  onerror="javascript:alert(document.cookie);">

这样用户进入这个页面就可以看到一个弹窗,弹出cookie。这里只是举个例子,既然可以执行js,那能做的事情很多。还有一个很常见的xss攻击是像下面这样,onerror中指定的src仍然不存在,于是会一直触发onerror,直到用户浏览器奔溃或者关掉当面页面。

1
<img src="image.gif"  onerror="javascript:this.src='src/images/default.jpg';">

基于如果光看上面的问题也许并不能很快的明白什么是xss,那先来总结下,什么是xss?这样也许才能把xss问题从源头解决。

什么是xss漏洞

xss 跨站脚本攻击,从字面意思的理解,就是web页面中被恶意插入非本站的js脚本。由于同源策略的约束,原本web在执行js脚本的时候都会检查是不是同源,只有同源的js才能被执行。而xss攻击就是利用的web页面的漏洞使网站中执行了不是同源的js脚本。

如果页面中存在这样的漏洞非常可怕。也就是说,攻击者可以在你的网站上执行任何js代码。那么也就意味着,你可以改变页面的dom结构,获取页面上的数据,获取用户名密码,把页面从定向等等···。

xss漏洞的分类

网上有两种分类方法,我认为这种比较好理解。

共分成三类:

  • 1)反射型:经过后端,不经过数据库

    反射型我的理解是,当用户输入某些信息,后端没有经过转义或者特殊的处理,就直接又反馈给了前端的页面。这样,后端就好像做了一个反射,把输入的东西原原本本的传给前端,如果用户输入的时候输入类似\<script>alert(‘hack’)\</script>,那么前端会把这个当成dom元素进行解析。这样就会执行js脚本。

    过程如下:

    浏览器 -> 后端 -> 浏览器。

  • 2)存储型:经过后端,经过数据库

    存储型,最重要的特点就是,经过了数据库,而且一旦被攻击,会伴随着数据库,一直存在,直到数据库被清除了。这里就好比在一个可以评论的页面,添加评论,但是提交的数据没有经过处理就直接存在数据库,并且又返回给前端页面。

    过程如下:

    浏览器 -> 后端 -> 数据库 -> 后端 -> 浏览器。

    1、2的对比图如下:

    xss反射型和存储型对比

  • 3)DOM:不经过后端, 不经过数据库

    有的文章解释DOM-XSS 就是通过URL中参数进行攻击的,比如我们发现页面上有某个字段是根据url中的参数进行显示的,那就可以构造一个类似这样的url:

    https://a.com?a=\<img src=a.jpg onerror=”javascript:this.src=’src/images/default.jpg’;”/>

    那么页面也会执行onerror中的脚本。

    但是我认为,DOM-XSS是基于DOM元素的一种xss,url只是DOM元素中的一种,还有很多,比如input,script,所以DOM-XSS是一个广义的概念,他的主要特点是通过DOM元素发生。DOM-XSS的可以能是反射型的,可能是存储型的,也有可能只发生在前端。这取决于是否经过后端,是否经过数据库。

综上,我们可以发现,xss攻击经常发生一下几种场景中,因为他们有个共同点:输入–>展示输入类容

  • 留言系统
  • 邮件系统
  • 评论系统
  • 富文本编辑器

回头看,我遇到的问题属于存储型,因为用户评论是储存在数据库的。并未只要这条评论没有删除,浏览到这个页面的用户就都能看见。

解决办法: 对于xss攻击的解决办法:

  • 1)过滤关键字:script,style,a,href
  • 2)对html中的特殊字符“<”,“>”,“&”进行转义

一言以蔽之:就是让用户输入的js语句不能直接被浏览器执行 ,这就是防止xss的终极原则。

我的img问题是因为onerror中的js语言被执行了,那么,我们可以屏蔽onerror字段,也可以不输出onerror中间的脚本。原理大概就这样。

后记 之前在网上看到一篇关于测试xss的方法。看了以后我有点震惊,原来xss可以发生的场景如此之多,防不胜防,大家感受一下:

测试xss的方法

所以xss问题一定要谨慎对待,因为攻击者会想法设法绕过限制,构造可以成功的payload。

前端的安全问题远远不止我遇到的这些,还是那句话,一旦遇到安全问题,不能只满足解决一个问题,而是要了解背后的原理,从源头解决一类问题。

其他参考连接:

http://web.jobbole.com/95159/

http://www.cnblogs.com/ilinuxer/p/5245983.html

https://tgideas.qq.com/doc/frontend/spec/special/safe.html

https://blog.csdn.net/tanzhen1991910/article/details/53085274

https://tech.meituan.com/fe_security.html