毁人不倦-令人困惑的浏览器安全策略:同源策略
2017-03-05 14:43
501 查看
同源策略(The same-origin policy)
这是浏览器的一个基本却又非常重要的安全策略,浏览器会限制对异源(异域)(我们常称之为别人家的站点)的资源操作。打个比方,你不会让老王来你家,也不允许他在你家墙上打个洞,装个监控啥的。通过这个比喻你就知道
同源策略的重要性了。
同源策略主要针对
脚本(script)的行为进行限制,而
<script>,
<link>,
<img>,
<iframe>,
<object>等带有
src属性的
dom元素一般不受影响,这也很好理解,你可以禁止老王进你家,但是无法限制他在自己家装个雷达对你家进行监控。因此,在没有得到授权的情况下,用
javascript脚本操作
异域的资源,那是不允许的。科学的说法应该是:浏览器允许发起请求,但如果响应中没有包含对方的许可的话,浏览器就会屏蔽响应结果,不给你用。经常会抛出这样的异常:
XMLHttpRequest cannot load http://www.othersite.com/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.mysite.com' is therefore not allowed access.
源(Origin)
唠叨了半天异源(异域),科学的解释一下什么是
源(Origin)。
公式:
Origin = [protocol]://[domain]:[port]
Origin是你的页面所在的位置。例如我有个站点
www.mysite.com,那么它的
源就是
http://www.mysite.com:80,其中
[protocol]的缺省值是
http,
[port]的缺省值是
80。
不管我的站点在哪个页面
www.mysite.com/p/1.html,只要
[protocol],
[domain],
[port]三者相同则视为
同源,或者叫
同域,通常称之为
同域,因为我们通常都是叫别人的小名
二狗子,而不会称呼其大名
犬次郎。
更多示例:
URL | Origin |
---|---|
http://www.mysite.com/p/1.html | http://www.mysite.com |
https://wwww.mysite.com/p/1.html | https://www.mysite.com |
http://app.mysite.com/p/1.thml | http://app.mysite.com |
http://www.mysite.net/p/1.html | http://www.mysite.net |
http://www.mysite.com:9000/p/1.html | http:www.mysite.com:9000 |
http://www.mysite.com/news/fresh.html | http://www.mysite.com |
解决访问跨域资源的问题
虽然是安全了,但是如果想从我的www.mysite.com去我的分站
son.mysite.com获取点东西也会被
同源策略禁止,这就不是我们想要的了,那怎么办呢?可以利用
<script>等不受
同源策略限制的
dom元素绕过去,这种方式称之为
jsonp,这是很多年前就提出来的方法,很巧妙不过很繁琐,渐渐地不怎么再使用了,有兴趣的自行
还有现代化的解决方案:
CORS(Cross-Origin Resource Sharing)。还记得
同源策略的规定吗?通过屏蔽响应结果的方式保证信息安全,也就是说浏览器并没有阻止发起跨域请求。浏览器如果在跨域请求的响应(
Http Response Headers)中发现了对方的许可就会认为是安全的。这套标准称之为CORS。
这套标准规定一系列的
Http Headers,让服务器申明哪些资源是可以被谁访问,浏览器通过解析响应头部就能知道是否得到了许可。
举两个简单的🌰栗子说明这一系列的
Http Headers:
在
www.mysite.com中有如下脚本,要去访问
www.othersite.com的资源。
<script> var request = new XMLHttpRequest(); var url = 'www.othersite.com/post/001/'; function callOtherDomain() { if(request) { request.open('GET', url, true); request.onreadystatechange = handler; request.send(); } } </script>
通过浏览器的控制台,查看到该请求,请求和响应报文的重要内容如下:
请求报文
GET /post/001 HTTP/1.1 Host: www.othersite.com 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: www.mysite.com Origin: http://www.mysite.com[/code] 响应报文HTTP/1.1 200 OK Date: Sat, 04 Mar 2017 14:23:53 GMT Access-Control-Allow-Origin: * Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked
这是一个简单的Get请求,在请求报文中有一个值得注意的Origin,这个属性就表示了当前所处的源,Origin是由浏览器自动控制的,不允许用户干预。如果同源策略判定请求是跨域请求,那么就会自动把Origin加入请求头部中。
在响应报文中,注意Access-Control-Allow-Origin属性,如果对方允许你跨域访问,那么它会在响应中加入你的请求头部中的Origin,此处的响应报文中的*表示允许任何请求的跨域访问。https://www.mysite.com要去修改https://posts.mysite.com中的一篇文章。var request = new XMLHttpRequest(); var url = 'posts.mysite.com/?pid=1024'; var body = 'new post'; function callOtherDomain(){ if(request) { request.open('PUT', url, true); request.setRequestHeader('userid', 'keke'); request.onreadystatechange = handler; request.send(body); } }
这次是发送了PUT类型的请求,要去修改pid=1024的文章,同时还携带了我的身份信息userid=keke在请求头部中。继续在浏览器的控制台中观察请求信息,发现有两次请求,第一个如下:OPTIONS /?pid=1024 Host: https://posts.mysite.com 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: https://www.mysite.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: userid HTTP/1.1 200 OK Date: Sat, 04 Mar 2017 14:35:39 GMT Access-Control-Allow-Origin: https://www.mysite.com Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT Access-Control-Allow-Headers: userid Access-Control-Max-Age: 1728000 Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
这是因为这次发送的不是简单请求,CORS规范要求先发个预检请求(Preflight),一般会采用OPTIONS类型,该请求不包含请求体,会携带一些用于探测的信息,除了Origin,还有* `Access-Control-Request-Method` * `Access-Control-Request-Headers`
前者用来携带真正的请求的类型,后者携带真实请求自定义的头。
在响应报文中还有Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT表示对方许可的请求类型。
对方会根据预检请求中的信息判断是否可以接受真正的请求,如果预检请求通过了,浏览器才会发起真正的请求。PUT /?pid=1024/ HTTP/1.1 Host: https://posts.mysite.com 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 userid: keke Content-Type: text/xml; charset=UTF-8 Referer: https://www.mysite.com Content-Length: 8 Origin: https://www.mysite.com Pragma: no-cache Cache-Control: no-cache …… HTTP/1.1 200 OK Date: Sat, 04 Mar 2017 14:35:39 GMT Access-Control-Allow-Origin: https://www.mysite.com Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 235 Keep-Alive: timeout=2, max=99 Connection: Keep-Alive ……
响应报文报文头部中带有Access-Control-Allow-Origin: https://www.mysite.com[/code],表示对方同意https://www.mysite.com的请求,浏览器就不会屏蔽响应结果。
上述栗子中,提到了CORS规定了对于不简单的请求类型,要先发一个预检请求(Preflight),那么简单与否的判定条件是什么呢?在如下范围内的请求都是被视为简单请求
请求类型的范围限制:GET,HEAD,POST
自定义的请求头部限制范围:Accept,Accept-Language,Content-Language
媒体类型(Content-Type)的限制范围的:application/x-www-form-urlencoded,multipart/form-data,text/plain
除来上述栗子中提到的几个头部,还有哪些呢?如下明细:HTTP请求头部
Origin: 表示发送请求者的源(域),浏览器控制的。Access-Control-Request-Method: 这是预检请求(Preflight)中表示真实请求的请求方式。Access-Control-Request-Headers: 这是预检请求(Preflight)中表示真实请求的自定义的头部,可以有多个(Access-Control-Request-Headers: userid, pwd, location:表示真实请求会携带3个自定义头部(userid,pwd,location))。HTTP响应头部
Access-Control-Allow-Origin: <origin> | *: origin参数表示对方允许访问的URI.对于一个不带有credentials的请求,可以指定为'*',表示允许来自所有域的请求。Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header: 设置允许的请求头部。Access-Control-Max-Age: <delta-seconds>: 这个头告诉我们这次预检请求的结果的有效期是多久,delta-seconds 参数表示,允许这个预检请求的参数缓存的秒数,在此期间,不用发出另一条预检请求。Access-Control-Allow-Credentials: true | false: 告知客户端,当请求的credientials属性是true的时候,响应是否可以被得到.当它作为预检请求的响应的一部分时,它用来告知实际的请求是否使用了credentials.注意,简单的GET请求不会预检,所以如果一个请求是为了得到一个带有credentials的资源,而响应里又没有Access-Control-Allow-Credentials头信息,那么说明这个响应被忽略了。Access-Control-Allow-Methods: <method>[, <method>]*: 这个响应头信息在客户端发出预检请求的时候会被返回,表示被允许的请求方式。Access-Control-Allow-Headers:<field-name>[, <field-name>]*: 也是在响应预检请求的时候使用。用来指明在实际的请求中,可以使用哪些自定义HTTP请求头。
对于Access-Control-Allow-Credentials这个头部只会出现在请求头部中包含了凭证(HttpCookie)信息。一般而言,对于跨站请求,浏览器是不会发送凭证信息的。但如果将XMLHttpRequest的一个特殊标志位设置为true,浏览器就将允许该请求的发送。var invocation = new XMLHttpRequest(); var url = 'http://www.othersite.com'; function callOtherDomain(){ if(invocation) { invocation.open('GET', url, true); invocation.withCredentials = true; invocation.onreadystatechange = handler; invocation.send(); }
再例如使用jQuery发起ajax请求时:$.ajax({ //... xhrFields: { withCredentials: true }, //... });
针对脚本(script)的跨域操作是安全了,可是如果通过<iframe>这种dom元素来嵌入资源的话,同源策略就无法保护我们了。针对
我们肯定不希望自己的页面被不法分子用<iframe>的安全策略<iframe src="www.mysite.com"></frame>等方式嵌入,然后被利用。浏览器们早已考虑到这个漏洞,并提出了解决方案。类似于CORS标准,解析响应头部中的X-Frame-Options,用这个头部信息来表达是否可以被对方嵌入。X-Frame-Options有三种值:X-Frame-Options: DENY X-Frame-Options: SAMEORIGIN X-Frame-Options: ALLOW-FROM https://www.othersite.com/[/code]DENY: 无论请求者是谁,都不允许嵌入。SAMEORIGIN: 只有同源(origin)的页面才可以嵌入。ALLO-From https://www.othersite.com/[/code]: 只有https://www.othersite.com/才可以嵌入咱们的页面。
例如,www.othersite.com要用如下代码嵌入我的页面www.mysite.com。... <iframe src="www.mysite.com"></iframe> ...
我给www.mysite.com的响应头部中加入X-Frame-Options: SAMEORIGIN,浏览器会屏蔽响应结果,并报错Refused to display 'http://www.mysite.com/' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'.
这些都是基本的网络安全规范,但是其重要性却不可忽略。面对红果果的互联网,时刻不能放松。
相关文章推荐
- Java安全编码:糟糕的在线建议和令人困惑的APIs
- 关于页面开发的安全,防止重复提交以及浏览器拦截策略
- 几个令人困惑的安全问题收藏
- 6、浏览器安全(同源策略-沙箱-拦截)
- linux新加坡专有网络部署tomcat以后外部浏览器无法访问--专线网络安全组策略
- 浏览器特性与安全策略
- 同源策略以及cookie安全策略
- 【每日安全资讯】夏威夷导弹警报系统有一个令人困惑的界面
- 浏览器安全之同源策略
- HSTS安全策略在浏览器中的应用
- CSP浏览器安全策略备忘
- 浏览器安全-同源策略
- Java安全编码:糟糕的在线建议和令人困惑的APIs
- DotNET应用架构设计指南(第三章:安全 运行管理和通讯策略(13-16))
- 关于浏览器同源策略的一些小疑惑
- IE 浏览器安全级别详情及区别小结
- 电脑出现“你不能访问此共享文件夹,因为你组织的安全策略阻止未经身份验证的来宾访问。”怎么办?
- 本地策略 域控制器策略 域安全策略
- 编写PHP的安全策略
- 服务器安全基本策略