同源策略与跨域请求

同源策略与跨域请求

Source & Reference

教程视频:JSONP+CORS+服务器代理

参考文档:

  1. 同源策略(没有找到文档出处)
  2. 同源策略与跨域请求(作者:晚风)

0x00 SOP的介绍

同源策略(Same origin policy)是一种约定,是浏览器最核心也是最基本的功能,可以说WEB是构建在同源策略的基础上的

同源策略的核心就是:它认为来自任何站点装载的信赖内容都是不安全的,当被浏览器半信半疑的脚本运行在沙箱时,它们只被允许访问来自同⼀站点的资源,而不是那些来自其它站点可能怀有恶意的资源。

简单来说,就是只有IP、协议、端口相同的页面才会被认为是同源的,而只有被认为同源的页面才不会被跨域限制

image-20220704171215062

另外,同源策略(跨域限制)又分为两种:

  • DOM同源策略:禁止不同源的页面DOM进行操作,主要针对iframe跨域的情况
  • XMLHttpRequest同源策略:禁止使用XHR对象向不同源的服务器发起HTTP请求

我们换一个角度看待问题,可以看作一些操作受到同源策略的限制

  1. 无法读取非同源页面的Cookie、sessionStorage、localStorage、IndexedDB
  2. 无法读写非同源网页的DOM
  3. 无法向非同源地址发送AJAX请求:可以发送,但会报错

我们现在已经了解了同源策略的限制,接下来我们需要知晓跨域限制的必要性

  • 如果没有DOM同源策略:hacker可以制作假网站,诱导用户登录,登录完成后直接用其登录的数据访问真实网站,这样就可以在不被用户察觉的情况下拿到用户的账密
  • 如果没有XMLHttpRequest同源策略:hacker构造的恶意网站,可以直接使用用户登录其它网站的凭证进行操作,即可以轻易的进行CSRF攻击

0x01 前端跨域方法

业务环境种不可避免的会出现一些跨域的需求,如:电商网站想通过用户浏览器加载第三方快递网站的物流信息

① - JSONP跨域

原理:由于<script>不受浏览器同源策略的影响,允许跨域调用资源,因此可以通过动态创建script标签,然后利用src属性进行跨域

image-20220704213356904

有点难理解,我们直接上源码,再理解不了,就去看视频

流程示例

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
<script>
    function test(data) {
        alert(data);
    }
</script>

<script src="http://localhost/temp/index.php?callback=test"></script>
</body>
</html>
<?php
    header("Content-type: application/json");
    $callbackGet = $_GET['callback'];
    $data = 'Hi, H1kki~';
    echo $callbackGet . "('$data')";

image-20220704214044619

  1. HTML页面通过<script>的src属性访问远程PHP脚本,并将需要调用的JS函数名作为参数传递
  2. PHP脚本在接收到GET传参之后,构造输出需要调用的JS函数,其中函数的传参可以是PHP脚本中的数据
  3. HTML页面在收到PHP的输出值后,将其作为JS脚本数据解析执行,即成功将远程PHP脚本的数据作为JS脚本函数的传参来执行
② - HTML标签跨域

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

常见标签

<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>

流程实例

var img = new Image();

// 通过 onload 及 onerror 事件可以知道响应是什么时候接收到的,但是不能获取响应⽂本
img.onload = img.onerror = function() {
	console.log("Done!") ;
}

// 请求数据通过查询字符串形式发送
img.src = 'http://www.xxxx.cn/test?name=xxx';
③ - document.domain 跨域

局限性:适用于顶级域名相同的两个页面中的跨域访问,常用于iframe跨域的情况

document.domain:只能设置为以当前页面为子域的主域,默认为当前页面的域名。当且仅当两个页面的document.domain一致时,它们可以进行相互访问。

示例

<iframe src="http://localhost:81/b.php" i d="iframepage" width="100%"
height="100%" frameborder="0" scrolling="yes" onLoad="getData"> </iframe>
<script>
     window.parentDate = {
     "name": "hello world!",
     "age": 1 8
     }
     // 使⽤document.domain解决iframe⽗⼦模块跨域的问题
     let parentDomain = window.location.hostname;
     console.log("domain",parentDomain); //localhost
     document.domain = parentDomain;
</script>
④ - window.name 跨域

window.name:在JS的window对象中有一个name属性,该属性有一个特征:即一个窗口(window)的生命周期内,窗口载入的所有页面(无论同不同域)都是共享一个window.name的,并且每个页面都对window.name具有读写权限,且window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置

示例

<!-- http://localhost/temp/a.html -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<iframe src="http://localhost/temp/b.html" id="target" onload="test()" style="display: none"></iframe>
<script>
    function test() {
        const iframe = document.getElementById("target");
        const data = iframe.contentWindow.name;
        alert(data);
    }
</script>
</body>
</html>
<!-- http://localhost/temp/b.html -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<script>
    window.name = "hello, h1kki~"
</script>
</body>
</html>

