web安全之同源策略
为什么使用同源策略?
一个重要原因就是对cookie的保护,cookie 中存着sessionID 。如果已经登录网站,同时又去了任意其他网站,该网站有恶意JS代码。如果没有同源策略,那么这个网站就能通过js 访问document.cookie 得到用户关于的各个网站的sessionID。其中可能有银行网站,通过已经建立好的session连接进行攻击,这里有一个专有名词,CSRF,还有需要注意的是同源策略无法完全防御CSRF,这里需要服务端配合。
什么是同源策略?
URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们是同源的。同源策略是浏览器上为安全性考虑实施的非常重要的安全策略。限制来自不同源的"document",对当前"document"读取或设置某些属性。 在浏览器中,<script>、<img>、<link>、<frame>等标签都可加载跨域资源,而不受同源策略限制,这带"src"属性的标签加载时,实际上是由浏览器发起一次GET请求,不同于XMLHttpRequest,他们通过src属性加载的资源。但浏览器限制了JavaScript的权限,使其不能读、写其中返回的内容。
跨域请求的安全基础是,JavaScript无法修改请求对象的http头部。 如果XMLHttpRequest能够跨域请求资源,可能导致敏感信息泄露,比如CSRF的token信息,
受同源策略限制的有哪些?
DOM、Cookie、XMLHttpRequest,还有一些第三方插件Flash、Java Applet、Sliverlight、Google Gears等都有自己的控制策略。
如何规避同源策略,即跨域请求?
document.domain属性
如果两个window或者frames包含的脚本可以把domain设置成一样的值,那么就可以规避同源策略,每个window之间可以互相沟通。例如,orders.example.com下页面的脚本和catalog.example.com下页面的脚本可以设置他们的document.domain属性为example.com,从而让这两个站点下面的文档看起来像在同源下,然后就可以让每个文档读取另一个文档的属性。这种方式也不是一直都有用,因为端口号是在内部保存的,有可能被保存成null。换句话说,example.com的端口号80,在我们更新document.domain属性的时候可能会变成null。为null的端口可能不被认为是80,这主要依赖浏览器实现。
跨域资源共享(CORS)
Cross-origin Resource Sharing跨资源共享,使用自定义的HTTP头部让浏览器与服务器沟通,从而决定请求和响应是否成功。这种方式使用了一个新的Origin请求头和一个新的Access-Control-Allow-Origin响应头扩展了HTTP。允许服务端设置Access-Control-Allow-Origin头标识哪些站点可以请求文件,或者设置Access-Control-Allow-Origin头为"*",允许任意站点访问文件。
浏览器,例如Firefox3.5,Safari4,IE10使用这个头允许跨域HTTP请求。
服务器端在HTTP的响应头中加入(页面层次的控制模式):
Access-Control-Allow-Origin: example.com
Access-Control-Request-Method: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization, Accept, Range, Origin
Access-Control-Expose-Headers: Content-Range
Access-Control-Max-Age: 3600
多个域名之间用逗号分隔,表示对所示域名提供跨域访问权限。"*"表示允许所有域名的跨域访问
客户端可以有两种行为:
1. 发送OPTIONS请求,请求Access-Control信息。如果自己的域名在允许的访问列表中,则发送真正的请求,否则放弃请求发送。
2. 直接发送请求,然后检查response的Access-Control信息,如果自己的域名在允许的访问列表中,则读取response body,否则放弃。 本质上服务端的response内容已经到达本地,JavaScript决定是否要去读取。
跨文档通信(window.postMessage方法)
这种方式允许一个页面的脚本发送文本信息到另一个页面的脚本中,不管脚本是否跨域。基本上,它就像是跨域的AJAX,但不是浏览器跟服务器之间交互,而是在两个客户端之间通信。在一个window对象上调用postMessage()会异步的触发window上的onmessage事件,然后触发定义好的事件处理方法。一个页面上的脚本仍然不能直接访问另外一个页面上的方法或者变量,但是他们可以安全的通过消息传递技术交流。
允许程序员跨域在两个窗口/frames间发送数据信息。
窗口A: 发送窗口使用postMessage发送数据 window.postMessage(msg,urlOfB); 窗口B: 接收端,监听“message”事件,
window.onmessage(event){ var data=event.data; var origin=event.origin; }
JSONP
JSONP利用<script>标签的跨域能力实现跨域数据的访问,请求动态生成的JavaScript脚本同时带一个callback函数名作为参数。其中callback函数本地文档的JavaScript函数,服务器端动态生成的脚本会产生数据,并在代码中以产生的数据为参数调用callback函数。当这段脚本加载到本地文档时,callback函数就被调用。
为了动态实现JSONP请求,可以使用Javascript动态插入<script>标签:
<script type="text/javascript"> // this shows dynamic script insertion var script = document.createElement('script'); script.setAttribute('src', url); // load the script document.getElementsByTagName('head')[0].appendChild(script); </script>
WebSocket
现代浏览器允许脚本直连一个WebSocket地址而不管同源策略。然而,使用WebSocket URI的时候,在请求中插入Origin头就可以标识脚本请求的源。为了确保跨站安全,WebSocket服务器必须根据允许接受请求的白名单中的源列表比较头数据。 与JSONP方法不同的是,该响应函数被传入到创建<script> 标签的构造函数中,检测到已经成功接受到收据的状态后再执行函数。
document.domain + iframe (只有在主域相同的时候才能使用该方法)
location.hash + iframe
window.name + iframepostMessage(HTML5中的XMLHttpRequest Level 2中的API)