同源策略

Same-Origin Policy

1995 年,首次提出同源策略的概念,在 Netscape Navigator 2.02 中引入(在 V2.0 中引入 JavaScript 后不久)。最初,同源策略旨在保护对 DOM 的访问,后来扩展到保护全局 JavaScript 对象的敏感部分。

所有的现代浏览器都实现某种形式的同源策略,因为它是重要的安全基石。这些策略不需要匹配精确的规范,但通常会扩展用来定义与其它 Web 技术大致兼容的安全边界,比如 Microsoft Silverlight、Adob​​e Flash,比如用于直接 DOM 操作以外的机制 XMLHttpRequest。

1. 工作原理

same-origin 意味着 URL 里的 protocol、host、port 都相同,但凡有一个不同就是 cross-origin。

origin = protocol + host + port

同源策略是 Web 应用程序安全模型中的一个重要概念。根据该策略,只有当两个网页具有相同的 origin 时,浏览器才会允许第一网页中包含的 script 访问第二网页中的 data。它可以防止一个页面中的恶意 script 获取对另一个网页上的敏感 data 的访问权限。

同源策略限制了不同 origin 之间如何进行资源交互。它有助于隔离潜在的恶意文档,减少可能的攻击,比如防止互联网上的恶意网站在浏览器中运行 JS 以从第三方 Web 邮件服务(用户已登录)或公司内网读取数据并转发给攻击者。这种机制,对于广泛依赖 HTTP cookie 来维护 authenticated(经过身份认证的)user session 的现代 Web 应用程序来说,具有特殊意义。客户端必须严格区分不相关 site(站点)提供的内容,以防丢失数据机密性或完整性。

2. 防止身份信息泄露

同源策略可以防止通过复用 authentication(身份认证)读取敏感的 cross-origin response,即防止 cross-origin 复用 authenticated session(经过身份认证的会话)。

举个例子说明,在没有同源策略的情况下可能出现的潜在安全风险。

假设用户正在访问银行网站且没有退出登录,然后用户转去另外一个包含恶意 JavaScript 代码的站点,该代码会从银行站点请求数据。 由于用户在银行网站上仍保持着登录状态,因此恶意代码可以执行用户在银行网站上能执行的任何操作,比如获取用户最近的交易列表、创建新交易等。

银行网站的所有者期望常规浏览器不允许从恶意网站加载的代码访问银行的 session cookie 或 platform-level authorization(平台级授权)。虽然 JavaScript 确实无法直接访问银行的 session cookie,但它仍然可以使用银行站点的 session cookie 向银行站点发送和接收 request。

同源策略的引入是为了满足安全浏览器的要求,以拒绝对 cross-origin response 的读取访问,并假设大多数用户选择使用兼容的浏览器。 同源策略不拒绝写入,要阻止写入权限的滥用需要目标站点提供额外的 CSRF(Cross-Site Request Forgery,跨站点请求伪造)保护。

3. 仅适用于 script

同源策略仅适用于 script,理解这一点非常重要。

这意味着,通过 HTML 标签来加载 cross-origin 的图像、CSS、动态加载 script 等资源是不受限制的(font 是个例外)。攻击通常会利用同源策略不适用于 HTML 标签的这一事实。

3.1 HTML 标签的 cross-origin 访问

同源策略不适用于 HTML 标签,所以网页可以自由地嵌入 cross-origin 样式表、脚本、图像、音视频和 iframe 等。比如:

内嵌
举例

