前端安全

安全是一个很大的话题,各种安全问题的类型也是种类繁多,如果我们把安全问题按照所发生的地方进行分类到的话,那么所有发生在后端服务器、应服务当中的安全问题就是‘后端安全问题’,所有发生在浏览器、单页面应用、web页面当中的安全问题则算是‘前端安全问题’。比如说:SQL注入漏洞发生在后端应用中,是后端安全问题,跨站脚本攻击(xss)则是前端安全问题,因为他发生在用户的浏览器里。

除了从安全问题发生的地方来分类以外,也可以从另一个纬度来判断这是否是个前端安全问题,针对某个安全问题,团队哪个角色更适合来修复它,是后端开发还是前端开发。

总的来说,当我们下面讨论前端安全问题的时候,我们说的是发生浏览器,前端应用当中,或者通常由前端开发工程师来对其进行修复的问题。

九大前端安全问题

1、SCRF

2、老生常谈的XSS

3、警惕iFrame带来的风险

4、别被点击劫持了

5、恶意文件上传这个常见安全问题

6、防火防盗防猪队友:不安全的第三方依赖包

7、用了HTTPS也可能掉坑里

8、本地存储数据泄露

9、缺失静态资源完整性校验

(1)csrf

未登录

        从基础开始,第一步是登陆鉴权,如果一个需要用户鉴权的应用系统没有登陆过滤的,那简直无法想象,方案基本都是用户输入用户名,密码或者第三方openID授权后再session里保存用户此次登陆的凭证来确保每次请求的合法性。

由于session有时间限制,所以若用户一段时间没有与服务器监护则会过期重登,当然我们也可以通过把登陆凭证存在cookie里来自由控制用户登陆时间,这个是最基本的鉴权我们就不深入细节。

登陆了,但是被csrf

虽然有了登陆验证后,我们可以档掉其他非登陆用户的骚扰,但是悲剧的是坏人还是可以欺骗我们善良的用户,借已登录用户的手搞破坏。即csrf(Cross-site-request forgery) 跨站强求伪造。

举个例子:

有一个黑客网站h.com,我们的网站a.com,但是被诱惑点击进入h.com(如收到qq消息或者邮件传递的h.com的链接),当用户访问这个链接的时候,h.com上自动发送一个请求到a.com,由于用户已经登录了a.com浏览器根据同源策略(两个页面地址中的协议、域名、端口号一致,则表示同源),会在该请求上自动附带上cookie,而前边我们提到了鉴权是通过cookie里的某个值key值凭证的,所以如若没有判断该请求的来源和发行,我们则通过该伪造的请求,执行了想一个的操作,比如这个请求是让该用户发一篇日志,或者是发微博,或者严重的发起一笔转账。

img_c57129c6536783eed27020366a77445b.png

post请求会稍微麻烦一些,但是同样很好实现,可以伪造一个诱导用户点击,也可以直接利用ajax发送post请求。

要防止此类伪造请求我们第一反应都是检查这个请求的来源,确实,在上述的情形下发来的请求报文里referer字段的网址不是我们的站点,而会是一个第三方的,如上假设的h,com。但是很多情况下,referer并不完全靠谱,比如如果众多的二级域名之间需要通信,那么referer就会设置的比较泛,如:*a.com。如果是历史原因一些referer为空的请求会漏过校验等,所以这种方式只是提高了破解成本,并不能完全杜绝。

现在业界比较通用的解决方案还是在每个请求上附带一个anti-CSRF toten.

将sessionID加盐再散射处理,然后一起发送给后端,服务器端拿到token后用相同的算法对sid运算后匹配,相同则为已登录拥护 发送请求,没有则不对等说明请求是伪造的。

token = MD5( sid * salt )

其实这个算法的精髓在于使用了cookie中的sid(用户登录后我们服务器种的cookie凭证),因为前端的代码对于用户而言都是没有秘密的,只要花点时间就可以推算出来我们的算法,但是由于攻击者无法登陆,又拿不到cookie的的sid(根据浏览器的同源策略,在h.com无法获取属于a.com的cookie),所以无法造出token.

至于MD5当然是因为我们不会傻的把登陆凭证sid放到url上给人直接拿了登陆,为什么要加盐,则是因为怕简单的一层MD4还是有可能被通过撞库的方式解出sid,当然加了盐也不意味着100%防住,只是大大提高了破解的成本而已。

(2)XSS

从上面我们知道防住 CSRF 最关键的是要守住 cookie,如果用户的 cookie 被人窃取了,那上面的防护就形同虚设了。而 XSS 就可以很轻易的获取用户的 cookie,

所以有句话叫Buy one XSS, get a CSRF for free。

用户输入的内容原封不动的通过服务器程序渲染在页面上 。

反射型

举个栗子

