xml地图|网站地图|网站标签 [设为首页] [加入收藏]
30分钟ES6从陌生到熟悉,大前端安全问题
分类:web前端

1. 什么是跨域?

跨域一词从字面意思看,就是跨域名嘛,但实际上跨域的范围绝对不止那么狭隘。具体概念如下:只要协议、域名、端口有任何一个不同,都被当作是不同的域。之所以会产生跨域这个问题呢,其实也很容易想明白,要是随便引用外部文件,不同标签下的页面引用类似的彼此的文件,浏览器很容易懵逼的,安全也得不到保障了就。什么事,都是安全第一嘛。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。所以我们要通过一些方法使本域的js能够操作其他域的页面对象或者使其他域的js能操作本域的页面对象(iframe之间)。下面是具体的跨域情况详解:

JavaScript

URL 说明 是否允许通信 同一域名下 允许 同一域名下不同文件夹 允许 同一域名,不同端口 不允许 同一域名,不同协议 不允许 域名和域名对应ip 不允许 主域相同,子域不同 不允许(cookie这种情况下也不允许访问) 同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问) 不同域名 不允许

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
URL                           说明                    是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js         同一域名下               允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js  同一域名下不同文件夹      允许
http://www.a.com:8000/a.js
http://www.a.com/b.js         同一域名,不同端口        不允许
http://www.a.com/a.js
https://www.a.com/b.js        同一域名,不同协议        不允许
http://www.a.com/a.js
http://70.32.92.74/b.js       域名和域名对应ip         不允许
http://www.a.com/a.js
http://script.a.com/b.js      主域相同,子域不同        不允许(cookie这种情况下也不允许访问)
http://www.a.com/a.js
http://a.com/b.js             同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js         不同域名                  不允许

这里我们需要注意两点:

  1. 如果是协议和端口造成的跨域问题“前台”是无能为力的;
  2. 在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
    (“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。)

前言

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

这句话基本涵盖了为什么会产生ES6这次更新的原因——编写复杂的大型应用程序。

回顾近两年的前端开发,复杂度确实在快速增加,近期不论从系统复杂度还是到前端开发人员数量应该达到了一个饱和值,换个方式说,没有ES6我们的前端代码依旧可以写很多复杂的应用,而ES6的提出更好的帮我们解决了很多历史遗留问题,另一个角度ES6让JS更适合开发大型应用,而不用引用太多的库了。

本文,简单介绍几个ES6核心概念,个人感觉只要掌握以下新特性便能愉快的开始使用ES6做代码了!

这里的文章,请配合着阮老师这里的教程,一些细节阮老师那边讲的好得多:

除了阮老师的文章还参考:

PS:文中只是个人感悟,有误请在评论提出

小结

本文对前端安全问题进行了一次梳理,介绍了其中4个典型的前端安全问题,包括它们发生的原因以及防御办法。在下篇文章中,我们将介绍其他的几个前端安全问题,敬请期待。

文/ThoughtWorks马伟

1 赞 7 收藏 评论

7. 通过window.name跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

比如:我们在任意一个页面输入

window.name = "My window's name"; setTimeout(function(){ window.location.href = ""; },1000)

1
2
3
4
window.name = "My window's name";
setTimeout(function(){
    window.location.href = "http://damonare.cn/";
},1000)

进入damonare.cn页面后我们再检测再检测 window.name :

window.name; // My window's name

1
window.name; // My window's name

可以看到,如果在一个标签里面跳转网页的话,我们的 window.name 是不会改变的。
基于这个思想,我们可以在某个页面设置好 window.name 的值,然后跳转到另外一个页面。在这个页面中就可以获取到我们刚刚设置的 window.name 了。

由于安全原因,浏览器始终会保持 window.name 是string 类型。

同样这个方法也可以应用到和iframe的交互来:
比如:我的页面()中内嵌了一个iframe:

JavaScript

<iframe id="iframe" src=";

1
<iframe id="iframe" src="http://www.google.com/iframe.html"></iframe>

在 iframe.html 中设置好了 window.name 为我们要传递的字符串。
我们在 index.html 中写了下面的代码:

var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { data = iframe.contentWindow.name; };

