同源策略和cors的信息量太多。网络上很少有全面总结的文章。学习同源策略最大的收获是,之前忽略了浏览器在安全中的重要性,总以为安全主要靠服务端。浏览器(协议规范)和服务端都非常重要。
host和domain
host
https://url.spec.whatwg.org/#concept-host
domain
https://url.spec.whatwg.org/#concept-domain
根据以上的定义,domain是“域名“,比如baidu.com,host可以是domain,或者ip地址,或者opaque host。
显然对于大部分开发情况,domain等于host,就是一个域名
opaque host
资料甚少,参考https://github.com/w3c/wpub/issues/321
比如file://这种host就是opaque host的一种,与常见的domain不一样。不太需要关注。
origin的定义
源的定义:
https://html.spec.whatwg.org/multipage/origin.html#concept-origin
基本上可以理解为
协议(http/https/...) + host(domain) + port
只能3者都一样,才叫同源。
x.baidu.com 与 y.baidu.com 就不是同源。
同源策略
同源策略是互联网安全的基石。一个浏览器tab打开了源A,另一个tab打开了源B,源A的tab不能访问到源B tab的数据,包括cookie数据和dom数据。浏览器提供了这样的安全保障。
但是HTML除了cookie和dom,还有:
- JavaScript
- img,vedio
- css
- 跨域请求(源A向源B请求数据)
- document.domain API可以修改当前页面的domain
违反了同源策略的访问,统称为跨域访问
详细的定义可以查阅:
https://code.google.com/archive/p/browsersec/wikis/Part2.wiki
https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
跨域访问
按照MDN的说法:
- 跨域写请求一般是被允许的(比如form提交)(这里的“被允许”只是浏览器不会主动阻止这次请求)
- 跨域embed一般是被允许的(同上)
- 跨域读请求一般是不允许的(浏览器和服务端都有安全措施)。于是有一些人鸡贼的想用embed来做跨域的读。
CORS
cross-origin resource sharing. 通过JS脚本来发起请求(AJAX或者fetch API, 与AJAX大同小异)
在互联网上,有的服务端愿意开放自己的数据,有的不愿意
CORS规范了不同情况下浏览器和服务端的行为。
详细说明:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
CORS与origin请求头
在CORS的说明中,“简单”请求不会触发CORS预检测机制。
要求“简单”请求的请求头只能有以下类型
但是浏览器发现一个请求是跨域请求时,一定会在请求中加入头部
origin
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin
服务端可以根据这个头部做出相应处理。没法用脚本阻止浏览器添加此头部。
CORS与预检测请求
不是“简单”请求的脚本跨域请求,都是预检测请求。浏览器会执行预检测机制。
预检测机制会先用option method请求服务器,看服务端是否支持当前origin的跨域请求.
不管是简单请求还是预检测请求,涉及到的http头部常见的有:
Access-Control-Allow-Origin 允许跨域访问的域
Access-Control-Max-Age 在多长的时间内,跨域访问不再需要预检测机制
Access-Control-Allow-Headers 允许跨域时含有这些请求header
不管是简单跨域请求还是预检测跨域请求,都会发送http请求到服务端。
收到服务端返回后,如果:
- 返回头没有任何跨域相关的header,视为不允许跨域
- 返回头中有跨域相关的header,且允许此次跨域,一切正常。
- 返回头中有跨域相关的header,且不予许此次跨域,浏览器报错
即使是被服务端的返回header拒绝跨域请求,通过fiddler抓包,可以看到整个http请求是已经完成的,数据body也都传输到了浏览器。
Chrome浏览器的“Provisional headers are shown”
如下图,一些请求在chrome调试器中看到请求头被标上了"provisional headers are shown", 直译就是“显示的是临时的请求头”
出现这个提示最常见的情况下数据有缓存,不过也有其他情况:
- 浏览器装了广告拦截插件,广告请求被拦截了
- 一个请求还没完成被用户取消了
- 服务端长时间没反应,超时了
还要一种情况与跨域有关。在跨域预处理中,会先发送options请求,但是如果服务端不能正确处理options请求,会导致请求超时,出现这个警告。
通过chrome自带插件可以查看chrome的log,看看发生了什么。
chrome://net-export/
对embed的疑问
同源策略中提到一个概念叫embed cross-origin。对于这些访问,浏览器就“友好”多了。
那么像上面说的,有人在下面的src中填入一个原本会被CORS阻挡住的连接,是不是就能跳过CORS的限制?
<img src="">
过了浏览器还有服务端。
一般服务端默认都有如下配置:
- 将png,gif,js, css这些可公开的数据, 允许任何人任何方式访问
- 其他后缀的访问,比如XXX.do, XXX.jsp,哪怕用embed的方式请求,也会被服务端拦截,拒绝访问。CSRF就是手段之一。
注意:
跨域相关的返回header,比如access-control-allow-origin,对embed请求无效。这也是JSONP技术出现的原因。
图片被盗链
如果img没有CORS的限制,是不是我可以去随意用img标签盗链别人的图片贴在我自己的网站上?
当然不。如果一个站点不想被别人盗链图片,他就会在服务端里设置:
对于script,png,gif等embed请求,统统检测请求者的referer(为什么不是检测origin?因为img不是脚本跨域请求,浏览器不会自动给这次请求加origin请求头)。
这些embed请求,浏览器都会自动加上referer请求头。
不过referer涉及到了用户的浏览记录隐私,使用下面的标签可以让浏览器不加referer请求头。
<meta name="referrer" content="no-referrer">
为了防止盗链,服务端的逻辑可以是:
先保证自己站点的embed请求都有referfer。服务端再检测所有请求的referer,如果没有referer或者referer不对,都拦截。
图片盗链还有另外一种思路:图片都使用随机名字,并且定期更改。
crossorigin属性
<img>, <script> 等标签有一个crossorigin属性
https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes
加上这个标签,把embed请求变成CORS请求。
谁会这么做呢?要知道CORS比embed有更多的约束。
当然是合法的访问者愿意这么做啦。
服务端不提供embed方式的图片数据,访问者统统要用CORS的方式,并且服务端配置了跨域origin的白名单。
JS与crossorigin
embed方式引入的跨域脚本,如果有异常,不会在window.onerror 中抛出。这是出于安全考虑。
因为按照同源策略,脚本是可以访问到本地的同源数据(cookie等)。
如果想要这些异常信息,需要用crossorigin属性,走CORS请求。
document.domain的修改
api document.domain支持修改当前doucment的domain值。由于同源的判断是基于这个domain属性的,因此修改有如下的限制:
这段话提到了一个概念:effective domain
https://html.spec.whatwg.org/multipage/origin.html#concept-origin-effective-domain
iframe
iframe也收到同源策略的保护
iframe是embed的一种,因此需要服务端做iframe的安全配置。
参考:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
access-control-allow-origin 返回header对于iframe无效(ifram也是embed的一种)
window.open
window.open 方法可以打开一个新的tab,并放回一个window对象。不过这个window对象也受同源策略的保护。
跨域脚本只允许访问该对象中的部分属性和部分方法。
具体参考:
不允许用脚本修改的请求header
最重要的是origin和referer,是不能通过脚本修改的。