同源策略详解
一、“同源”定义
- 网站的“源”是由三元组(protocol、host、port)定义的。
- 两个网站同源是指:它们的协议、主机名(域名)、端口号均相同。
- 同源实质上是一种安全区域的划分,A和B同源表示它们同处一个Web服务端应用中,可以互相信任。
二、“同源策略”定义
- 同源策略:阻止一个源的JavaScript代码读取另一个源的数据。(包括localStorage、Cookie)
- 注意!读取资源和加载资源是两个概念,浏览器跨域加载资源是不受限制的(CSS加载字体文件是个例外)。
- 浏览器加载另一个源的资源,仅仅是把资源拉取回来展示或执行,当前页面的JavaScript代码并不会读取这个资源的内容,即JavaScript代码不会对这个资源的内容进行任何操作。
- 浏览器读取另一个源的资源,是指当前页面的JavaScript代码能够读取另一个源的资源的内容,如获取资源中的文本信息等。
- 注意!读取资源和加载资源是两个概念,浏览器跨域加载资源是不受限制的(CSS加载字体文件是个例外)。
- 同源策略并不是用于保护网站服务器安全的,要防的也不是网站的访问者。同源策略的作用是防止恶意网站读取用户在当前网站的数据,或防止恶意网站以用户的身份向当前网站发起操作。
- 同源策略中,网页跨域嵌入资源、网页跨域“写”操作(如:在一个网站中嵌入百度页面并在嵌入的百度页面的搜索框中输入搜索信息)都是被允许的,但就是不能通过JavaScript代码跨域读取内容,只有这样才能从根本上保证用户在网站上的数据不被其他恶意网站读取,举例如下:
- 假设没有同源策略存在;
- 当你访问一个恶意网站时,该恶意网站使用< iframe >嵌入了学信网的登陆页面,同时该恶意网站中嵌入了恶意JavaScript代码;
- 恶意网站加载完毕后,你在恶意网站嵌入的学信网页面中登陆了个人账户。
- 由于没有同源策略,JavaScript代码可访问已登陆的你的学信网个人账户中的内容,获取诸如学校、年龄、身份证号等信息;
- 假如存在同源策略,则恶意JavaScript代码无法跨域读取已登陆的你的学信网账户中的信息,因此成功保护住了个人隐私。
三、跨域前端DOM互访问【跨域前端页面交互】
- 如果一个公司的域名存在多个子域名,不同子域名的网页需要进行交互,即前端不同源的页面之间的DOM互访问,这种场景下,就需要某些措施来实现在同源策略下的跨域前端DOM互访问,下面介绍一些对应的解决方案。
3.1 document.domain方案 (不推荐)
- 修改两个子域名页面的document.domain属性使两者在同一个源内(主要是修改域名,因为两个子域名使用的协议相同,修改后的端口也均为null),这两个页面就可以互相使用JavaScript代码访问对方的DOM内容。
- 注意事项
- 修改document.domain时,浏览器会做安全检查,只允许将其修改为当前域名或者父域名,否则会抛出异常。
- 修改document.domain的同时会把当前源的端口置为“null”。
- 缺点
- 当修改后的域名还存在多个子域名时,如果其中某些子域名应用的安全不受自己控制或者存在漏洞时,将直接威胁当前域名应用的安全。
- 比如,若手动修改一个域名的document.domain后,端口便默认为null,若存在一子域名为恶意网站,则该恶意网站可通过修改自身document.domain使自身与父域名同处一个源即可实现使用JavaScript代码访问父域名中的数据。
- 当修改后的域名还存在多个子域名时,如果其中某些子域名应用的安全不受自己控制或者存在漏洞时,将直接威胁当前域名应用的安全。
3.2 window.name方案(不推荐)
- window.name属性用来保存当前窗口的名称
- 一个窗口设置好window.name的值后,如果窗口发生跳转,即使跳转到不同的域,该窗口的window.name属性值仍然会保留。
- 如果一个父页面通过iframe嵌入加载一个跨域子页面,该iframe中的页面修改自身window.name值(存入一定信息),然后再使用window.location跳转到与父页面同域的页面,那么由于嵌入的跳转后的子页面与父页面同源,因此父页面就可以读取子页面的window.name值,从而通过window.name这个载体传输一定的数据信息。
3.3 window.postMessage方案【标准方案】
- window.postMessage用于一个页面向另一个窗口发送消息。这里的窗口可以是通过window.open打开的子窗口,也可以是通过iframe嵌入的窗口。
- 子窗口和使用iframe嵌入的窗口也分别可以向opener和parent发送消息。
- 发送方可以指定接收方的源,以确保消息不会被发送给不可信的源,同时接受方也能检查发送方的源,以确保消息的来源是可信的。
- 使用window.postMessage发送及接收消息的示例代码如下
//发送方
targerwindow.postMessage(nessage, targerOrigin, [transfer]);
/*语法:otherWindow.postMessage(message, targetOrigin, [transfer]);
message:将要发送到其他窗口的数据
targetOrigin:指定哪些窗口能接收到消息事件,其值可以是“*”或一个URL。
[transfer]:可选,是一串和message同时传递的Transferable对象,这些对象的所有权被转移给消息的接收方,而发送方将不再保有所有权。
*/
//接收方
window.addEventListener("message", (event) => {
if(event.origin !== "可信的信息发送方") //最好再做一次数据格式校验
return;
//如果来自可信的信息发送方,则在此处理消息message。
}, false);
/*语法:element.addEventListener(event, function, useCapture);
event:字符串,用于指定事件名,该示例事件名为message。
function:指定事件触发时执行的函数
useCapture:可选,布尔值,true:指定事件在捕获阶段执行;false:默认,指定事件在冒泡阶段执行。
*/
四、跨域访问服务端
- 前面的案例讲的是两个不同源的前端页面互访问,另一种需求场景是前端页面需要从跨域的服务端获取数据,下面介绍一些解决方案。
4.1 JSONP方案
- 不跨域的情况下,在前端通常以JSON格式从服务端获取数据;虽然跨域时这种方案不可行,但可通过对JSON数据进行简单处理便可进行跨域共享,这便是JSONP(JSON with Padding)方案。
- JSONP方法
- 在JSON字符串前后做一些填充,使JSON变为一段JavaScript代码;
- 由于当前源加载跨域的JavaScript代码是不受限制的,所以跨域加载JSONP代码时,就把JSON数据当作参数传递给当前JavaScript执行环境所在的源。
- 通过函数参数传递JSON数据时,需要提前定义好回调函数,再通过
<script src="JSONP地址"></script>
载入JSONP代码,这样回调函数就得到了JSON数据。JSONP示例如下(其中JSON作为callback()回调函数的参数)
callback({
"key1":"value1",
"key2":"value2",
});
//其中括号"()"和分号";"是填充内容,使得JSON数据转变为JSONP(JavaScript代码)
- 另一种常见的填充方式是通过赋值语句把数据赋给一个变量,这样也能把JSON数据引入到当前JavaScript执行环境。
- 使用JSONP方案时的注意事项
- 不要在JSONP中包含敏感数据。
- 在不加限制的情况下,任何网站都可以载入JSONP的URL,从而使敏感数据被泄露给其他站点,这种攻击叫做“JSONP劫持”,该漏洞也被称为“只读型CSRF(Cross-site request forgery跨站请求伪造)漏洞”。
- 如果要通过JSONP传递敏感数据,那么在服务端要严格校验Referrer(Referrer是HTTP请求头的一部分,用于指示当前请求是从哪个URL页面发起的),确保只有可信的源可以跨域访问数据。
- 不要忽略Referrer为空的情况,这会带来安全隐患。
- 攻击者的网站只需在< script >标签中加上
referrerPolicy="no-referrer"
属性就可以控制浏览器不发送Referrer头信息,这时便可顺利获取到JSONP。 - 为了阻止不可信的源调用JSONP,服务端同时要拒绝不带Referrer头信息的JSONP请求。
- 攻击者的网站只需在< script >标签中加上
- JSONP方案实际上是在当前域的页面执行了另一个域的JavaScript代码,将当前域的风险面扩大了。
- 只有确保拥有JSONP代码的域是安全可信的,才能使用JSONP跨域传输。
- 在大部分Web应用中默认响应的Content-Type为“text/html”,因此在响应头中需要设置Content-Type为“application/javascript”。
- 不要在JSONP中包含敏感数据。
- JSONP方案只能实现单向的读操作(只支持GET请求),下面介绍更安全、更标准的做法来实现跨域访问。
4.2 跨域资源共享方案【跨域访问的标准】
- Cross-Origin Resource Sharing, CORS跨域资源共享是H5标准中的特性,它是一种跨域访问授权策略。
- 在CORS方案中,Web服务器可以指定哪些域能访问自己的资源、访问时是否需要携带凭证信息、哪些请求类型和请求头是被允许的,之后由浏览器或服务器来执行这些策略,同时阻止违反策略的行为。
- 在CORS中,与安全相关的主要是服务端响应的Access-Control-Allow-Origin头信息,它相当于定义了访问源的白名单。
- 服务端的另一个可选的CORS响应头信息为Access-Control-Allow-Credentials。
- 当属性为true时:表示服务器允许客户端将凭证信息(Cookie、Authorization头或客户端TLS证书)一起发送给服务器;
- 当属性为fasle时:若客户端设置了XMLHttpRequest对象的withCredentials=true,则这个跨域请求会失败。
- 简单跨域请求场景【历史遗留包袱】
- 通过JavaScript跨域访问数据时,浏览器会在请求中带上一个名为Origin的头信息,用于表示自己的源。
- 请求中的Origin头信息完全由浏览器控制,网页不能通过JavaScript脚本来修改其值。
- 服务器在对此请求的响应中需要带上一个名为“Access-Control-Allow-Origin”的头信息,用于指示哪些源可以访问自己。
- 在CORS中,Access-Control-Allow-Origin头信息的值可以是三种:“*”、一个明确的源、“null”
- 当设定Access-Control-Allow-Origin为“*”时:
- 设置后不允许客户端请求中带有凭证信息,即要求客户端请求中的withCredentials=fasle,如果withCredentials=true,则服务器拒绝访问请求。
- 因此,在Access-Control-Allow-Origin为“*”的情况下,由于跨域访问无法携带用户凭证信息,因此跨域访问只能获取到在非登陆状态时可得到的信息,而不能获取到登陆后才能得到的信息。
- 当设定Access-Control-Allow-Origin为一个明确的源时:
- 如果Web应用需要支持多个来源进行跨域访问,则需要对请求中的Origin进行白名单校验,如果Origin出现在白名单中,才将它作为Access-Control-Allow-Origin的值返回。
- 当设定Access-Control-Allow-Origin为null时【强烈不建议】:
- 对于本地文件系统加载的网页、使用Data URL加载的网页、沙箱化的iframe页面,浏览器均会为他们指定一个新的源“null”,即它们发出的跨域请求其Origin值均为null。
- 若Access-Control-Allow-Origin为null,则服务器会允许上述三类网页跨域访问当前源,这将存在较大安全风险,因为任意本地文件或者恶意网站通过Data URL或沙箱化iframe载入的页面均能够访问服务器的当前源。
- 当设定Access-Control-Allow-Origin为“*”时:
- Access-Control-Allow-Origin不允许含有通配符。
- 在CORS中,Access-Control-Allow-Origin头信息的值可以是三种:“*”、一个明确的源、“null”
- 需要注意到,在简单跨域请求场景中,当服务端产生响应时,代表着请求已被服务端接收,这可能会对服务端产生一定的负面影响,这便是所谓的历史遗留问题,即浏览器可在无需服务端同意的情况下直接发起请求,这导致CORS标准不得不兼容该请求方式从而催生出简单请求和复杂请求两种请求场景。
- 通过JavaScript跨域访问数据时,浏览器会在请求中带上一个名为Origin的头信息,用于表示自己的源。
- 复杂跨域请求场景
- 在正式发送请求前,浏览器会使用OPTIONS方法先发送一个预检请求(Preflight request)
- 预检请求中的Access-Control-Request-Method头信息包含了浏览器需要用到的方法,Access-Control-Request-Headers头信息包含了需要用到的HTTP头。
- 预检请求用于询问服务器是否允许这种请求方式,只有服务器允许,浏览器才会正式发送该请求。
- 浏览器使用预检机制来询问服务端是否允许该请求跨域访问,确保了请求不会对服务器造成负面影响。
4.3 私有网络访问方案【了解】
- chrome浏览器中,如果一个公网的非加密网站向私有IP网段的网站发起请求,浏览器会直接拒绝该请求,这是为了防止外部网站对内部网络的应用发起CSRF攻击。
- 浏览器会根据IP地址将网站的网络区域分为三类:公共网络、私有网络、本地网络。它们的私密属性是逐级递增的。
- 如果一个网络区域的网页向私密性更高的网络区域发起请求,这种行为就被称为私有网络访问。
- 浏览器访问私有网络前会发起预检请求并携带请求头
Access-Control-Request-Private-Network:true
- 只有目标应用对预检请求响应了
Access-Control-Allow-Private-Network:true
,浏览器才会继续请求访问目标应用,否则这个请求就会失败。
- 浏览器访问私有网络前会发起预检请求并携带请求头
4.4 WebSocket跨域访问
- 只通过服务端白名单来校验发起跨域访问请求的源是否合法。
五、参考资料
- 《白帽子讲Web安全(第二版)》——吴翰清、叶敏
本文作者:宇星海
本文链接:https://www.cnblogs.com/Yu-Xing-Hai/p/18589289/Homologous-Policy
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步