关于前端安全性那些事

前言

这篇文章鸽了好久了,一直在草稿箱呆着,刚好这周在家休病假,终于有时间更了。

为啥突然想写前端安全性相关的文章了呢?
起因是在逛boss直聘或者前程无忧等提桶软件时,看到很多职位要求都写了精通前端安全性相关问题,我:黑人问号脸.jpg!现在什么都要精通了嘛?

在这里插入图片描述
那么前端相关的安全性问题都有哪些呢?

1、XSS攻击

提起前端安全性问题,我第一个想起来的就是XSS( 全称为Cross Site Scripting,为了和CSS分开简写为XSS,也就是跨站脚本注入)。指攻击者在 html 中嵌入script,浏览器遇到 html 中的 script 标签时,会解析并执行其中的js代码,攻击者直接为所欲为。

XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行

那么问题来了,他怎么在我的html中嵌入代码呢?举个栗子,在三大框架没有出来之前,我们渲染一般通过数据拼接html,然后使用 innerHtml、outerHTML、document.write 的方式,往页面中动态添加渲染元素,假如页面有个查询功能,点击查询按钮后,url会拼接用户输入的 search key,那么攻击者可以通过直接修改url拼接的key为<script>alert('XSS');</script>,当用户点了这个改造过的url后,查询的key复显,碰巧你没有对用户输入的数据做转义,浏览器遇到 script 标签就会直接执行 alert。这只是xss攻击最简单的一种方式。

不过现在三大框架已经不需要我们通过上述的方式渲染数据了,再加上模板语法渲染时做了处理,已经很大程度上帮我们避免了这种攻击方式。

除此之外还有很多方式可以注入,美团技术团队分享的 这篇文章 总结的非常好,XSS攻击被分为存储型反射型DOM型三种。

1.1、存储型

存储型就是攻击者将恶意代码提交到目标网站的数据库中,当用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器,用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。

恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

预防这种漏洞,有两种常见做法:

  1. 纯前端渲染,代码和数据分离,比如vue等框架。
  2. 对HTML做充分转义,如果拼接html是必须的(比如需要做SEO优化),那么需要对HTML各个插入点进行充分的转义,常用的模板引擎,如 doT.js、ejs、FreeMarker 等。

1.2、反射型

反射型 XSS 的攻击步骤:
攻击者构造出特殊的 URL,其中包含恶意代码。
用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。是不是感觉很熟悉,我们找盗版资源时候,那些网站上那些性感游戏图片??

1.3、DOM型

DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。 与存储型比较类似,唯一的区别就是DOM型不用通过数据库,也就是开篇时最上面讲的那种,通过serch input 进行注入。

如何预防:

  1. 如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患。
  2. 尽量不使用 .innerHTML、.outerHTML、document.write()等方法,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等。
  3. location、onclick、onerror、onload、onmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。尽量避免把不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患。

通用的预防方案: Content Security Policy

严格的 CSP 在 XSS 的防范中可以起到以下的作用:

  • 禁止加载外域代码,防止复杂的攻击逻辑。
  • 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
  • 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
  • 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。

2、 Iframe

2.1、让自己的网站不被其他网站的 iframe 引用

通过判断location,如果不是自己的网站则重定向到其他网站,可以是无法访问的页面,也可以直接给他跳转到百度、谷歌等。

if(top.location != self.location){
    top.location.href = 'http://xxxxx.com'
}

2.2 、禁用使用iframe内嵌页面操作自己网站

通过h5的新特性 Sandbox

Content-Security-Policy: sandbox;
Content-Security-Policy: sandbox <value>;

可选值如下:

  • allow-forms
    允许嵌入式浏览上下文提交表单。如果未使用此关键字,则不允许此操作。

  • allow-modals
    允许嵌入式浏览上下文打开模态窗口。

  • allow-orientation-lock
    允许嵌入式浏览上下文禁用锁定屏幕方向的功能。

  • allow-pointer-lock
    允许嵌入式浏览上下文使用Pointer Lock API (en-US)。

  • allow-popups
    允许弹出窗口(像window.open,target=“_blank”,showModalDialog)。如果未使用此关键字,则该功能将无提示失败。

  • allow-popups-to-escape-sandbox
    允许沙盒文档打开新窗口而不强制沙盒标记。例如,这将允许安全地沙箱化第三方广告,而不会对登陆页面施加相同的限制。

  • allow-presentation
    允许嵌入器控制 iframe 是否可以启动演示会话。

  • allow-same-origin
    允许将内容视为来自其正常来源。如果未使用此关键字,则嵌入的内容将被视为来自唯一来源。

  • allow-scripts
    允许嵌入式浏览上下文运行脚本(但不创建弹出窗口)。如果未使用此关键字,则不允许此操作。

  • allow-top-navigation
    允许嵌入式浏览上下文将内容导航(加载)到顶级浏览上下文。如果未使用此关键字,则不允许此操作。

2.3、 打开新标签跳转 window.opener

我们常用的打开新标签页的方式通常有两种

<a target='_blank' href='http://www.baidu.com'>
 window.open('http://www.baidu.com')

考虑以下场景:
A 页面通过 <a> 或 window.open 方式,打开 B 页面。但是 B 页面存在恶意代码如下:

// 此代码仅针对打开新标签有效
window.opener.location.replace('https://www.baidu.com') 