前端get一个请求:www.a.com/xss.php?name=userA

后台处理:

img_b3b09a323d2bee22053fcb471a448316.png

代码本意是根据queryString 的 name 来动态展示用户名,但由于未对 name 做编码校验,

img_1c7f0eb7e1fbca26095296485da8ca54.png

alert(document.cookie);

这时访问这个链接则会弹出我们的 cookie 内容,如果这时候再把 alert 改为一个发送函数,则可把 cookie 偷走。

前端DOM-Based XSS:

document.getElementById('intro-div').innerHTML = document.location.href.substring(document.location.href.indexOf("intro=")+6);

如上,直接将用户的输出到页面标签中。但是如果将链接中的内容设置为:

img_d06a38588841a3e6d7475266f4c253fb.png

alert(document.cookie)那我们的 cookie 又没了。

持久型XSS

也称为存储型 XSS,注入脚本跟 XSS 大同小异,只是脚本不是通过浏览器->服务器->浏览器这样的反射方式,而是多发生在富文本编辑器、日志、留言、配置系统等数据库保存用户输入内容的业务场景。即用户的注入脚本保存到了数据库里,其他用户只要一访问到都会中招。

前端get一个请求:

img_bef21505fbdcf9744a9cd001797bd5fd.png

后台处理:

img_abf934c3a60e1d25f6460562a619209d.png

前端请求的页面:

img_68d2fb5d2783ecb259eda579f8fb50de.png

解决XSS

    从上面我们可以看出各种攻击手段很重要的一点就是要获取 cookie,有了 cookie 就相当于获取了我们用户的身份信息,所以自然的我们要保护我们的 cookie。在 cookie 里有个 HttpOnly 属性可以让页面无法通过 JS 来读写 cookie。

img_d25e3a99dae51b96a838c5e16fa38e07.png

开启这个属性后 document 将无法获取 cookie。当然这个方法也不是万能的,我们的 cookie 还是会在 header 中,还是有其他手段去获取 header 中的 cookie,

不过使用后我们还是提高了攻击的成本。关键还是我们要不相信用户的一切输入,对编码输出在页面中会破坏原有代码(HTML、JavaScript甚至WML等)规则的特殊字符以及对某些标签的某些属性进行白名单检查。

XSS防护也做了,被用户SQL注入

举个例子:

img_09a714b7e5fb4a111008da4b7b1b9664.png

功能是查询userId为123的用户出来,这个请求到我们服务端最后sql语句是这样:

img_1c558e2e056e7fb65bb470085954df34.png

如果不做任何校验,如果用户输入如下:

img_fe30e24c5cb49342f9d884ef8ff2bffc.png

嘎嘎,整个表就没有了。

所以同样的,还是那个原则,我们不能相信用户的任何输入,如果一个sql语句里包含了用户输入的内容,那我们要对内容做sql安全相关的过滤检查。

同时,使用一些ORM工具,不使用拼凑字符串型的语句执行方式。

XSS是跨站脚本攻击(Cross-Site Scripting)的简称,它是个老油条了,在OWASP Web Application Top 10 排行榜中长期霸榜,从未掉出过前三名。XSS这类安全问题发生的本质性原因在于,浏览器错误的将攻击者提供的用户输入数据当做了JavaScript脚本给执行了。

XSS有几种不同的分类办法,例如按照恶意输入的脚本是否在应用中存储,XSS被划分为“存储型XSS”和“反射型XSS”,如果按照是否和服务器有交互,又可以划分为“Server Side XSS”和“DOM based XSS”。

无论怎么分类,XSS漏洞始终是威胁用户的一大安全隐患。攻击者可以利用XSS漏洞来窃取包括用户身份信息在内的各种敏感信息、修改Web页面以欺骗用户,甚至控制受害者浏览器,或者和其他漏洞结合起来形成蠕虫攻击,等等。总之,关于XSS漏洞的利用,只有想不到没有做不到。

防御

防御XSS最佳的做法就是对数据进行严格的输出编码,使得攻击者提供的数据不再被浏览器认为是脚本而被误执行。例如在进行HTML编码后变成了,而这段数据就会被浏览器认为只是一段普通的字符串,而不会被当做脚本执行了。

编码也不是件容易的事情,需要根据输出数据所在的上下文来进行相应的编码。例如刚才的例子,由于数据将被放置于HTML元素中,因此进行的是HTML编码,而如果数据将被放置于URL中,则需要进行URL编码,将其变为%3Cscript%3E。此外,还有JavaScript编码、CSS编码、HTML属性编码、JSON编码等等。好在现如今的前端开发框架基本上默认都提供了前端输出编码,这大大减轻了前端开发小伙伴们的工作负担。