1
2
3
4
5
6
var iframe = document.getElementById('iframe');
var data = '';
 
iframe.onload = function() {
    data = iframe.contentWindow.name;
};

Boom!报错!肯定的,因为两个页面不同源嘛,想要解决这个问题可以这样干:

var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { iframe.onload = function(){ data = iframe.contentWindow.name; } iframe.src = 'about:blank'; };

1
2
3
4
5
6
7
8
9
var iframe = document.getElementById('iframe');
var data = '';
 
iframe.onload = function() {
    iframe.onload = function(){
        data = iframe.contentWindow.name;
    }
    iframe.src = 'about:blank';
};

或者将里面的 about:blank 替换成某个同源页面(about:blank,javascript: 和 data: 中的内容,继承了载入他们的页面的源。)

这种方法与 document.domain 方法相比,放宽了域名后缀要相同的限制,可以从任意页面获取 string 类型的数据。

let、const和var

之前的js世界里,我们定义变量都是使用的var,别说还真挺好用的,虽有会有一些问题,但是对于熟悉js特性的小伙伴都能很好的解决,一般记住:变量提升会解决绝大多数问题。

就能解决很多问题,而且真实项目中,我们会会避免出现变量出现重名的情况所以有时候大家面试题中看到的场景在实际工作中很少发生,只要不刻意臆想、制造一些难以判断的场景,其实并不会出现多少BUG,不能因为想考察人家对语言特性的了解,就做一些容易容易忘掉的陷阱题。

无论如何,var 声明的变量受到了一定诟病,事实上在强类型语言看来也确实是设计BUG,但是完全废弃var的使用显然不是js该做的事情,这种情况下出现了let关键词。

let与var一致用以声明变量,并且一切用var的地方都可以使用let替换,新的标准也建议大家不要再使用var了,let具有更好的作用域规则,也许这个规则是边界更加清晰了:

{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1

1
2
3
4
5
6
7
{
  let a = 10;
  var b = 1;
}
 
a // ReferenceError: a is not defined.
b // 1

这里是一个经典的闭包问题:

var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10

1
2
3
4
5
6
7
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

因为i在全局范围有效,共享同一个作用域,所以i就只有10了,为了解决这个问题,我们之前会引入闭包,产生新的作用域空间(好像学名是变量对象,我给忘了),但是那里的i跟这里的i已经不是一个东西了,但如果将var改成let,上面的答案是符合预期的。可以简单理解为每一次“{}”,let定义的变量都会产生新的作用域空间,这里产生了循环,所以每一次都不一样,这里与闭包有点类似是开辟了不同的空间。

for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc

1
2
3
4
5
6
7
for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

这里因为内部重新声明了i,事实上产生了3个作用域,这里一共有4个作用域指向,let最大的作用就是js中块级作用域的存在,并且内部的变量不会被外部所访问,所以之前为了防止变量侮辱的立即执行函数,似乎变得不是那么必要了。

之前我们定义一个常量会采用全部大写的方式:

var NUM = 10;

1
var NUM = 10;

为了解决这个问题,ES6引入了const命令,让我们定义只读常量,这里不对细节做过多研究,直接后续项目实践吧,项目出真知。

警惕iframe带来的风险

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

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

图片 1

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

6. 通过CORS跨域

CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

平时的ajax请求可能是这样的:

<script type="text/javascript"> var xhr = new XMLHttpRequest(); xhr.open("POST", "/damonare",true); xhr.send(); </script>

1
2
3
4
5
<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "/damonare",true);
    xhr.send();
</script>

以上damonare部分是相对路径,如果我们要使用CORS,相关Ajax代码可能如下所示:

<script type="text/javascript"> var xhr = new XMLHttpRequest(); xhr.open("GET", "); xhr.send(); </script>

1
2
3
4
5
<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://segmentfault.com/u/trigkit4/",true);
    xhr.send();
</script>

代码与之前的区别就在于相对路径换成了其他域的绝对路径,也就是你要跨域访问的接口地址。

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。关于CORS更多了解可以看下阮一峰老师的这一篇文章:跨域资源共享 CORS 详解

  • CORS和JSONP对比
    • JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
    • 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
    • JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。

CORS与JSONP相比,无疑更为先进、方便和可靠。

30分钟ES6从陌生到熟悉

2018/07/30 · JavaScript · es6

原文出处: 叶小钗   

别被点击劫持了

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

图片 2

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

  1. 攻击者精心构造一个诱导用户点击的内容,比如Web页面小游戏
  2. 将我们的页面放入到iframe当中
  3. 利用z-index等CSS样式将这个iframe叠加到小游戏的垂直方向的正上方
  4. 把iframe设置为100%透明度
  5. 受害者访问到这个页面后,肉眼看到的是一个小游戏,如果受到诱导进行了点击的话,实际上点击到的却是iframe中的我们的页面

点击劫持的危害在于,攻击利用了受害者的用户身份,在其不知情的情况下进行一些操作。如果只是迫使用户关注某个微博账号的话,看上去仿佛还可以承受,但是如果是删除某个重要文件记录,或者窃取敏感信息,那么造成的危害可就难以承受了。

修改document.domain的方法只适用于不同子域的框架间的交互。

生成器Generators

ES6中提出了生成器Generators的概念,这是一种异步编程的解决方案,可以将其理解为一种状态机,封装了多个内部状态,这里来个demo:

function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

这个yield(产出)类似于之前的return,直观的理解就是一个函数可以返回多次了,或者说函数具有“顺序状态”,yield提供了暂停功能。这里我想写个代码来验证下期中的作用域状态:

function* test(){ let i = 0; setTimeout(function() { i++; }, 1000); yield i; yield i++; return i } let t = test(); console.log(t.next()); setTimeout(function() { console.log(t.next()); }, 2000); console.log(t.next()); //{value: 0, done: false} //{value: 0, done: false} //{value: 2, done: true}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function* test(){
    let i = 0;
    setTimeout(function() {
        i++;
    }, 1000);
    yield i;
    yield i++;
    return i
}
 
let t = test();
console.log(t.next());
 
setTimeout(function() {
    console.log(t.next());
}, 2000);
console.log(t.next());
 
//{value: 0, done: false}
//{value: 0, done: false}
//{value: 2, done: true}

之前我们写一个城市级联的代码,可能会有些令人蛋疼:

$.get('getCity', {id: 0}, function(province) { let pid = province[0]; //根据省id获取城市数据 $.get('getCity', {id: pid}, function(city) { let cityId = city[0]; //根据市级id获取县 $.get('getCity', {id: cityId}, function(city) { //do smt. }); }); });

1
2
3
4
5
6
7
8
9
10
11
$.get('getCity', {id: 0}, function(province) {
    let pid = province[0];
    //根据省id获取城市数据
    $.get('getCity', {id: pid}, function(city) {
        let cityId = city[0];
        //根据市级id获取县
        $.get('getCity', {id: cityId}, function(city) {
            //do smt.
        });
    });
});

这个代码大家应当比较熟悉了,用promise能从语法层面解决一些问题,这里简单介绍下promise。

如何防御

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

JavaScript

<iframe sandbox src="..."> ... </iframe>

1
<iframe sandbox src="..."> ... </iframe>

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的介绍。

关于作者:Damonare

图片 3

知乎专栏[前端进击者] 个人主页 · 我的文章 · 19 ·          

图片 4

参数新特性

ES6可以为参数提供默认属性

function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello

1
2
3
4
5
6
7
function log(x, y = 'World') {
  console.log(x, y);
}
 
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

至于不定参数撒的,我这里没有多过多的使用,等项目遇到再说吧,如果研究的太细碎,反而不适合我们开展工作。

如何防御

有多种防御措施都可以防止页面遭到点击劫持攻击,例如Frame Breaking方案。一个推荐的防御方案是,使用X-Frame-Options:DENY这个HTTP Header来明确的告知浏览器,不要把当前HTTP响应中的内容在HTML Frame中显示出来。

关于点击劫持更多的细节,可以查阅OWASP Clickjacking Defense Cheat Sheet。

本文由澳门新葡亰手机版发布于web前端,转载请注明出处:30分钟ES6从陌生到熟悉,大前端安全问题

上一篇:CSS镂空图片transition过渡初加载背景色块问题解决 下一篇:没有了
猜你喜欢
热门排行
精彩图文