此时,用户正在浏览新标签页,但是原来网站的标签页已经被导航到了百度页面。甚至恶意网站可以伪造一个足以欺骗用户的页面,从而获得用户的敏感信息。即使在跨域状态下 opener 仍可以调用 location.replace 方法。

对于a标签,我们可以采用配置rel属性的方式解决此问题:

// noopener会将 window.opener 置空,从而源标签页不会进行跳转(存在浏览器兼容问题)
<a target="_blank" href="" rel="noopener"></a>

对于window.open,可采用手动置空的方式解决此问题:

  const newTab = window.open();
  newTab.opener = null;
  newTab.location = targetUrl;

3、跨站请求伪造(Cross-site request forgery)

wiki百科的解释是这样的:

跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求是发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。

wiki举的例子:

假如一家银行用以执行转账操作的URL地址如下: https://bank.example.com/withdraw?account=AccoutName&amount=1000&for=PayeeName

那么,一个恶意攻击者可以在另一个网站上放置如下代码:
<img src=“https://bank.example.com/withdraw?account=Alice&amount=1000&for=Badman” />

如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000资金。

这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。

防御措施(推荐添加token / HTTP头自定义属性):

  • HTTP 协议中使用 Referer 属性来确定请求来源进行过滤

HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。以上文银行操作为例,Referer字段地址通常应该是转账按钮所在的网页地址,应该也位于bank.example.com之下。而如果是CSRF攻击传来的请求,Referer字段会是包含恶意网址的地址,不会位于bank.example.com之下,这时候服务器就能识别出恶意的访问。

  • 请求地址添加 token ,使黑客无法伪造用户请求
  • HTTP 头自定义属性验证(类似上一条)
  • Cookie-SameSite属性

Cookie 的SameSite属性用来限制第三方 Cookie,从而减少安全风险。它可以设置三个值。
Strict完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外(只包括三种情况:链接,预加载请求,GET 表单)。
None 关闭SameSite。

还记得浏览器的同源策略吗,也就是跨域访问限制,同源策略确实减少了部分CSRF发生,但并不能完全解决该问题。

4、ClickJacking(点击劫持)

一般会利用透明 iframe 覆盖原网页诱导用户进行某些操作达成目的。

原理非常简单

  1. 访问者被恶意页面吸引。怎样吸引的不重要。

  2. 页面上有一个看起来很诱人的图片(例如:点我,超好玩!或者美女秘书等你来体验!”)。
    在这里插入图片描述

  3. 恶意页面在该链接上方放置了一个透明的 <iframe>,其 src 来自于 bilibili.com,这使得“点赞”按钮恰好位于该链接上面。这通常是通过 z-index 实现的。

  4. 用户尝试点击该链接时,实际上点击的是“点赞”按钮。而你电脑默认已经登陆过了bilibili,你不知不觉就已经给某个视频点了个赞。

推荐的防御方案:
使用X-Frame-Options: DENY这个HTTP Header来明确的告知浏览器,不要把当前HTTP响应中的内容在HTML Frame中显示出来。

5、HTTPS重定向问题

由于不能改变用户的输入习惯,很多网站在实现全站HTTPS后,选择通过配置强制301的方式让用户的http请求重定向到https,以保障网站的安全性。

比如我们输入“http://www.baidu.com”或“www.baidu.com”最终都会被302重定向到“https://www.baidu.com”。
然而,当我们第一次通过 HTTP 或域名进行访问时,302重定向有可能会被劫持,篡改成一个恶意或钓鱼网站。

对于该问题,推荐看这篇文章,介绍了苏宁遇到该问题的经历。

解决方案就是采用HSTS(HTTP Strict Transport Security)方案。详见文档

6、CDN劫持

出于性能考虑,前端应用通常会把一些静态资源存放到CDN(Content Delivery Networks)上面,例如 某些j 大型 js库或者脚本和 style 文件。这么做可以显著提高前端应用的访问速度。但这也给了别人劫持的可能。如果攻击者劫持了CDN,或者对CDN中的资源进行了污染,攻击者可以肆意篡改我们的前端页面,对用户实施攻击。

劫持的原理我找了几篇文章都没解释清楚,看了知乎上一篇CDN原理简单介绍文章后,梳理下来如下:由于CDN结束本身就是一种DNS(将域名和IP地址相互映射)劫持,只不过CDN是一种良性的劫持,通过负载均衡分发,将从当前地区距离最近的服务器上,把资源返回出去。那么CDN劫持,估计是在DNS劫持的过程中进行了某种操作,对返回的资源做了替换或者修改,从而进行攻击。
在这里插入图片描述
解决方案:
在script 和 link 标签上添加 integrity属性,它通过验证获取到的资源的哈希值是否和标签上该属性提供的哈希值一样来判断资源是否被篡改。

<script type="text/javascript" integrity="sha256-mY9nzNMPPf8oL3CJss7THIEoXAC2ToW1tEX0NBhMvuw= sha384-ncIKElSEk2OR3YfjNLRSY35mzt0CUwrpNDVS//iD3dF9vxrWeZ7WPlAPJTqGkSai" crossorigin="anonymous"  src="//s.url.cn/xxxx/xxx.js?_offline=1"></script>

使用 SRI 需要两个条件:一是要保证 资源同域 或开启跨域,二是在<script>中 提供签名 以供校验。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值