同源策略及其绕过详解
前言
最近在看吴翰清写的 白帽子讲 web安全 一书,遇到同源策略一词,我便开始深入学习相关内容,查阅了许多资料后,写该博客梳理、记录所学知识
基础知识
Origin(源)
- Origin: < scheme > “😕/” <host> [ “:” < port > ],origin由用于访问它的URL的scheme(协议)、port(端口)、host(IP或域名)定义
- scheme:请求所使用的协议,通常是HTTP协议或者它的安全版本HTTPS协议;host:服务器的域名或 IP 地址;port:服务器正在监听的TCP 端口号,可选
DOM
DOM(Document Object Model,文档对象模型)将 web 页面与脚本或编程语言连接起来。DOM模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。(简单来说通过 DOM,JavaScript 能够访问和改变 HTML 文档的所有元素)节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。具体参考文章
BOM
BOM(Browser Object Model,浏览器对象模型)允许 JavaScript 与浏览器对话
XMLHttpRequest
XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据,这允许网页在不影响用户操作的情况下,更新页面的局部内容
Ajax
AJAX(Asynchronous JavaScript And XML )是一种使用 XMLHttpRequest 技术构建更复杂,动态的网页的编程实践,简单地说就是使用 XMLHttpRequest 对象与服务器通信;异步特性:可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面;具体理解参考网站
cookie
cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上
localStorage
- localStorage是JavaScript中window对象的一个属性,只读属性
- localStorage用于在浏览器中存储key/value对的数据,可以长久保存网站的数据,无过期时间,直到手动删去,具体语法参考文档
IndexedDB
-
现有的浏览器数据储存方案,都不适合储存大量数据:Cookie 的大小不超过4KB,且每次请求都会发送回服务器;LocalStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引。所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景
-
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(包括文件/二进制大型对象),它使用索引实现对数据的高性能搜索;通俗地说,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作
SOP(同源策略)
同源
-
定义:若两个URL的 协议、端口、IP或域名 均相同,则这两个URL同源
-
下表给出了与 URL
http://store.company.com/dir/page.html
的源进行对比的示例:
同源策略
-
同源策略(Same Origin Policy,SOP)是由Netscape提出的一个著名的安全策略,它限制一个源(origin)加载的文档或脚本如何与来自另一个源的资源交互;
同源策略主要应用于从脚本访问数据,通过相应的HTML标记嵌入跨源的资源,如图像、CSS和脚本不受限制 -
同源策略(安全性)主要表现在 DOM、web 数据 和 网络 这三个层面
- DOM层面,同源策略限制了来⾃不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作
- 数据层面,同源策略限制了不同源的站点读取当前站点的Cookie、IndexDB、LocalStorage数据
- 网络层面, 同源策略限制了通过 XMLHttpRequest(Ajax请求) 等方式将站点的数据发送给不同源的站点
跨域–>同源策略的绕过
CORS(Cross-Origin Resource Sharing)
-
CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种基于HTTP头的机制,其中的HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应,该机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行
-
CORS还通过浏览器(
OPTIONS
方法)发起一个到服务器托管的跨源资源的**"预检"请求**,在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头 -
简单请求与预检请求
-
简单请求:一些CORS请求不会触发预检请求,这样的请求称为简单请求
-
若满足下述所有条件,则该请求可视为简单请求
- 请求方法是GET、HEAD、POST三者之一
- 允许人为设置的CORS 安全的首部字段集为:Accept、Accept-Language、Content-Language、Content-Type(有限制)
- Content-Type的值仅限于 text/plain 、multipart/form-data 、application/x-www-form-urlencoded 三者之一
- 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用
XMLHttpRequest.upload
属性访问 - 请求中没有使用
ReadableStream
对象
-
示例
-
假如站点
http://foo.example
的网页应用想要访问http://bar.other
的资源http://foo.example
的网页中可能包含类似于下面的 JavaScript 代码var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/public-data/'; function callOtherDomain() { if(invocation) { invocation.open('GET', url, true); invocation.onreadystatechange = handler; invocation.send(); } }
-
请求报文与响应报文
请求报文 GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/access-control/simpleXSInvocation.html Origin: http://foo.example //origin表明该请求来源于 http://foo.example 服务器响应 HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * //表明该资源可以被任意外域访问 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml [XML Data]
使用
Origin
和Access-Control-Allow-Origin
就能完成最简单的访问控制;上述例子中,将Access-Control-Allow-Origin: *
设置为Access-Control-Allow-Origin: http://foo.example
,那么除了http://foo.example
,其它外域均不能访问该资源
-
-
-
预检请求:在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求(一般都是浏览检测到请求跨域时,会自动发起),以检测实际请求是否可以被服务器所接受
-
当请求满足下述任一条件时,应该先发送预检请求
- 使用了 PUT、DELETE、CONNECT、TRACE、PATCH 方法之一
- 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段
- Content-Type 的值不属于 application/x-www-form-urlencoded、multipart/form-data、text/plain 三者之一
-
示例
-
如下是一个需要执行预检请求的HTTP请求(根据其请求内容可知需要先发起预检请求)
var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/post-here/'; var body = '<?xml version="1.0"?><person><name>Arun</name></person>'; function callOtherDomain(){ if(invocation) { invocation.open('POST', url, true); //使用POST请求发送XML文档 invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); //自定义的请求首部字段X-PINGOTHER invocation.setRequestHeader('Content-Type', 'application/xml'); //该请求的Content-Type为 application/xml invocation.onreadystatechange = handler; invocation.send(body); } } ......
-
发起预检请求
预检请求报文 OPTIONS /resources/post-here/ HTTP/1.1 //使用OPTIONS方法 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://foo.example Access-Control-Request-Method: POST //该字段告知服务器实际请求将使用POST方法 Access-Control-Request-Headers: X-PINGOTHER, Content-Type //该字段告知服务器实际请求携带的两个自定义首部字段 服务器响应报文 HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS //表明服务器允许客户端使用 POST, GET 和 OPTIONS 方法发起请求 Access-Control-Allow-Headers: X-PINGOTHER, Content-Type //表明表明服务器允许请求中携带字段 X-PINGOTHER 与 Content-Type Access-Control-Max-Age: 86400 //该字段表明该响应的有效时间为 86400 秒,也就是 24 小时,在有效时间内,浏览器无须为同一请求再次发起预检请求 Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
-
-
-
JSONP( JSON with padding)
-
在segmentfault看到一个回答说“JSONP 是一种请求一段 JS 脚本,把执行这段脚本的结果当做数据的玩法”,那么JSONP实现跨域只能使用GET请求(局限性很大,现在基本都是使用CORS跨域)就得到了很好的解释:我们通过
<script>
标签的src属性是不能引入一段POST提交的脚本的 -
在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但
<img>、<iframe>、<script>、<link>、<video>、<audio>
等标签不受同源策略约束,这些标签可以通过src属性请求到其他服务器上的数据;如<img>
的src(获取图片),<script>
的src(获取javascript) -
所以JSONP实现跨域请求的原理简单的说,就是动态创建
<script>
标签,然后利用<script>
的 src 不受同源策略约束来跨域获取数据,将前端方法作为参数传递到服务器端,再由服务器端注入参数之后返回,实现服务器端向客户端通信 -
JSONP请求的关键:服务端要在返回的数据外层包裹一个客户端已经定义好的函数,然后该函数会被客户端调用执行(否则浏览器会将返回的数据当作js代码执行)
iframe
- iframe是HTML标签,<iframe> 标签规定一个内联框架,用于在当前HTML页面嵌入另一个HTML页面,基础语法参考、示例参考
- iframe跨域的基本前提是:一个页面可以嵌套非同源站点的html文件,以及某一个域名下的html页面可以通过脚本向同域名服务器发出ajax请求;一级域名相同
- 深入剖析iframe跨域问题
document.domain
- 一级域名相同是使用document.domain跨域的前提
- document.domain解决跨域问题
window.name
片段识别符(fragment identifier)
跨文档通信API(Cross-document messaging)
WebSockets
参考文档
这篇博客中所学的都是一些新知识,故引用内容很多,自己的理解较少;一些的内容的引用地址在文中给出,还有一些在下面列出: