✔ 跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境
✔ 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了
之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
如下图所示:
这三个源分别由于域名、协议和端口号不一致,导致会受到同源策略的限制。
1. 不能向工作在不同源的的服务请求数据(client to server)
这里有个问题之前也困扰了我很久,就是为什么home.com加载的cdn.home.com/index.js可以向home.com发请求而不会跨域呢?其实home.com加载的JS是工作在home.com的,它的源不是提供JS的cdn,所以这个时候是没有跨域的问题的,并且script标签能够加载非同源的资源,不受同源策略的影响。
假设用户登陆了bank.com,同时打开了evil.com,如果没有任何限制,evil.com可以向bank.com请求到任何信息,进而就可以在evil.com向bank.com发转账请求等。
如果这样,为什么不直接限制写,只限制读?
因为如果连请求都发不出去了,那就不能做跨域资源共享了,无法读取返回结果,evil.com就无法继续下一步的操作,如获取转账请求的一些必要的验证信息。
如果不限制的话,那么很容易就可以伪装其它的网站,如套一个iframe或者通过window.open的方法,从而得到用户的操作和输入,如账户、密码。
另外,添加这个http头可以限制别人把你的网站套成它的iframe:
X-Frame-Options: SAMEORIGIN
同源策略提供了安全的同时也造成了不方便,因为有时候我们需要跨域请求,如获取第三方提供的服务信息,由于第三方的源和本网站的源不一样,所以这个时候就受到跨域的限制。
跨域最常用的方法,应当属CORS,如下图所示:
只要浏览器检测到响应头带上了CORS,并且允许的源包括了本网站,那么就不会拦截请求响应。
CORS把请求分为两种,一种是简单请求,另一种是需要触发预检请求,这两者是相对的,怎样才算“不简单”?只要属于下面的其中一种就不是简单请求:
这个时候就认为需要先发个预检请求,预检请求使用OPTIONS方式去检查当前请求是否安全,如下图所示:
代码里面只发了一个请求,但在控制台看到了两个请求,第一个是OPTIONS,服务端返回:
返回头里面包含了允许的请求头、请求方式、源,以及预检请求的有效期,上图是设置了20天,在这个有效期内就不用再发一个options的请求,实际上浏览器有一个最长时间,如Chrome是5分钟。如果在预检请求检测到当前请求不符合服务端设定的要求,则不会发出去了直接抛异常,这个时候就不用去发“复杂”的请求了。
如本源不在允许的源范围内,则会抛异常,无法获取返回结果:
为了支持CORS,nginx可以这么配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | location / { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; } |
第二种常用的跨域的方法是JSONP,JSONP是利用了script标签能够跨域,如下代码所示:
1 2 3 4 5 | function updateList (data) { console.log(data); } $body.append(‘<script src=“http://otherdomain.com/request?callback=updateList"></script>'); |
代码先定义一个全局函数,然后把这个函数名通过callback参数添加到script标签的src,script的src就是需要跨域的请求,然后这个请求返回可执行的JS文本:
1 2 3 4 | // script响应返回的js内容为 updateList([{ name: 'hello' }]); |
由于它是一个js,并且已经定义了upldateList函数,所以能正常执行,并且跨域的数据通过传参得到。这就是JSONP的原理。
所以由于script/iframe/img等标签的请求默认是能带上cookie(cookie里面带上了登陆验证的票token),用这些标签发请求是能够绕过同源策略的,因此就可以利用这些标签做跨站请求伪造(CSRF),如下面代码所示:
1 2 3 4 5 | // 转账请求 <iframe src="http://Abank.com/app/transferFunds?amount=1500&destinationAccount=..."></iframe> // 配置路由器添加代理 <img src="http://192.168.1.1/admin/config/outsideInterface?nexthop=123.45.67.89" style="display:none"> |
如果相应的网站支持GET请求,或者没有做进一步的防护措施,那么如果用户在另外一个页面登陆过了,再打开一个“有毒”的网站就中招了。
而动态ajax请求默认是不带cookie的,如果你要带cookie,可以设置ajax的一个属性withCredentials,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 原生请求 let xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.open("GET", "http://otherdomain.com/list"); xhr.send(); // jquery请求 $.ajax({ url: "http://otherdomain.com/list", xhrFields: { withCredentials: true } }); |
这个时候就和img/script标签一样,能带上cookie,并且还支持除GET之外的其它方式。所以这种方式也是能实现CSRF的,如下图所示:
所以如果转账请求只是不支持GET,没做其它的防护措施,仍然有CSRF攻击的风险。那怎么办呢?
方法一是每次请求都要在参数里面显示地带上token即登陆的票,虽然跨域请求能带上cookie,但是通过document.cookie仍然是获取不到其它源的cookie的,所以攻击者无法在代码里面拿到cookie里面的token,所以就没办法了。方法一的缺点是会暴露token,所以需要带token的最好不能是GET,因为GET会把参数拼在url里面,用户可能会无意把链接发给别人,但不知道这个链接带上了自己的登陆信息。
方法二是每次转账请求前都先请求一个随机串,这个串只能用一次转账或者支付请求,用完就废弃,只有这个串对得上才能请求成功,攻击者是无法拿到这个串的,因为如果跨域请求带cookie,浏览器要求Access-Control-Allow-Origin不能为通配符,只能为指定的源,如:
Access-Control-Allow-Origin: http://renren.com
由于攻击者所在的域名不在这个源里面,所以它是无法得到请求结果,所以请求不到随机串。因此这种方式也是可以避免CSRF攻击。
假设Allow-Origin为*,ajax设置withCredentials为true时,浏览器会抛异常,无法得到返回结果:
另外服务还需要指定Allow-Credentials的头部,如下代码所示:
1 2 | add_header "Access-Control-Allow-Origin" "http://fedren.com"; add_header "Access-Control-Allow-Credentials" "true"; |
关于cookie还有两个地方值得注意,如下图所示:
讨论完了client to server,我们再讨论client to client,即如何和一个frame通信,包括iframe或者使用window.open打开的页面。
iframe访问父页面可通过window.parent得到父窗口的window对象,通过open打开的可以用window.opener,进而得到父窗口的任何东西;父窗口如果和iframe同源的,那么可通过iframe.contentWindow得到iframe的window对象,如果和iframe不同源,则存在跨域的问题,这个时候可通过postMessage进行通讯。
使用postMessage的基本原理如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // main frame let iframeWin = document.querySelector("#my-iframe") .contentWindow; iframeWin.postMessage({age: 18}, "http://parent.com"); iframeWin.onmessage = function(event) { console.log("recv from iframe ", event.data); }; // iframe window.onmessage = function(event) { // test event.origin if (event.origin !== expectOrigin) { return; } console.log("recv from main frame ", event.data); }; window.parent.postMessage("hello, this is from iframe ", "http://child.com"); |
以页面嵌入youtobe视频为例,通过以下代码可以在页面嵌入一个youtobe视频,嵌入的是一个跨域的iframe,所以就涉及到如何和iframe进行通信的问题。如怎么知道iframe的状态,触发父页面定义的事件onPlayerReady,这个是iframe通知父页面,而父页面可以调player.stopVideo控制iframe的行为,这个是父页面通知iframe。
iframe通知父页面是通过window.parent.postMessage,同时监听message事件:
经检查上面代码4304行的c就是window.parent,这个embed-player.js是iframe的js,iframe的js通过postMessage发送了一个消息,如上图右边的窗口所示,然后在父窗口的widgetapi.js就收到了这个消息。
同样地,父窗口的JS也是使用postMessage向iframe发送消息,如下图所示:
当然postMessage不限于跨域,同域的也可以使用,只是同域的话可以通过window对象互相操作,你可能需要额外定义一些全局变量或者函数供其它frame使用,或者是定义一套事件机制(可以借助原生事件/jQuery/Vue事件等)。
这里有一个特例,就是子域如mail.hello.com要跨hello.com的时候,可以显式地设置子域的document.domain值为父域的domain:
1 | document.domain = "hello.com"; |
就不会有跨域的问题了。
补充一点,如果需要和同源的不同标签页进行通信可以使用localStorage,即一个页面设置localStorage,其它页面就会触发storage事件:
1 2 3 4 5 6 7 | window.addEventListener('storage', function(e) { e.key; e.oldValue; e.newValue; e.url; e.storageArea; }); |
这个我没试过,读者可以试一下。
再补充一点,websocket是不受同源策略限制的,没有跨域的问题。CSS的字体文件是会有跨域问题,指定CORS就能加载其它源的字体文件(通常是放在cdn上的)。而canvas动态加载的外部image,也是需要指定CORS头才能进行图片处理,否则只能画不能读取。
最后,跨域分为两种,一种是跨域请求,另一种访问跨域的页面,跨域请求可以通过CORS/JSONP等方法进行访问,跨域的页面主要通过postMesssage的方式。由于跨域请求不但能发出去还能带上cookie,所以要规避跨站请求伪造攻击的风险,特别是涉及到钱的那种请求。
原文来自:人人网FED博客
声明:所有来源为“聚合数据”的内容信息,未经本网许可,不得转载!如对内容有异议或投诉,请与我们联系。邮箱:marketing@think-land.com
涉农贷款地址识别,支持对私和对公两种方式。输入地址的行政区划越完整,识别准确度越高。
根据给定的手机号、姓名、身份证、人像图片核验是否一致
通过企业关键词查询企业涉讼详情,如裁判文书、开庭公告、执行公告、失信公告、案件流程等等。
IP反查域名是通过IP查询相关联的域名信息的功能,它提供IP地址历史上绑定过的域名信息。
结合权威身份认证的精准人脸风险查询服务,提升人脸应用及身份认证生态的安全性。人脸风险情报库,覆盖范围广、准确性高,数据权威可靠。