其他的防御措施,例如设置CSP HTTP Header、输入验证、开启浏览器XSS防御等等都是可选项,原因在于这些措施都存在被绕过的可能,并不能完全保证能防御XSS攻击。不过它们和输出编码却可以共同协作实施纵深防御策略。

你可以查阅OWASP XSS Prevention Cheat Sheet,里面有关于XSS及其防御措施的详细说明。

(3)警惕iFrame带来的风险

有些时候我们的前端页面需要用到第三方提供的页面组件,通常会以iframe的方式引入进来。典型的例子是使用iframe在页面上添加第三方提供的广告、天气预报、社交分享插件等等。

iframe在给我们的页面带来更多丰富的内容和能力的同时,也带来了不少的安全隐患。因为iframe中的内容是由第三方来提供的,默认情况下他们不受我们的控制,他们可以在iframe中运行JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会破坏前端用户体验。

如果说iframe只是有可能会给用户体验带来影响,看似风险不大,那么如果iframe中的域名因为过期而被恶意攻击者抢注,或者第三方被黑客攻破,iframe中的内容别替换掉了,从而利用用户浏览器中的安全漏洞下载安装木马、恶意勒索软件等等,这可就闹大了。

防御

还好HTML5中iframe有了一个叫做sandbox的安全属性,通过它可以对iframe的行为进行各种限制,充分实现“最小权限“原则。使用sandbox的最简单的方式就是只在iframe元素中添加上这个关键词就好,就像下面这样:

img_e1dbe51910cfe13382098edc377c57cd.png

sandbox还忠实的实现了”Secure By Default“原则,也即是说,如果你只是添加上这个属性而保持属性值为空,那么浏览器将会对iframe实施史上最严厉的调控限制,基本上来讲就是除了允许显示静态资源以外,其他什么都做不了。比如不准提交表单、不准弹窗、不准执行脚本等等,连Origin都会被强制重新分配一个唯一的值,换句话讲就是iframe中的页面访问它自己的服务器都会被算作跨域请求。

另外,sandbox也提供了丰富的配置参数,我们可以进行较为细粒度的控制。一些典型的参数如下:

allow-forms: 允许iframe中提交form表单

allow-popups: 允许iframe中弹出新的窗口或者标签页(例如,window.open(),showModalDialog(),target=”_blank”等等)

allow-scripts: 允许iframe中执行JavaScript

allow-same-origin: 允许iframe中的网页开启同源策略

更多详细的资料,可以参考iframe中关于sandbox的介绍:https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe

(4)被点击劫持了

有句话叫做防不胜防,我们在通过iframe使用别人家提供的内容的时候,我们自己的页面也可能正在被不法分子放到他们精心构造的iframe或者frame当中,进行点击劫持攻击。

这是一种欺骗性比较强,同时也需要用户高度参与才能完成的一种攻击。通常的攻击步骤是这样的:

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

(5)恶意文件上传这个常见安全问题

想象一下这样一个攻击场景:某网站允许用户在评论里上传图片,攻击者在上传图片的时候,看似提交的是个图片文件,实则是个含有JavaScript的脚本文件。该文件逃过了文件类型校验(这涉及到了恶意文件上传这个常见安全问题,但是由于和前端相关度不高因此暂不详细介绍),在服务器里存储了下来。接下来,受害者在访问这段评论的时候,浏览器会去请求这个伪装成图片的JavaScript脚本,而此时如果浏览器错误的推断了这个响应的内容类型(MIME types),那么就会把这个图片文件当做JavaScript脚本执行,于是攻击也就成功了。

问题的关键就在于,后端服务器在返回的响应中设置的Content-TypeHeader仅仅只是给浏览器提供当前响应内容类型的建议,而浏览器有可能会自作主张的根据响应中的实际内容去推断内容的类型。

在上面的例子中,后端通过Content-TypeHeader建议浏览器按照图片来渲染这次的HTTP响应,但是浏览器发现响应中其实是JavaScript,于是就擅自做主把这段响应当做JS脚本来解释执行,安全问题也就产生了。

如何防御

浏览器根据响应内容来推断其类型,本来这是个很”智能“的功能,是浏览器强大的容错能力的体现,但是却会带来安全风险。要避免出现这样的安全问题,办法就是通过设置X-Content-Type-Options这个HTTP Header明确禁止浏览器去推断响应类型。

同样是上面的攻击场景,后端服务器返回的Content-Type建议浏览器按照图片进行内容渲染,浏览器发现有X-Content-Type-OptionsHTTP Header的存在,并且其参数值是nosniff,因此不会再去推断内容类型,而是强制按照图片进行渲染,那么因为实际上这是一段JS脚本而非真实的图片,因此这段脚本就不会被浏览器执行。

更多关于X-Content-Type-Options的细节请参考这里

(6)不安全的第三方依赖包