image-20220704230928207

Tips:可以利用window.name来缩短任意长度的XSS

⑤ - window.postMessage 跨域

window.postMessage :是HTML5时代新出现的API,用于安全的进行跨域请求,实现不同页面中的跨域通信

示例

<!-- http://localhost/temp/a.html -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<iframe src="http://localhost/temp/b.html" id="target" onload="test()" style="display: none"></iframe>
<script>
    function test() {
        const iframe = document.getElementById("target");
        const win = iframe.contentWindow;
        // postMessage(需要传递的消息, 限定接收消息的window对象所在的域)
        win.postMessage("Hi, I'm a.html !", '*');
    }
</script>
</body>
</html>
<!-- http://localhost/temp/b.html -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<script>
    window.onmessage = function (e) {
        e = e || event;             // 获取事件对象
        if (e.origin !== 'http://localhost') {
            console.log('origin error!');
            return;
        }
        console.log(e.data);        // 通过 data 属性得到发送来的消息
    }
</script>
</body>
</html>

image-20220704232818672

⑥ - location.hash 跨域

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

局限:传递的数据的字节数是有限的

<!-- http://localhost/temp/a.html -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<iframe src="http://localhost/temp/b.html" id="target" onload="test()" style="display: none"></iframe>
<script>
    function test() {
        const data = window.location.hash;
        alert(data);
    }
</script>
</body>
</html>
<!-- http://localhost/temp/b.html -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<script>
    parent.location.hash = "hello, h1kki~"
</script>
</body>
</html>

image-20220704233355073

⑦ - 浏览器 SOP Bug

虽然所有的浏览器都有同源策略,但是各家浏览器实现的方式也是各不相同。难免实现也会有漏洞。我 们可以找出浏览器同源策略的漏洞来实现跨域访问。

例如,浏览器对CSS的松散解析就会导致跨域bug的出现,详见 → 9877 - Security: cross domain thefts via CSS string property injection - chromium

0x02 后端跨域方法

① - 服务器代理

浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所有域的资源再返回给客户端。

② - CORS(跨域资源共享)

CORS是一个W3C标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。其原理为:使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是否成功。

实现CORS的关键是Server端,只要Server实现了CORS接口,就可以跨源通信;而浏览器会自动完成整个CORS过程,不需要用户参与

浏览器将CORS的请求分为两类——

如果满足以下两个条件,就属于简单请求:
1. 请求方法为这三种之一: HEAD, GET, POST
2. HTTP头部字段不超出如下范围
	* Accept
	* Accept-Language
	* Content-Language
	* Last-Event-ID
	* Content-Type: application/x-www-form-urlencoded、multipart/form-data、text/plain
否则就属于非简单请求

简单请求

在请求中需要附加⼀个额外的 Origin 头部,其中包含请求页面的源信息(协议、端口、域名),Server通过这个字段信息来决定是否给予响应 —— 如果Server决定响应,则就在Access-Control-Allow-Origin头部中回发相同的源信息(*为公共资源);反之,浏览器驳回请求

非简单请求

浏览器在发送真正的请求之前,会先发送⼀个 Preflight 请求给服务器,这种请求使用OPTIONS方法,发送下列头部:

  • Origin:请求页面的源信息
  • Access-Control-Request-Method:请求自身使用的方法
  • Access-Control-Request-Headers[可选]:自定义的头部信息,多个头部以逗号分隔
# 示例
Origin: http://www.xxx.cn
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ

发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览 器进行沟通:

  • Access-Control-Allow-Origin:回发相同的源信息(*为公共资源)
  • Access-Control-Allow-Methods:允许的⽅法,多个⽅法以逗号分隔
  • Access-Control-Allow-Headers:允许的头部,多个⽅法以逗号分隔
  • Access-Control-Max-Age:应该将这个Preflight请求缓存多长时间(s)
  • Access-Control-Allow-Credentials:是否允许请求带有验证信息
  • Access-Control-Expose-Headers:允许脚本访问的返回头,请求成功后,脚本可以在 XMLHttpRequest 中访问这些头信息
# 示例
Access-Control-Allow-Origin: http://www.xxx.cn
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

⼀旦服务器通过 Preflight 请求允许该请求之后,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样了

③ - flash

Flash有自己的一套安全策略,服务器可以通过crossdomain.xml文件来声明能被哪些域的SWF文件访问,SWF也可以通过API确定自身能够被哪些域的SWF加载。

简单来说,如果 A → B,需要满足如下条件——

  1. B站下的crossdomain.xml文件存在
  2. B站下的crossdomain.xml中设置了允许A站访问

接下来举一个公共资源的crossdomain.xml示例

<?xml version="1.0"?>
<cross-domain-policy>
 <allow-access-from domain="*" / >
</cross-domain-policy>

如果不想域内的⽂件被其他任何域都能访问到,那么这种做法是不推荐的。正确的做法应该是明确指定本域内的⽂件能被哪些域访问。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值