同源策略

同源策略是一种关键的安全机制,它限制了一个 加载的文档或脚本与另一个源的资源进行交互的方式。

它有助于隔离潜在的恶意文档,减少可能的攻击向量。例如,它阻止互联网上的恶意网站通过浏览器中的 JS 读取第三方 Web 邮件服务(用户已登录)或公司内网(攻击者无法直接访问,因为它没有公共 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:blankjavascript: URL 的页面执行的脚本会继承包含该 URL 的文档的源,因为这些类型的 URL 不包含有关源服务器的信息。

例如,about:blank 通常用作新的、空白的弹出窗口的 URL,父脚本会将内容写入其中(例如,通过 Window.open() 机制)。如果此弹出窗口也包含 JavaScript,则该脚本将继承与其创建者相同的源。

data: URL 会获得一个新的、空的、安全上下文。

文件源

现代浏览器通常将使用 file:/// 方案加载的文件的源视为不透明源。这意味着,如果一个文件包含同一文件夹中的其他文件(例如),它们不会被假定来自同一源,并可能触发 CORS 错误。

请注意,URL 规范指出文件的源是实现定义的,并且某些浏览器可能会将同一目录或子目录中的文件视为同源,尽管这会产生安全隐患

更改源

警告:此处描述的方法(使用 document.domain 设置器)已弃用,因为它会削弱同源策略提供的安全保护,并使浏览器中的源模型复杂化,导致互操作性问题和安全漏洞。

页面可以更改其自身的源,但有一些限制。脚本可以将 document.domain 的值设置为其当前域或其当前域的超域。如果设置为当前域的超域,则使用较短的超域进行同源检查。

例如,假设来自 http://store.company.com/dir/other.html 文档的脚本执行了以下操作

js
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:8080company.com 通信。必须同时在两者中设置,以便它们的端口号都为 null

该机制存在一些限制。例如,如果文档位于沙盒化的 <iframe> 中,它将抛出 SecurityError DOMException,并且以这种方式更改源不会影响许多 Web API(例如 localStorageindexedDBBroadcastChannelSharedWorker)使用的源检查。更全面的故障案例列表可以在 Document.domain > 故障 中找到。

注意:当使用 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 标头来防止跨域帧。

如何允许跨域访问

使用 CORS 允许跨域访问。CORS 是 HTTP 的一部分,它允许服务器指定浏览器应允许加载内容的任何其他主机。

如何阻止跨域访问

  • 为了防止跨域写入,请在请求中检查一个不可猜测的令牌——称为跨站请求伪造 (CSRF) 令牌。您必须阻止对需要此令牌的页面的跨域读取。
  • 为了阻止对资源的跨域读取,请确保它无法被嵌入。通常需要阻止嵌入,因为嵌入资源总是会泄露有关该资源的一些信息。
  • 为了阻止跨域嵌入,请确保您的资源不能被解释为上面列出的可嵌入格式之一。浏览器可能不会尊重 Content-Type 标头。例如,如果您将 <script> 标签指向一个 HTML 文档,浏览器将尝试将 HTML 解析为 JavaScript。当您的资源不是您网站的入口点时,您还可以使用 CSRF 令牌来防止嵌入。

跨域脚本 API 访问

JavaScript API,如 iframe.contentWindowwindow.parentwindow.openwindow.opener 允许文档直接相互引用。当两个文档不具有相同的源时,这些引用对 WindowLocation 对象的访问非常有限,如下一个两个部分所述。

要实现不同源之间的文档通信,请使用 window.postMessage

规范:HTML Living Standard § Cross-origin objects

Window

以下对这些 Window 属性的跨域访问是允许的

方法
window.blur
window.close
window.focus
window.postMessage
属性
window.closed 只读。
window.frames 只读。
window.length 只读。
window.location 读/写。
window.opener 只读。
window.parent 只读。
window.self 只读。
window.top 只读。
window.window 只读。

某些浏览器允许访问比上述更多的属性。

Location

以下对 Location 属性的跨域访问是允许的

方法
location.replace
属性
location.href 只写。

某些浏览器允许访问比上述更多的属性。

跨域数据存储访问

对浏览器中存储的数据(如 Web StorageIndexedDB)的访问按源进行分隔。每个源都有自己的独立存储,一个源中的 JavaScript 无法读取或写入属于另一个源的存储。

Cookie 使用独立的源定义。页面可以为其自己的域或任何父域设置 cookie,只要父域不是公共后缀。Firefox 和 Chrome 使用 公共后缀列表来确定域是否为公共后缀。设置 cookie 时,您可以使用 DomainPathSecureHttpOnly 标志来限制其可用性。读取 cookie 时,您看不到它是从哪里设置的。即使您只使用安全的 https 连接,您看到的任何 cookie 都可能是在不安全的连接上设置的。

另见