现如今进行应用开发,就好比如站在巨人的肩膀上写代码,据统计一个应用有将近80%的代码其实是来自于第三方组件、依赖的类库等,而应用自身的代码其实只占了20%左右。无论是后端服务器应用还是前端应用开发,绝大多数时候我们都是在借助开发框架和各种类库进行快速开发。

这样做的好处显而易见,但是与此同时安全风险也在不断累积——应用使用了如此多的第三方代码,不论应用自己的代码的安全性有多高,如果这些来自第三方的代码有安全漏洞,那么对应用整体的安全性依然会造成严峻的挑战。

举个例子,jQuery就存在多个已知安全漏洞,例如jQuery issue 2432,使得应用存在被XSS攻击的可能。而Node.js也有一些已知的安全漏洞,比如CVE-2017-11499,可能导致前端应用受到DoS攻击。另外,对于前端应用而言,除开使用到的前端开发框架之外,通常都还会依赖不少Node组件包,它们可能也有安全漏洞。

手动检查这些第三方代码有没有安全问题是个苦差事,主要是因为应用依赖的这些组件数量众多,手工检查太耗时,好在有自动化的工具可以使用,比如NSP(Node Security Platform),Snyk等等。

(7)用了HTTPS也可能掉坑里

为了保护信息在传输过程中不被泄露,保证传输安全,使用TLS或者通俗的讲使用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。另外,当遇到证书或者链接不安全的时候,则首先警告用户,并且不再让用户选择是否继续进行不安全的通信。

(8)本地存储数据泄露

以前,对于一个Web应用而言,在前端通过Cookie存储少量用户信息就足够支撑应用的正常运行了。然而随着前后端分离,尤其是后端服务无状态化架构风格的兴起,伴随着SPA应用的大量出现,存储在前端也就是用户浏览器中的数据量也在逐渐增多。

前端应用是完全暴露在用户以及攻击者面前的,在前端存储任何敏感、机密的数据,都会面临泄露的风险,就算是在前端通过JS脚本对数据进行加密基本也无济于事。

举个例子来说明,假设你的前端应用想要支持离线模式,使得用户在离线情况下依然可以使用你的应用,而这就意味着你需要在本地存储用户相关的一些数据,比如说像电子邮箱地址、手机号、家庭住址等PII(Personal Identifiable Information)信息,或许还有历史账单、消费记录等数据。

尽管有浏览器的同源策略限制,但是如果前端应用有XSS漏洞,那么本地存储的所有数据就都可能被攻击者的JS脚本读取到。如果用户在公用电脑上使用了这个前端应用,那么当用户离开后,这些数据是否也被彻底清除了呢?前端对数据加密后再存储看上去是个防御办法,但其实仅仅只是提高了一点攻击门槛而已,因为加密所用到的密钥同样存储在前端,有耐心的攻击者依然可以攻破加密这道关卡。

所以,在前端存储敏感、机密信息始终都是一件危险的事情,推荐的做法是尽可能不在前端存这些数据。

(9)缺乏静态资源完整性校验

出于性能考虑,前端应用通常会把一些静态资源存放到CDN(Content Delivery Networks)上面,例如Javascript脚本和Stylesheet文件。这么做可以显著提高前端应用的访问速度,但与此同时却也隐含了一个新的安全风险。

如果攻击者劫持了CDN,或者对CDN中的资源进行了污染,那么我们的前端应用拿到的就是有问题的JS脚本或者Stylesheet文件,使得攻击者可以肆意篡改我们的前端页面,对用户实施攻击。这种攻击方式造成的效果和XSS跨站脚本攻击有些相似,不过不同点在于攻击者是从CDN开始实施的攻击,而传统的XSS攻击则是从有用户输入的地方开始下手的。

防御这种攻击的办法是使用浏览器提供的SRI(Subresource Integrity)功能。顾名思义,这里的Subresource指的就是HTML页面中通过和元素所指定的资源文件。

每个资源文件都可以有一个SRI值,就像下面这样。它由两部分组成,减号(-)左侧是生成SRI值用到的哈希算法名,右侧是经过Base64编码后的该资源文件的Hash值。

浏览器在处理这个script元素的时候,就会检查对应的JS脚本文件的完整性,看其是否和script元素中integrity属性指定的SRI值一致,如果不匹配,浏览器则会中止对这个JS脚本的处理。

总结

    总的来说,前端最重要的就是一个sessionId这个代表用户身份的凭证,保护好这个凭证,同时利用同源策略以及自己加密token来识别用户,最后以最恶意的眼光对待用户的输入,不要相信用户的输入。这样就能屏蔽绝大部分常见的安全问题了。

希望能通过对这些问题的介绍,引起前端开发小伙伴的注意,尽可能提前绕过这些安全问题的坑。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值