概述
浏览器普遍使用origin来表示权限范围。通过隔离不同origin的内容来防止某些恶意网站的运营人员(黑客)干涉正常网站的运行。本文除了描述构成origin概念的几个核心准则外,还描述了如何确定URI的origin、如何把origin序列化成字符串。本文也定义了一个命名为"Origin"的HTTP头部字段,用来表示与当前HTTP请求相关的origin。
介绍
浏览器解析执行来自不同网站的网页内容,尽管很多网页开发者怀有善意,但是还有一部分可能怀有恶意。浏览器实现方希望能够保护正常网站的安全性不受其他恶意网站的影响。
浏览器加载并渲染来自服务器的HTML文档时会执行包含在这些文档中的脚本程序,浏览器希望阻止恶意脚本读取其他服务器的文档内容。
浏览器根据origin把内容划分成不同的部分,同一个origin的内容之间可以随意交互,但是不同origin之间交互会受到某些限制。
本文档描述了same-origin机制的几个核心准则,同时也对比较和序列化origin做了详细介绍。本文档不会对same-origin的每个方面都进行介绍,细节方面的介绍交由具体的协议文档,比如HTML和WebSockets。
Same-Origin的准则
浏览器按照服务器的网页内容执行相应的动作,比如网页重定向,为脚本提供文档Dom接口。
如果没有任何安全模型的话,浏览器可能会执行对用户有危害的某些行为。随着时间的推移,web技术慢慢地形成了一个称为“same-origin”的网络安全模型。尽管这个安全模型现在进化成很复杂的技术,但是我们可以通过几个核心概念来理解这个模型。这个章节描述了这些核心概念,并且给出了几个如何安全使用这些概念的建议。
信任(Trust)
Same-origin机制通过URI地址来决定是否信任。比如HTML文档通过URI指定运行哪些脚本,< script src=“https://example.com/library.js”>< /script >。当浏览器执行到这个HTML元素的时候,会从URI指定的地址下载js脚本,然后为脚本赋予和当前文档相同的权限。文档通过这种方式把自己所拥有的权限赋予URI指定的资源,也就是说文档相信URI指定的资源。
除了通过URI引用类库外,浏览器也会向URI指定的服务器发送信息,比如HTML表单提交:
<form method="POST" action="https://example.com/login">
... <input type="password"> ...
</form>
当用户输入用户名和密码之后点击提交,浏览器就会把信息发送到表单URI指定的服务器,这就表明文档的编写者信任URI指定的服务器。
注意(Pitfalls)
当使用Same-origin机制设计新协议的时候,不同的信任对象一定要在URI中体现出来,如果http即表示使用tls安全加密传输也表示不使用tls安全加密传输,那么当文档期望使用tls加密传输的时候,就没办法通过URI指定。所以,通过https表示安全加密传输之后,文档就可以指定只信任https传输的资源文件,这样可以防止黑客攻击。
来源(Origin)
原则上,浏览器认定不同的URI为不同的资源,不同的资源相互通讯需要有明确的许可。这样会为开发增加很多复杂度,因为一个网页应用会包含很多不同的URI。
现在浏览器普遍地把这些不同的URI按照规则划分为不同的组,每个组都称为origin。简单地讲,如果两个URI如果有相同的scheme、host和port就算是属于同一个origin(组)。
提问:为什么不直接使用host当作划分组的依据?
在origin元组中包含scheme是确保网络安全的前提。如果浏览器不包括sheme,那么http://example.com 和 https://example.com之间就没有任何资源隔离。如果没有这层隔离的话,网络黑客可以通过修改来自http://example.com 网站的内容,然后通过修改后的文档控制浏览器去获取 https://example.com中 的内容,从而可以绕过网站设置的TLS(RFC5246)安全层。
提问:为什么使用主机的全限定主机名(portal.ibm.com),而不只是使用顶级域名?
比如教育相关的网站,学生会把相关信息放在 https://example.edu/~student/ ,但是这并不意味着,这个地址内的内容会和位于 https://grades.example.edu/ 的管理年级信息的网页应用拥有相同的权限。这个例子说明了,很多网站在部署的时候都没有按照相关应用场景合理地规划主机域名,所有的学生信息管理应用都使用相同的权限显然不是很合理。所以Origin的分组粒度规划就是网络安全模型的发展史。
几个简单例子
如下几个网站拥有相同的origin:
http://example.com/
http://example.com:80/
http://example.com/path/file
每一个网站都拥有相同的scheme、host、和port。
下面每两个地址都不是相同的origin:
http://example.com/
http://example.com:8080/
http://www.example.com/
https://example.com:80/
https://example.com/
http://example.org/
http://ietf.org/
授权(Authority)
尽管浏览器把多个URI划分为同一个origin,但是并不是一个origin里的所有资源都拥有相同的权限。比如图片是一个被动获取的资源,它没有与origin中其他资源相同的权限。相反,HTML文档拥有origin的所有权限,并且文档中的脚本可以访问所属origin中的其他资源。
浏览器通过检查资源的媒体类型(media type)来决定授予资源多少权限。当处理用户生成的网页内容时,网络应用可以通过设置媒体类型来限制内容的权限。比如image/png类型的内容就比text/html类型的内容安全的多。很多网页应用在它们的HTML文档中包含了很多不可信的内容,如果处理不当,这些应用可能会把自己的权限暴露给这些不可信的内容,xss(cross-site scripting)就是利用了这种漏洞。
注意(Pitfalls)
在为网络平台设计新功能时,必须谨慎地考虑媒体类型的权限问题。很多网络应用在处理不可靠内容时,一般都会为它们设置不同的媒体类型来限制权限。新功能如果为不可靠的内容授予过高权限可能会为当前的应用架构带来很多安全漏洞。授予权限时,应该把权限授予已经拥有所有权限的某些媒体类型,或者创建一个专门用来表示某些权限的媒体类型。
为了与那些实现媒体类型不正确的服务器做兼容,浏览器使用“content sniffing”[SNIFF]表示与服务器提供的媒体类型不一样的类型。如果处理不当,浏览器很有可能会为低权限的资源(image)授予很高的权限(HTML文档)。
机制(Policy)
通常来说,浏览器通过origin隔离不同的资源,并且允许不同origin的资源可以进行一些受限制的通讯。具体浏览器如何隔离以及如果提供通讯功能取决于几个要素。
对象访问(Object Access)
浏览器通常只给属于同一个origin的资源提供相应的访问接口,更详细的讲,如果两个资源的scheme、host和port都一样,那么它们之间就可以互相访问。
也有一些例外,比如一些HTML location接口可以访问其他origin的内容,HTML的postMessage接口也可以跨origin访问。把当前的接口暴露其他origin非常危险,因为这样可能会导致潜在的网络攻击。
网络访问(Network Access)
往往会根据是否同属于一个origin来决定是否可以访问网络资源。
通常来讲,读取其他origin的网络内容是被禁止的。但是,一个origin内的资源是可以读取其他origin中的某些类型资源。比如在某个origin中是可以执行其他origin中的脚本,渲染图片,以及加载样式表(css)。
同样的,一个origin中可以通过frame来展示其他origin的HTML内容。网络资源可以自由选择是否允许其他origin的服务读取自己的内容,比如通过CORS方式。在这些场景中,这种访问控制基本上是基于单个origin进行授权访问。
向其他origin的服务发送信息是可以的,但是在网络上发送任意格式的数据是比较危险的。基于这个原因,浏览器一般会限制脚本以一定的格式发送信息,比如没有自定义头部的HTTP请求。必须谨慎地增加新的网络协议,比如增加WebSockets支持时不能引入其他网络漏洞。
注意(Pitfalls)
当浏览器允许一个origin中的资源与另一个origin的资源进行交互时会引入新的安全问题。比如展示来自另一个origin的图片时,可能会暴露自己的宽度和高度。从一个origin向另一个origin发送网络请求时可能会引起CSRF攻击。当然,浏览器提供商通常会在跨origin通讯带来的优势与弊端之间权衡。比如浏览器可以通过阻止跨origin访问,禁止用户在本网站中点击其他网站的超链接。
为一种资源添加某个权限同时防止origin中的其他资源拥有这个权限会非常有意思,但是这种授权机制基本上不会生效,因为浏览器是按照origin来进行资源隔离,同一个origin的资源会持有相同的权限。
结论(Conclusion)
Same-origin通过URI来确定信任关系。多个URI组成一个origin,从而形成一个保护域。Origin中的主动资源(active content,比如js)会授予Origin中的所有权限,其他的被动资源(passive content,比如img、css)不会授予这些权限。
URI中的Origin(Origin of a URI)
通过如下算法可以确定一个URI的Origin:
- 如果URI不是层次结构的,或者不是一个绝对地址,那么生成一个全局唯一的标识符。
- 每次运行这个算法都会为同一个URI生成不同的值,所以浏览器会缓存一个HTML文档的Origin值,之后的权限检测会使用第一次计算的Origin。
- 获取URI中的scheme值(比如http、https、file)作为Origin的uri-scheme,并且转换成小写。
- 如果浏览器不支持uri-scheme中指定的协议,那么生成一个全局唯一的标识符。
- 如果uri-scheme是file,那么不同的浏览器实现可能有不同的返回值。
- 历史上,浏览器会为uri-scheme为file的内容授予很高的权限。为本地文件授予这么高的权限可能会遭受特权提升攻击。某些浏览器会为本地文件授权目录级别的权限,但是目前这个方案没有被广泛的采用。目前很多浏览器会为每个本地文件生成一个不同的全局唯一的标识符,这也是目前最安全的措施。
- 获取URI中的host值并赋予uri-host,并且转换成小写。
- 本文档假设浏览器在构建URI时使用IDNA(Internationalizing Domain Names in Applications)规则。并且假定uri-host的值只包含LDH字符,因为浏览器会把non-ASCII的字符转换成A-labels。基于这个原因,origin-based安全机制对浏览器使用的IDNA算法比较敏感。
- 获取uri-port:
- 如果URI没有指定端口,使用uri-scheme指定协议的默认端口。
- 否则使用URI的端口。
- 返回(uri-scheme, uri-host, uri-port)三元组。
比较Origin(Comparing Origins)
只有符合如下规则才会认为两个Origin是相等的:
- 如果两个Origin都是scheme/host/port三元组,那么三元组对应的值都相等,才会认为相同。
- 三元组和唯一标识符永远不相等。
某个URI与自己并不一定属于同一个origin。比如uri-scheme为data的URI,因为“data:”并不使用server-based naming授权机制。
序列化Origin(Serializing Origins)
本章节介绍如何把Origin序列化成unicode字符串和ASCII字符串。
序列化成Unicode字符
下面算法描述了如何把Origin转换成Unicode字符:
- 如果Origin不是scheme/host/port三元组,返回null(U+006E, U+0075, U+006C, U+006C)。
- 如果是scheme/host/port三元组,返回scheme。
- 拼接“: //”。
- 把host的每个部分使用U+002E(“.”)分隔。
- 如果host值使用A-label,那么转换成对应的U-label。
- 否则,一字不差的返回。
- 如果scheme/host/port三元组中port不是scheme指定协议的默认端口,那么:
- 追加U+003A(“:”),把端口号转换成十进制拼接在冒号后面。
- 返回拼接的字符串。
序列化成ASCII字符
下面算法描述了如何把Origin转换成ASCII字符:
- 如果Origin不是scheme/host/port三元组,返回null(U+006E, U+0075, U+006C, U+006C)。
- 如果是scheme/host/port三元组,返回scheme。
- 拼接“: //”。
- 拼接scheme/host/port三元组中的host。
- 如果scheme/host/port三元组中port不是scheme指定协议的默认端口,那么:
- 追加U+003A(“:”),把端口号转换成十进制拼接在冒号后面。
- 返回拼接的字符串。
HTTP中的Origin头部字段
这个章节定义了HTTP协议中的Origin头部字段
语法
origin = "Origin:" OWS origin-list-or-null OWS
origin-list-or-null = %x6E %x75 %x6C %x6C / origin-list
origin-list = serialized-origin *( SP serialized-origin )
serialized-origin = scheme "://" host [ ":" port ]
; <scheme>, <host>, <port> from RFC 3986
语义(Semantics)
当脚本向服务器发送HTTP请求的时候,浏览器会使用Origin字段用来表明脚本是从哪个站点下载的,可以使得服务器来判断是否接收这个HTTP请求。在某些场景中,可能Origin的值有多个,比如,最初的一个origin发起了HTTP请求,然后转发到另一个origin,那么可以通过Origin字段向服务器表明HTTP请求涉及到两个origin地址。
浏览器要求(User Agent Requirements)
浏览器可能在每一个HTTP请求都会包含Origin头部字段。
浏览器不能在一个HTTP请求中包含多个Origin头部字段。
当浏览器工作在比较隐私的场景下,Origin的字段值必须为“null”。
文档不规定隐私场景的具体含义,应用程序(不指浏览器)可以根据需要规定什么场景下发送的HTTP请求中的Origin字段值为“null”。
当生成Origin字段时,浏览器必须符合如下两个要求:
- origin的值必须是ascii序列化的。
- 两个origin不能是一样的。
安全性考虑(Security Considerations)
Same-origin是很多用户代理,包括浏览器,安全性的基础。用户代理一直在尝试使用其他安全机制,包括污点跟踪(taint tracking)和泄漏保护(exfiltration prevention),但是这些方式很难实现。
评估same-origin的安全性比较困难,因为origin在安全领域是一个比较核心的概念。Origin在这里只是表示一组隔离特性,它并不能解决所有的问题,接下来讨论same-origin的几个弱点。
对DNS的依赖(Reliance on DNS)
实际场景中,same-origin机制依赖于DNS域名解析服务,因为现在流行的URI scheme都使用DNS,比如HTTP。如果DNS服务被攻击了,那么same-origin机制不能为应用提供安全保障。
其他的URI scheme,比如https,在DNS受攻击时,可以保证应用需要的安全性,因为https可以为浏览器提供其他安全机制,比如可以验证从URI下载的证书来确保内容的安全性。其他的URI scheme,比如chrome-extension使用基于公钥的命名授权可以提供更好的安全机制。
Origin机制可以隔离来自不同URI的内容,这样可以消除DNS攻击的部分影响。
隔离粒度(Divergent Units of Isolation)
随着时间地推移,很多网络技术认为Origin是比较合理的隔离粒度。然而,现在比较流行的网络技术,比Origin的概念出现地还要早,比如cookies,这些技术有其他的隔离粒度,所以会导致一些网络漏洞。
可以选择使用注册机构管理(registry-controlled)的域名作为一个域(比如使用example.com而不是www.example.com)。这个方案有一些问题,所以不推荐使用。
- 注册机构(registry-controlled)域名只是方便我们使用的一种简便方式。比如,在日本,很多地级市的运行的DNS服务器,在DNS层级结构中往往比较深。有一些常用的"public suffix lists",但是这些列表的值很难保持最新。
- 不能与不使用基于DNS命名授权的URI scheme保持兼容。比如某个URI scheme使用公钥作为命名授权机制。再或者某些URI scheme,比如nntp,解析点号的顺序与DNS解析点号的方向刚好相反(比如alt.usenet.kook)。再或者,某些URI scheme使用DNS但是其中的字符是反方向排列(比如com.example.www)。
环境权限(Ambient Authority)
注:环境权限(英文:Ambient Authority)是系统访问控制研究中的术语。当主体(比如某个计算机程序或Linux上的某个用户)指明它需要的客体(Object,比如某一文件)的名称和它将要对该客体执行的动作(Operation,比如“复制”)便可以完成该动作的时候,我们称该主体使用了环境权限。
当使用same-origin机制的时候,浏览器基于URI进行授权,而不是对内容所能访问的接口或者资源进行授权。不考虑内容所能访问的接口或者资源从而进行全部授权的机制是环境授权的一种,这种机制会有某些漏洞。
比如HTML文档中的跨站脚本,如果黑客可以在HTML中注入某些脚本内容,这个脚本就会拥有这个文档的所有权限,那么这个脚本可能会访问某些敏感信息,比如用户的病历信息。所以,如果能限制脚本访问的内容,那么,即使黑客注入了攻击脚本,那么也不能获得更多信息。
IDNA依赖和迁移(IDNA Dependency and Migration)
Same-origin机制的安全性严重依赖浏览器使用的IDNA算法。某些浏览器可能根据使用IDNA2003或者IDNA2008把某些国际域名映射成不同的ASCII值。
从一个IDNA算法迁移到另一个IDNA算法会重新界定某些安全范围,有可能会重新建立安全边界或者把两个互不信任的两个实体的安全边界拆除。把两个互不信任的实体放入同一个origin会非常的危险,因为这样可能导致其中一个攻击另一个。
IANA相关
Header field name: Origin
Applicable protocol: http
Status: standard
Author/Change controller: IETF
Specification document: this specification (Section 7)
本文翻译自:https://datatracker.ietf.org/doc/html/rfc6454