一、概述
“安全”是个很大的话题,各种安全问题的类型也是种类繁多。如果我们把安全问题按照所发生的区域来进行分类的话,那么所有发生在后端服务器、应用、服务当中的安全问题就是“后端安全问题”,所有发生在浏览器、单页面应用、Web页面当中的安全问题则算是“前端安全问题”。比如说,SQL注入漏洞发生在后端应用中,是后端安全问题,跨站脚本攻击(XSS)则是前端安全问题,因为它发生在用户的浏览器里。
二、常见前端安全问题及防护
1、跨站脚本攻击(Cross-Site Scripting)
XSS是跨站脚本攻击(Cross-Site Scripting)的简称。XSS这类安全问题发生的本质原因在于:浏览器错误的将攻击者提供的用户输入数据当做JavaScript脚本给执行了。
XSS有几种不同的分类办法,例如按照恶意输入的脚本是否在应用中存储,XSS被划分为“存储型XSS”和“反射型XSS”,如果按照是否和服务器有交互,又可以划分为“Server Side XSS”和“DOM based XSS”。
无论怎么分类,XSS漏洞始终是威胁用户的一个安全隐患。攻击者可以利用XSS漏洞来窃取包括用户身份信息在内的各种敏感信息、修改Web页面以欺骗用户,甚至控制受害者浏览器,或者和其他漏洞结合起来形成蠕虫攻击,等等。总之,关于XSS漏洞的利用,只有想不到没有做不到。
如何防御?
对数据进行严格的输出编码,使得攻击者提供的数据不再被浏览器认为是脚本而被误执行。例如<script>在进行HTML编码后变成了<script>,这段数据就不会被当做脚本执行了。
需要根据输出数据所在的上下文来进行相应的编码。数据放置于HTML元素中,需进行HTML编码,放置于URL中,需要进行URL编码。此外,还有JavaScript编码、CSS编码、HTML属性编码、JSON编码等等。(一些主流前端开发框架基本上都默认提供了前端输出编码,这大大减轻了前端开发小伙伴们的工作负担)
其他的防御措施,例如设置CSP HTTP Header、输入验证、开启浏览器XSS防御等等都是可选项,原因在于这些措施都存在被绕过的可能,并不能完全保证能防御XSS攻击。不过它们和输出编码却可以共同协作实施纵深防御策略。
2、使用iframe的风险
有时前端页面需要用到第三方提供的页面组件,通常会以iframe的方式引入。典型的例子是使用iframe在页面上添加第三方提供的广告、天气预报、社交分享插件等等。
iframe在给我们的页面带来更多丰富的内容和能力的同时,也带来了不少的安全隐患。因为iframe中的内容是由第三方来提供的,默认情况下他们不受我们的控制,他们可以在iframe中运行JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会破坏前端用户体验。
如果说iframe只是有可能会给用户体验带来影响,看似风险不大,那么如果iframe中的域名因为过期而被恶意攻击者抢注,或者第三方被黑客攻破,iframe中的内容被替换掉了,从而利用用户浏览器中的安全漏洞下载安装木马、恶意勒索软件等等,这问题可就大了。
如何防御?
在HTML5中,iframe有个sandbox安全属性,通过它可以对iframe的行为进行各种限制,充分实现“最小权限“原则。例如: <iframe sandbox src="..."> ... </iframe>
sandbox提供了丰富的配置参数,可以进行较为细粒度的控制。
allow-forms:允许iframe中提交form表单
allow-popups:允许iframe中弹出新的窗口或者标签页(例如,window.open(),showModalDialog(),target=”_blank”等等)
allow-scripts:允许iframe中执行JavaScript
allow-same-origin:允许iframe中的网页开启同源策略
更多详细的资料,可以参考iframe中关于sandbox的介绍。
3、点击劫持
这是一种欺骗性比较强,同时也需要用户高度参与才能完成的一种攻击。通常的攻击步骤是这样的:
1、攻击者构造一个诱导用户点击的内容,如Web页面小游戏
2、将被攻击的页面放入到iframe当中
3、利用z-index等CSS样式将这个iframe叠加到小游戏的垂直方向的正上方
4、把iframe设置为100%透明度
5、受害者访问这个页面,肉眼看到的是一个小游戏,如果受到诱导进行了点击的话,实际上点击到的却是iframe中的页面
点击劫持的危害在于,攻击利用了受害者的用户身份,在其不知情的情况下进行一些操作。
如何防御?
有多种防御措施都可以防止页面遭到点击劫持攻击,例如Frame Breaking方案。一个推荐的防御方案是,使用X-Frame-Options:DENY 这个HTTP Header来明确的告知浏览器,不要把当前HTTP响应中的内容在HTML Frame中显示出来。
关于点击劫持更多的细节,可以查阅OWASP Clickjacking Defense Cheat Sheet。链接地址:https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet
4、错误的内容推断
攻击场景:某网站允许用户在评论里上传图片,攻击者将含有恶意JavaScript的脚本文件扩展名改成图片格式进行上传(这涉及到了恶意文件上传这个常见安全问题,但是由于和前端相关度不高因此暂不详细介绍)。接下来,受害者在访问这段评论的时候,浏览器会去请求这个伪装成图片的JavaScript脚本,而此时如果浏览器错误的推断了这个响应的内容类型(MIME types),那么就会把这个图片文件当做JavaScript脚本执行,于是攻击也就成功了。
问题的关键:后端服务器在返回的响应中设置的Content-Type Header仅仅只是给浏览器提供当前响应内容类型的建议,而浏览器有可能会自作主张的根据响应中的实际内容去推断内容的类型。
上例中,后端通过Content-Type Header建议浏览器按图片来渲染HTTP响应,但浏览器发现响应的其实是JavaScript,于是就擅自做主把这段响应当做JS脚本来解释执行,安全问题也就产生了。
如何防御?
浏览器根据响应内容来推断其类型,本来这是个很“智能”的功能,是浏览器强大的容错能力的体现,但是却会带来安全风险。要避免出现这样的安全问题,办法就是通过设置X-Content-Type-Options这个HTTP Header明确禁止浏览器去推断响应类型。
同样是上面的攻击场景,后端服务器返回的Content-Type建议浏览器按照图片进行内容渲染,浏览器发现有X-Content-Type-Options HTTP Header的存在,并且其参数值是nosniff,因此不会再去推断内容类型,而是强制按照图片进行渲染,从而最终防止了安全问题的发生。
更多关于X-Content-Type-Options的细节请参考:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
5、不安全的第三方依赖包
现如今进行应用开发,无论是后端服务器应用还是前端应用开发,绝大多数时候我们都是在借助开发框架和各种类库进行快速开发。
这样做的好处显而易见,但是与此同时安全风险也在不断累积——应用使用了如此多的第三方代码,不论应用自己的代码的安全性有多高,一旦这些来自第三方的代码有安全漏洞,那么对应用整体的安全性依然会造成严峻的挑战。
如何防御?
尽量减少第三方依赖,选用相对成熟的依赖包。
使用自动化工具检查这些第三方代码有没有安全问题,比如NSP(Node Security Platform),Snyk等等。
6、HTTPS中间人攻击
即使是服务器端开启了HTTPS,也还是存在安全隐患,黑客可以利用SSL Stripping这种攻击手段,强制让HTTPS降级回HTTP,从而继续进行中间人攻击。
问题的本质在于浏览器发出去第一次请求就被攻击者拦截了下来并做了修改,根本不给浏览器和服务器进行HTTPS通信的机会。大致过程如下,用户在浏览器里输入URL的时候往往不是从https://开始的,而是直接从域名开始输入,随后浏览器向服务器发起HTTP通信,然而由于攻击者的存在,它把服务器端返回的跳转到HTTPS页面的响应拦截了,并且代替客户端和服务器端进行后续的通信。由于这一切都是暗中进行的,所以使用前端应用的用户对此毫无察觉。
如何防御?
解决这个安全问题的办法是使用HSTS(HTTP Strict Transport Security),它通过下面这个HTTP Header以及一个预加载的清单,来告知浏览器在和网站进行通信的时候强制性的使用HTTPS,而不是通过明文的HTTP进行通信:
Strict-Transport-Security: max-age=; includeSubDomains; preload
这里的“强制性”表现为浏览器无论在何种情况下都直接向服务器端发起HTTPS请求,而不再像以往那样从HTTP跳转到HTTPS。另外,当遇到证书或者链接不安全的时候,则首先警告用户,并且不再让用户选择是否继续进行不安全的通信。
7 、本地存储数据泄露
随着前后端分离,尤其是后端服务无状态化架构风格的兴起,随着SPA应用大量出现,存储在前端数据量也在逐渐增多。
前端应用是完全暴露在用户以及攻击者面前的,在前端存储任何敏感、机密的数据,都会面临泄露的风险,就算是在前端通过JS脚本对数据进行加密基本也无济于事。
尽管有浏览器的同源策略限制,但是如果前端应用有XSS漏洞,那么本地存储的所有数据就都可能被攻击者的JS脚本读取到。如果用户在公用电脑上使用了这个前端应用,那么当用户离开后,这些数据是否也被彻底清除了呢?前端对数据加密后再存储看上去是个防御办法,但其实仅仅提高了一点攻击门槛而已,因为加密所用到的密钥同样存储在前端,有耐心的攻击者依然可以攻破加密这道关卡。
所以,在前端存储敏感、机密信息始终都是一件危险的事情,推荐的做法是尽可能不在前端存这些数据。
如何防御?
8 、CDN劫持/污染
以出于性能考虑,前端应用通常会把一些静态资源存放到CDN上,再提高前端应用的访问速度的同时也隐含了一个新的安全风险。
如果攻击者劫持了CDN,或者对CDN中的资源进行了污染,那么我们的前端应用拿到的就是有问题的JS脚本或者Stylesheet文件。这种攻击方式造成的效果和XSS跨站脚本攻击相似。
如何防御?
防御这种攻击的办法是使用浏览器提供的SRI(Subresource Integrity)功能。
每个资源文件都有一个SRI值。由两部分组成,减号(-)左侧是生成SRI值用到的哈希算法名,右侧是经过Base64编码后的该资源文件的Hash值。<script src="“https://example.js”" integrity="“sha384-eivAQsRgJIi2KsTdSnfoEGIRTo25NCAqjNJNZalV63WKX3Y51adIzLT4So1pk5tX”"/>
浏览器在处理这个script元素的时候,就会检查对应的JS脚本文件的完整性,看其是否和script元素中integrity属性指定的SRI值一致,如果不匹配,浏览器则会中止对这个JS脚本的处理。