同源策略
同源策略 是一种重要的安全机制,它限制了一个由一个源加载的文档或脚本如何与来自另一个源的资源交互。
它有助于隔离潜在的恶意文档,从而减少可能的攻击途径。例如,它可以防止互联网上的恶意网站在浏览器中运行 JS 以读取来自第三方网络邮件服务(用户已登录)或公司内网(攻击者无法直接访问,因为没有公共 IP 地址)的数据,并将这些数据转发给攻击者。
源的定义
如果两个 URL 的协议、端口(如果指定)和主机相同,则它们具有相同源。您可能会看到它被引用为“方案/主机/端口元组”,或简称“元组”。(“元组”是一组项目,它们共同构成一个整体——双/三/四/五/等元组的通用形式。)
下表给出了使用 URL http://store.company.com/dir/page.html
进行源比较的示例
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html |
相同源 | 仅路径不同 |
http://store.company.com/dir/inner/another.html |
相同源 | 仅路径不同 |
https://store.company.com/page.html |
失败 | 协议不同 |
http://store.company.com:81/dir/page.html |
失败 | 端口不同(http:// 默认端口为 80) |
http://news.company.com/dir/page.html |
失败 | 主机不同 |
继承的源
从具有about:blank
或javascript:
URL 的页面执行的脚本继承包含该 URL 的文档的源,因为这些类型的 URL 不包含有关源服务器的信息。
例如,about:blank
通常用作新空白弹出窗口的 URL,父脚本在其中写入内容(例如,通过Window.open()
机制)。如果此弹出窗口还包含 JavaScript,则该脚本将继承创建它的脚本的相同源。
data:
URL 获取一个新的、空的、安全上下文。
文件源
更改源
警告:此处描述的方法(使用document.domain
设置器)已弃用,因为它破坏了同源策略提供的安全保护,并使浏览器中的源模型复杂化,从而导致互操作性问题和安全漏洞。
页面可以更改其自己的源,但有一些限制。脚本可以将document.domain
的值设置为其当前域或其当前域的父域。如果设置为当前域的父域,则较短的父域将用于同源检查。
例如,假设来自http://store.company.com/dir/other.html
中文档的脚本执行以下操作
document.domain = "company.com";
之后,页面可以与http://company.com/dir/page.html
通过同源检查(假设http://company.com/dir/page.html
将其document.domain
设置为“company.com
”以指示它希望允许这样做 - 有关详细信息,请参阅document.domain
)。但是,company.com
不能将document.domain
设置为othercompany.com
,因为这不是company.com
的父域。
端口号由浏览器单独检查。任何对document.domain
的调用,包括document.domain = document.domain
,都会导致端口号被覆盖为null
。因此,一个不能仅通过在第一个中设置document.domain = "company.com"
来使company.com:8080
与company.com
通信。它必须在两者中都设置,以便它们的端口号都为null
。
此机制有一些限制。例如,如果启用了document-domain
Permissions-Policy
或文档位于沙箱化的<iframe>
中,它将抛出“SecurityError
” DOMException
,并且以这种方式更改源不会影响许多 Web API(例如localStorage
、indexedDB
、BroadcastChannel
、SharedWorker
)使用的源检查。在Document.domain > Failures 中可以找到更详尽的故障案例列表。
注意:当使用document.domain
允许子域访问其父域时,需要在父域和子域中将document.domain
设置为相同的值。即使这样做是将父域设置回其原始值,这也是必要的。否则可能会导致权限错误。
跨源网络访问
同源策略控制两个不同源之间的交互,例如当您使用fetch()
或<img>
元素时。这些交互通常分为三类
- 跨源写入通常是允许的。例如,链接、重定向和表单提交。某些 HTTP 请求需要预检。
- 跨源嵌入通常是允许的。(示例列在下面。)
- 跨源读取通常是不允许的,但读取访问通常通过嵌入泄漏。例如,您可以读取嵌入图像的尺寸、嵌入脚本的操作或嵌入资源的可用性。
以下是一些可能跨源嵌入的资源示例
- 使用
<script src="…"></script>
的 JavaScript。仅对同源脚本提供语法错误的错误详细信息。 - 使用
<link rel="stylesheet" href="…">
应用的 CSS。由于 CSS 的语法规则宽松,跨源 CSS 需要正确的Content-Type
标头。如果跨源加载的 MIME 类型不正确且资源未以有效的 CSS 结构开头,浏览器将阻止样式表加载。 - 由
<img>
显示的图像。 - 由
<video>
和<audio>
播放的媒体。 - 使用
<object>
和<embed>
嵌入的外部资源。 - 使用
@font-face
应用的字体。某些浏览器允许跨源字体,其他浏览器则要求同源。 - 由
<iframe>
嵌入的任何内容。网站可以使用X-Frame-Options
标头来防止跨源框架。
如何允许跨源访问
如何阻止跨源访问
- 要防止跨源写入,请在请求中检查一个不可猜测的令牌——称为跨站点请求伪造 (CSRF) 令牌。您必须防止跨源读取需要此令牌的页面。
- 要防止跨源读取资源,请确保它不可嵌入。通常需要防止嵌入,因为嵌入资源总是会泄漏有关它的某些信息。
- 为了防止跨源嵌入,请确保您的资源无法被解释为上述可嵌入格式之一。浏览器可能不会尊重
Content-Type
标头。例如,如果您将一个<script>
标签指向一个HTML文档,浏览器将尝试将HTML解析为JavaScript。当您的资源不是您网站的入口点时,您还可以使用CSRF令牌来防止嵌入。
跨源脚本 API 访问
像iframe.contentWindow
、window.parent
、window.open
和window.opener
这样的JavaScript API允许文档直接相互引用。当两个文档没有相同的源时,这些引用提供了对Window
和Location
对象的非常有限的访问权限,如接下来的两节所述。
要跨不同源的文档进行通信,请使用window.postMessage
。
规范:HTML 实时标准 § 跨源对象。
窗口
允许以下跨源访问这些Window
属性
属性 | |
---|---|
window.closed |
只读。 |
window.frames |
只读。 |
window.length |
只读。 |
window.location |
读/写。 |
window.opener |
只读。 |
window.parent |
只读。 |
window.self |
只读。 |
window.top |
只读。 |
window.window |
只读。 |
一些浏览器允许访问比上述更多的属性。
位置
跨源数据存储访问
对存储在浏览器中的数据(例如Web 存储和IndexedDB)的访问按源分离。每个源都有其自己的独立存储,一个源中的 JavaScript 无法读取或写入属于另一个源的存储。
Cookie使用源的不同定义。页面可以为其自己的域名或任何父域名设置 Cookie,只要父域名不是公共后缀。Firefox 和 Chrome 使用公共后缀列表来确定域名是否为公共后缀。当您设置 Cookie 时,您可以使用Domain
、Path
、Secure
和HttpOnly
标志限制其可用性。当您读取 Cookie 时,您无法看到它是从哪里设置的。即使您只使用安全的 https 连接,您看到的任何 Cookie 也可能是使用不安全的连接设置的。