HTML

  • 外部 CSS <link rel="stylesheet" href="...">。跨源 CSS 需要正确的 Content-Type header,如果它的 MIME 类型不正确且资源不以有效 CSS 构造开头,那么浏览器会阻止跨源样式表的加载

  • 外部 JS <script src="..."></script>。语法错误的详细信息仅适用于同源脚本

  • 多媒体资源 <img>, <audio>, <video>

  • 外部资源 <iframe>(站点可用 X-Frame-Options header 防止跨源 framing

  • 外部资源 <object>, <embed>

CSS

  • 跨源字体 @font-face(有些浏览器只能同源)

cross-origin network 访问通常分为三类:

跨域网络交互
说明

cross-origin write

通常是允许的,比如链接、重定向、表单提交。某些 HTTP 请求需要 preflight(预检),详见 CORS(Cross-Origin Resource Sharing,跨源资源共享)

要阻止,可检查请求的 CSRF(Cross-Site Request Forgery,跨站点请求伪造)token,必须阻止 cross-origin 读取需要此令牌的页面

cross-origin read

通常是不被允许的。然而,读权限却经常因为 embedding 泄漏,比如我们可以读取嵌入图像的尺寸、嵌入脚本的动作、嵌入资源的可用性

要阻止,请确保该资源是不可嵌入的,因为嵌入资源总会泄漏一些有关它的信息

cross-origin embed

通常是允许的,因为同源策略不适用于 HTML 标签,网页是可以自由地嵌入 cross-origin 图像、样式表、脚本、iframe 和音视频

要阻止,请确保该资源不被解释为可嵌入格式。浏览器可能会不遵守 Content-Type header, 比如如果将标签 <script> 指向 HTML 文档,浏览器将尝试把 HTML 解析为 JavaScript。当资源不是站点入口时,还可以使用 CSRF 令牌来防止嵌入

3.2 由脚本发起的 HTTP 请求

基于同源策略,浏览器会限制由脚本发起的 cross-origin HTTP request,比如 XMLHttpRequest 和 Fetch API 都遵循 same-origin 策略。这意味着,使用这些 API 的 Web 应用程序只能从 same-origin 请求资源,除非来自其它 origin 的 HTTP response 包含正确的 CORS header。

CORS(Cross-Origin Resource Sharing,跨域资源共享)能够让 Web server 自主地选择允许访问其资源的 cross-origin,浏览器以此为依据决定是否阻止前端 JavaScript 代码访问 cross-origin request 的 response。

3.3 脚本 API

JavaScript API 允许文档之间可以直接相互引用,比如 window.parent, window.open, window.openeriframe.contentWindow

然而,当两个文档不同 origin 时,上面的 API 能对 WindowLocation 对象的访问非常有限。如下:

对象
允许 cross-origin 访问的方法和属性
说明

Window

  • window.postMessage()

  • window.close()

  • window.blur()

  • window.focus()

要在 cross-origin 的文档之间进行通信,可以使用 window.postMessage

  • window.location

可读可写的属性

  • window.parent

  • window.self

  • window.top

  • window.opener

  • window.window

  • window.length

  • window.frames

  • window.closed

只读属性

Location

  • location.replace()

  • location.href

只写属性

有些浏览器允许访问的属性,比上面提到的多。规范 HTML Living Standard § Cross-origin objects

这里特别提一下 window.postMessage(),它是 Cross-Document Messaging,跨文档消息传递。在 HTML5 之前,Web 浏览器不允许 cross-site scripting(XSS)以防止安全攻击,然而这种做法也禁止了非敌对页面间的通信,使得任何类型的文档交互都变得困难。而 postMessage() API 允许脚本跨越这些边界进行交互,同时提供基本的安全级别。

3.4 数据存储

存储在浏览器中的数据(比如 Web Storage、IndexedDB),都是按 origin 来的,即每个 origin 都有自己的单独存储。一个 origin 的 JavaScript 无法读写属于另一 origin 的存储。

而 cookie 是使用单独的 origin 定义。只要页面的 parent domain 不是公共后缀(比如 .com, .cn, .org 等),它就可以为 own domain 和任何 parent domain 设置 cookie。

  • 设置 cookie 时,可以使用 Domain, Path, SecureHttpOnly 标志来限制其可用性。可以在服务器端用 Set-Cookie HTTP header 来设置和修改,也可以用 JavaScript document.cookie 来设置和修改

  • 读取 cookie 时,我们无法看到它是在哪儿设置的,可能是使用 HTTPs 连接设置的,也可能是使用不安全连接设置的

4. 放宽同源策略

在某些情况下,同源策略限制过多,会给使用多个 subdomains(子域)的大型网站带来问题。最初,使用了许多解决方法来在不同 domain 中的文档之间传递数据,比如 URL fragment、window.name

现代浏览器,支持多种技术以受控的方式放宽同源策略。

技术
说明

CORS(已标准化)

该标准使用新的 Origin request header 和 Access-Control-Allow-Origin response header 扩展了 HTTP。

CORS 允许服务器使用 header 显式列出可能请求文件的 origin,或使用通配符 * 允许任何 site 请求文件。浏览器使用此 header 来允许使用 XMLHttpRequest 的 cross-origin HTTP request,否则同源策略将禁止这些请求。

postMessage()

Cross-Document Messaging,跨文档消息传递

跨文档消息传递允许一个页面的 script 将文本消息传递到另一个页面上的 script 而不管 script 的 origin。

window.postMessage() 会异步触发该窗口中的 onmessage 事件,从而触发任何用户定义的事件处理程序。一个页面中的 script 仍然无法直接访问另一页面中的方法或变量,但可以通过这种消息传递技术安全地进行通信。

WebSockets

现代浏览器将允许 script 连接到 WebSocket 地址而无需应用同源策略,但是它们会识别何时使用 WebSocket URI 并将 Origin: header 插入到 request 中。为了确保 cross-site 安全,WebSocket server 必须把 header data 和允许接收回复的 origin 白名单进行比较。

JSONP

由于允许 HTML <script> 元素检索和执行来自其它域的内容,因此页面可以绕过同源策略来接收来自不同域的 JSON 数据,通过加载返回 JSONP payload 的资源。 JSONP payload 由一个被 pre-defined function call 包装的内部 JSON payload 组成。当浏览器加载 script 资源时,指定的 callback function 会被调用,以处理包装的 JSON payload。

其它

document.domain setter 已废弃 用它把两个页面的 domain 设置成一样的值

即使是使用纯粹的同源策略(没有使用 CORS 等技术手段),计算机也会受到某些 cross-origin 攻击。

其它信息

继承 origin

从 URL 是 about:blankjavascript: 的页面执行的脚本,会继承包含该 URL 的文档的 origin,因为那些类型的 URL 不包含有关源服务器的信息。比如用 window.open() 打开一个 about:blank 新页面,然后向其写入内容,如果此弹出窗口还包含 JavaScript,那么该脚本将继承与创建它的脚本相同的 origin。

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

安全上下文的主要目标是防止 MITM 攻击者访问强大的 API,比如侵犯用户的隐私、获得对用户计算机的低级访问权限、访问用户凭据等数据。

  • 本地交付的资源,例如 http://127.0.0.1, http://localhost, http://*.localhostfile:// 都被认为已安全交付

  • 非本地资源若要被视为安全,必须满足以下标准:

    • 必须通过 https://wss:// 提供服务

    • 用于传递资源的网络通道的安全属性不得被视为已弃用

可以使用功能检测 window.isSecureContext 来检查页面是否处于安全上下文中

file origin

现代浏览器通常将使用 file:/// 加载的文件 origin 视为不透明 origin,这意味着,如果一个文件包含来自同一目录中的其它文件,则不会认为它们 same origin,可能会触发 CORS 错误。

值得注意的是,URL 规范规定 file origin 的实现取决于浏览器。也就是说,有些浏览器会将同一目录或子目录中的文件视为 same origin,即使这具有安全隐患(如果用户打开本地保存的 HTML 文件,则该文件可以使用 file: URL 访问同一目录或子目录中的其它文件,就可以用 Fetch API 读取文件内容并将其上传到服务器了)。

主要参考

相关阅读

  1. HTTP CORS:Cross-Origin Resource Sharing,跨源资源共享

  2. HTTP 身份认证:基本认证、摘要认证

  3. CSRF:Cross-Site Request Forgery,跨站点请求伪造

Last updated