很多时候我们在做开发的时候并不会主要考虑安全问题,但是现在从事的行业不同,前端安全也会相应的受到更多的重视,下面通过几个个方面具体了解和分析一下前端的安全相关问题:
- 熟悉的CSRF(跨站请求伪造)
- 老生常谈的XSS(跨站脚本攻击)
- iframe带来的风险
- 打开新页面也可能有风险
1、CSRF(跨站请求伪造)
攻击者盗用了用户身份,拿到了登录信息,以用户的名义进行恶意请求。危险is watching you:以用户的名义发送邮件、发信息、盗取账号、购买商品、虚拟货币转账等。总结起来就是:个人隐私暴露及财产安全问题。
CSRF 攻击思想:
- 浏览并登录信任网站(举例:淘宝)
- 登录成功后在浏览器产生信息存储(举例:cookie)
- 用户在没有登出淘宝的情况下,访问危险网站
- 危险网站中存在恶意代码,代码为发送一个恶意请求(举例:购买商品/余额转账)
- 携带刚刚在浏览器产生的信息进行恶意请求
- 淘宝验证请求为合法请求(区分不出是否是该用户发送)
- 达到了恶意目标
防御措施(推荐添加token / HTTP头自定义属性)
- 涉及到数据修改操作严格使用 post 请求而不是 get 请求
- HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。
- HTTP 协议中使用 Referer 属性来确定请求来源进行过滤(禁止外域)
- 请求地址添加 token ,使黑客无法伪造用户请求
- HTTP 头自定义属性验证(类似上一条)
- 显示验证方式:添加验证码等
2、XSS(跨站脚本攻击)
XSS是跨站脚本攻击(Cross-Site Scripting)的简称,XSS这类安全问题发生的本质原因在于,浏览器错误的将攻击者提供的用户输入数据当做JavaScript脚本给执行了。
XSS分为:存储型 、反射型 、DOM型XSS
存储型XSS:存储型XSS,持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,插入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie
反射型XSS:非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。反射型XSS大多数是用来盗取用户的Cookie信息。
DOM型XSS:不经过后端,DOM-XSS漏洞是基于文档对象模型(Document Objeet Model,DOM)的一种漏洞,DOM-XSS是通过url传入参数去控制触发的,其实也属于反射型XSS。
例子1:
公司需要一个搜索页面,根据 URL 参数决定关键词的内容。
代码如下:
<input type="text" value="<%= getParameter("keyword") %>"> <button>搜索</button> <div> 您搜索的关键词是:<%= getParameter("keyword") %> </div>
上线后不久,收到安全组发来的一个神秘链接:
http://xxx/search?keyword="><script>alert('XSS');</script>
当浏览器请求 http://xxx/search?keyword="><script>alert('XSS');</script> 时,服务端会解析出请求参数 keyword,得到 "><script>alert('XSS');</script>,拼接到 HTML 中返回给浏览器。形成了如下的 HTML:
<input type="text" value=""><script>alert('XSS');</script>"> <button>搜索</button> <div> 您搜索的关键词是:"><script>alert('XSS');</script> </div> // 卒~~
例子2:
公司需要一个根据 URL 参数决定决定跳转地址。代码如下:
<a :href="getParameter("redirect_to")">跳转...</a>
继续收到安全组神秘链接:
http://xxx/?redirect_to=javascript:alert('XSS')
解析后:
<a href="javascript:alert('XSS';)">跳转...</a> // 卒~~
可能会被利用的地方
HTML 中内嵌的文本中:<div>{'</div><script>alert('XSS');</script><div>'}</div>
元素的属性(href、src 等):<a href={javascript:alert('XSS')}></a>
元素事件(onload、onerror、onclick 等):<img onl oad={javascript:alert('XSS')}></img>
URL的query:http://abc.com/xxx?search=<script>alert('XSS');</script>
style属性值:background-image:url("javascript:...");
如何防御:
编码:
把可运行的恶意代码编码成不可运行的单纯的数据
注:由于用户提交的数据可能会被使用到不同的场景,不同的场景不同的处理方式是不一样;甚至,同一个数据,可能会在不同的场景下使用。所以,在拿到数据之后往往不会立刻做处理。
相反的,在把数据render到HTML的时候,对数据进行处理,是我们较好处理时机。
根据显示场景的不同可以有不同的编码方式例如:
在HTML中显示: Node.textContent = userInput 在URL中使用:Window.encodeURIComponent(userInput) 作为属性使用:Element.style.XXX = userInput/Element.setAttribute(attribute, userInput) JSON编码等
HTML编码及转换方法:
1 var HtmlUtil = { 2 /*1.用浏览器内部转换器实现html编码(转义)*/ 3 htmlEncode:function (html){ 4 //1.首先动态创建一个容器标签元素,如DIV 5 var temp = document.createElement ("div"); 6 //2.然后将要转换的字符串设置为这个元素的innerText或者textContent 7 (temp.textContent != undefined ) ? (temp.textContent = html) : (temp.innerText = html); 8 //3.最后返回这个元素的innerHTML,即得到经过HTML编码转换的字符串了 9 var output = temp.innerHTML; 10 temp = null; 11 return output; 12 }, 13 /*2.用浏览器内部转换器实现html解码(反转义)*/ 14 htmlDecode:function (text){ 15 //1.首先动态创建一个容器标签元素,如DIV 16 var temp = document.createElement("div"); 17 //2.然后将要转换的字符串设置为这个元素的innerHTML(ie,火狐,google都支持) 18 temp.innerHTML = text; 19 //3.最后返回这个元素的innerText或者textContent,即得到经过HTML解码的字符串了。 20 var output = temp.innerText || temp.textContent; 21 temp = null; 22 return output; 23 }, 24 /*3.用正则表达式实现html编码(转义)*/ 25 htmlEncodeByRegExp:function (str){ 26 var temp = ""; 27 if(str.length == 0) return ""; 28 temp = str.replace(/&/g,"&"); 29 temp = temp.replace(/</g,"<"); 30 temp = temp.replace(/>/g,">"); 31 temp = temp.replace(/\s/g," "); 32 temp = temp.replace(/\'/g,"'"); 33 temp = temp.replace(/\"/g,"""); 34 return temp; 35 }, 36 /*4.用正则表达式实现html解码(反转义)*/ 37 htmlDecodeByRegExp:function (str){ 38 var temp = ""; 39 if(str.length == 0) return ""; 40 temp = str.replace(/&/g,"&"); 41 temp = temp.replace(/</g,"<"); 42 temp = temp.replace(/>/g,">"); 43 temp = temp.replace(/ /g," "); 44 temp = temp.replace(/'/g,"\'"); 45 temp = temp.replace(/"/g,"\""); 46 return temp; 47 }, 48 /*5.用正则表达式实现html编码(转义)(另一种写法)*/ 49 html2Escape:function(sHtml) { 50 return sHtml.replace(/[<>&"]/g,function(c){return {'<':'<','>':'>','&':'&','"':'"'}[c];}); 51 }, 52 /*6.用正则表达式实现html解码(反转义)(另一种写法)*/ 53 escape2Html:function (str) { 54 var arrEntities={'lt':'<','gt':'>','nbsp':' ','amp':'&','quot':'"'}; 55 return str.replace(/&(lt|gt|nbsp|amp|quot);/ig,function(all,t){return arrEntities[t];}); 56 } 57 };
验证:
对我们接收到的数据进行过滤,过滤出恶意代码,得到合法代码。
- 黑名单
黑名单,即列举了恶意代码类型的名单。如果数据在黑名单里面,则被判定为非法代码;否则为合法代码。
缺陷:因为现实是复杂的,攻击者的手法是千奇百怪的,要创建一个包含所有可能的恶意代码的黑名单,往往是困难,甚至是不能的,很难做到全面覆盖,而且时效性短。
- 白名单
白名单是包含了合法代码的名单。通常来说,合法的情况是可以穷尽的,比如一个输入用户名的input,我就只接受字母,数字,下划线这三种类型的字符。
优势:可覆盖所有的合法场景,时效性长
验证结果处理 - 舍弃
当我们检测出用户的输入里面包含恶意代码的时候,直接舍弃掉这一条数据,这条数据不会出现在网站的任何地方,即叫做舍弃。
例如,有一个需要用户输入信用卡号码的input,用户不仅输入了数字,还输入了连接线(用户出于习惯)。我们直接舍弃这条数据,把这条数据判定为不合法。当然,相应地,你会给用户返回一个错误提醒。
验证结果处理 - 消毒
当我们检测出用户的输入里面包含恶意代码的时候,经过一定的处理方式,去掉恶意的部分,留下合法的代码,即叫做消毒。
还是用户输入信用卡号码的例子。对比舍弃的做法,消毒的做法是允许用户输入连接线,只是我们拿到数据之后,把连接线给去掉,最后得到只包含数字的信用卡号码
xss小游戏:
3、iframe使用
a. 如何让自己的网站不被其他网站的 iframe 引用?(点击劫持--clickjacking)
利用透明 iframe 覆盖原网页诱导用户进行某些操作达成目的,进行点击劫持攻击。通常的攻击步骤是这样的:
- 攻击者精心构造一个诱导用户点击的内容,比如Web页面小游戏
- 将我们的页面放入到iframe当中
- 利用z-index等CSS样式将这个iframe叠加到小游戏的垂直方向的正上方
- 把iframe设置为100%透明度
- 受害者访问到这个页面后,肉眼看到的是一个小游戏,如果受到诱导进行了点击的话,实际上点击到的却是iframe中的我们的页面
- 点击劫持的危害在于,攻击利用了受害者的用户身份,在其不知情的情况下进行一些操作。如果只是迫使用户关注某个微博账号的话,看上去仿佛还可以承受,但是如果是删除某个重要文件记录,或者窃取敏感信息,那么造成的危害可就难以承受了。
如何防御
- 在HTTP header中加入 X-FRAME-OPTIONS 属性:该响应头属性是用来给浏览器指示允许一个页面可否在<frame>,<iframe>,<embed>或者<object>中展现的标记。站点可以通过确保网站没有被嵌入到别人的站点里面。
DENY:不能被所有网站嵌套或加载;
SAMEORIGIN:只能被同域网站嵌套或加载;
ALLOW-FROM URL:可以被指定网站嵌套或加载。
- 判断当前网页是否被 iframe 嵌套
// 检测当前网站是否被第三方iframe引用 // 若相等证明没有被第三方引用,若不等证明被第三方引用。当发现被引用时强制跳转百度。 if(top.location != self.location){ top.location.href = self.location }
b. 如何防御被使用的 iframe 对当前网站某些操作
有些时候我们的前端页面需要用到第三方提供的页面组件,通常会以iframe的方式引入。例如使用iframe在页面上添加第三方提供的广告、天气预报、社交分享插件等等。
iframe在给我们的页面带来更多丰富的内容和能力的同时,也带来了不少的安全隐患。因为iframe中的内容是由第三方来提供的,默认情况下他们不受我们的控制,他们可以在iframe中运行JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会破坏前端用户体验。
如何防御
在HTML5中,iframe有了一个叫做sandbox的安全属性,通过它可以对iframe的行为进行各种限制,充分实现“最小权限“原则。使用sandbox的最简单的方式就是只在iframe元素中添加上这个关键词就好,就像下面这样:
<iframe sandbox src="..."> ... </iframe>
当sandbox保持属性值为空,那么浏览器将会对iframe实施史上最严厉的调控限制,基本上来讲就是除了允许显示静态资源以外,其他什么都做不了。比如不准提交表单、不准弹窗、不准执行脚本等等,连Origin都会被强制重新分配一个唯一的值,换句话讲就是iframe中的页面访问它自己的服务器都会被算作跨域请求。
另外,sandbox也提供了丰富的配置参数,我们可以进行较为细粒度的控制。一些典型的参数如下:
allow-forms:允许iframe中提交form表单
allow-popups:允许iframe中弹出新的窗口或者标签页(例如,window.open(),showModalDialog(),target=”_blank”等等)
allow-scripts:允许iframe中执行JavaScript
allow-same-origin:允许iframe中的网页开启同源策略
4、打开新页面也可能有风险--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 页面存在恶意代码如下: * window.opener.location.replace('https://www.baidu.com') 【此代码仅针对打开新标签有效】 * 此时,用户正在浏览新标签页,但是原来网站的标签页已经被导航到了百度页面。 * 恶意网站可以伪造一个足以欺骗用户的页面,使得进行恶意破坏。 * 即使在跨域状态下 opener 仍可以调用 location.replace 方法。 */
如何防御
a、<a target="_blank" href="">
<a target="_blank" href="" rel="noopener noreferrer nofollow">a标签跳转url</a> <!-- 通过 rel 属性进行控制: noopener:会将 window.opener 置空,从而源标签页不会进行跳转(存在浏览器兼容问题) noreferrer:兼容老浏览器/火狐。禁用HTTP头部Referer属性(后端方式)。 nofollow:SEO权重优化,详情见 https://blog.csdn.net/qq_33981438/article/details/80909881 -->
b、window.open()
<button onclick='openurl("http://www.baidu.com")'>click跳转</button> function openurl(url) { var newTab = window.open(); newTab.opener = null; newTab.location = url; }
引用:
https://zhuanlan.zhihu.com/p/83865185
https://www.jianshu.com/p/734bcceb4f36
https://segmentfault.com/a/1190000016551188
https://insights.thoughtworks.cn/eight-security-problems-in-front-end/