同源政策 (SOP)

了解什么是同源策略 (SOP),以及它对 Web 开发人员的意义。

什么是同源策略?

同源策略是一组设计原则,用于管理 Web 浏览器功能的实现方式。

其目的是将浏览器窗口(和选项卡)相互隔离,例如,当您访问 example.com 时,该网站将无法读取您来自 gmail.com 的电子邮件,而您可能在另一个网站上打开了这些电子邮件标签。

什么是起源?

起源的定义很简单。如果两个网站的方案(http://、https:// 等)、主机(例如www.appsecmonkey.com)和端口(例如 443)相同,则它们的来源相同。您可以在RFC6545 - The Web Origin Concept中找到定义。

 

隐式端口

如果未明确指定端口,则默认为 80http和 443 https

例子

这些 URI 被认为是同源的:

  • https://www.appsecmonkey.com/
  • https://www.appsecmonkey.com/blog/same-origin-policy/
  • https://www.appsecmonkey.com:443/blog/same-origin-policy/

这些都是不同的起源:

  • http://www.appsecmonkey.com/
  • https://appsecmonkey.org/
  • https://www.appsecmonkey.com:8080/

同源策略允许什么,不允许什么?

一般来说,允许写入,拒绝读取。具体如何应用取决于浏览器功能,所以让我们看一些示例。

JavaScript 窗口访问

网站可以通过多种方式获取另一个窗口的句柄。但是,您可以通过使用COOP(跨域 Opener 策略)CSP(内容安全策略)frame-ancestors指令来限制这一点。

这些方法包括:

  • 使用window.open.
  • 创建一个框架(就像我们即将要做的那样)。
  • window.opener如果网站被另一个框架使用,则使用。
  • 收到 postMessage event.source

window此句柄提供对和location对象的精简版本的访问。

让我们在http://a.local上进行一些实验。我们将首先通过创建一个跨域框架来获取http://b.local的窗口句柄,如下所示:

var crossOriginFrame = document.createElement('iframe')
crossOriginFrame.src = 'http://b.local'
document.body.appendChild(crossOriginFrame)
var handle = crossOriginFrame.contentWindow

现在让我们看看我们可以用它做什么。

❌ 不允许:读取跨域内容

console.log(handle.document.body.innerHTML);
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.

❌ 不允许:写跨域内容

handle.document.body.innerHTML = "<h1>Hacked</h1>";
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.

✅ 允许:读取跨域窗口内的帧数

console.log(handle.frames.length);
✅ 2

☠ 安全影响:能够对帧进行计数,可以进行帧计数跨站点泄漏攻击。

❌ 不允许:读取跨域 URI

console.log(handle.location.href)
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.

✅ 允许:编写跨域 URI

handle.location.replace('https://www.example.com')

 

☠ 安全影响:您在网站上构建的网站可以通过window.opener属性获得一个窗口句柄。这意味着,如果您在网站的 iframe 中加载恶意网站,该框架可以将您网站的 URI 更改为例如网络钓鱼页面(克隆您的页面,例如窃取用户密码或让他们下载恶意的东西)。您可以使用沙盒 iframe来防止这种情况。

✅ 允许:通过 postMessage 向窗口发送消息

postMessage方法允许跨域窗口相互通信。

// on http://b.local/
window.addEventListener(
  'message',
  (event) => {
    document.write('Got message: ' + event.data)
  },
  false
)

// on http://a.local/
handle.postMessage('hello', 'http://b.local')

 

❌ 不允许:读取 localStorage 或 sessionStorage

console.log(handle.localStorage);
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.

console.log(handle.sessionStorage);
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.

资源嵌入和 JavaScript 访问

一般来说,嵌入任何资源(图像、样式、脚本等)都是允许跨域的,但 JavaScript 不能直接访问该资源。但是,您可以使用CORP(跨源资源策略)来限制这一点。

此外,当嵌入资源时,嵌入资源站点的浏览器用户 cookie 会与请求一起发送。实际上,这允许网站发送凭证(带有 cookie)的跨站点 GET 和 HEAD 请求。

☠ 安全影响:如果您的网站允许通过 GET 请求(例如,转账、更改密码、删除帐户)执行操作(例如,它,当然,不应该)。

让我们看几个跨站点资源的示例。

✅ 允许:显示图像

<img id="cross-origin-image" src="http://b.local/monkey.png" />

✅ 允许:从图像创建画布

var crossOriginImage = document.getElementById('cross-origin-image')
var canvas = document.createElement('canvas')
canvas.width = crossOriginImage.width
canvas.height = crossOriginImage.height
canvas
  .getContext('2d')
  .drawImage(crossOriginImage, 0, 0, crossOriginImage.width, crossOriginImage.height)
document.body.appendChild(canvas)

 

❌ 不允许:从画布中读取像素

canvas.getContext('2d').getImageData(1, 1, 1, 1).data;
❌ Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.

✅ 允许:加载样式

这可以。样式将呈现在页面上。

<link rel="stylesheet" href="http://b.local/test.css"/

❌ 不允许:阅读样式内容

console.log(document.styleSheets[0].cssRules);
❌ Uncaught DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules
    at <anonymous>:1:25

✅ 允许:加载脚本

<script id="cross-origin-script" src="http://b.local/test.js"></script>

以下是 的内容test.js

var x = 5

❌ 不允许:阅读脚本源

没有办法获得脚本的来源。

✅ 允许:访问脚本提供的数据和功能

脚本有x变量,记得吗?我们现在可以在我们的页面上使用它。

console.log(x)
5

这本质上就是JSONP的工作方式(不要再使用它了,这从来都不是一个好主意,而且现在我们有更好的方法,你马上就会看到)。

☠ 安全影响:如果您的网站提供包含经过身份验证的用户数据的动态 JavaScript 文件,浏览器允许访问跨域脚本提供的数据/功能这一事实会导致 XSSI(跨站点脚本包含)攻击。所以不要做那样的事情。

HTML 表单

在上一节中,我们了解了嵌入跨域资源如何允许恶意网站代表浏览器用户发送有凭据的 GET 请求。现在您将看到 HTML 表单如何使发送有凭据的POST请求成为可能。

☠ 安全影响:这种行为是CSRF漏洞如此普遍的主要原因。幸运的是,随着默认情况下开始启用SameSite Cookie,情况终于会有所改善。

✅ 允许:提交经过认证的跨域 urlencoded HTML 表单

假设我们在 http://a.local 上有以下表单,并且用户在http://b.local上有一个活动会话:

<form method="POST" action="http://b.local/transferFunds">
  <input name="amount" type="text" value="10000" />
  <input name="iban" type="text" value="HACKERBANK1337" />
  <input type="submit" value="Send" />
</form>

当用户单击“发送”按钮时,会向http://b.local发送这样的 HTTP 请求:

POST /transferFunds HTTP/1.1
Host: b.local
Cookie: SESSIONID=s3cr3t
Content-Type: application/x-www-form-urlencoded
...
amount=10000&iban=HACKERBANK1337

不知情的网络应用程序会发送钱,认为请求来自用户。

✅ 允许:提交有凭证的跨域多部分 HTML 表单

可以毫无问题地提交跨域多部分表单。只需像这样添加enctype参数:

<form method="POST" action="http://b.local/transferFunds" enctype="multipart/form-data">
  <input name="amount" type="text" value="10000" />
  <input name="iban" type="text" value="HACKERBANK1337" />
  <input type="submit" value="Send" />
</form>

❌ 不允许:提交有凭证的跨域 JSON HTML 表单

为指定application/jsonenctype不起作用。浏览器将回退到application/x-www-form-urlencoded

<form method="POST" action="http://b.local/transferFunds" enctype="application/json">
  <input name="amount" type="text" value='{"amount": 1000, "iban": "HACKERBANK1337", "foo": "' />
  <input name="amount" type="text" value='bar"}' />
</form>

☠ 安全影响:如果应用程序未能正确验证内容类型,它可能会将这种 POST 请求解释为有效的 JSON。此外,还有一些关于实现的草案enctype="json",尽管目前没有浏览器这样做。出于这些原因,如果它们使用基于 cookie 的会话管理,那么对例如 REST API 以及传统 Web 应用程序实施 CSRF 保护至关重要。

XHR 和 Fetch 请求

✅ 允许:使用 XHR 发送有凭证的跨域 GET、HEAD 和 POST 请求

以下将起作用。你会得到一个错误,但是请求会被发送。您可以使用浏览器的开发人员工具进行验证,或者更好的是,在您的浏览器和网络服务器之间设置一个代理工具,例如OWASP ZAP,以真正了解发生了什么。

let xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.open('GET', 'http://b.local/')
xhr.send()

✅ 允许:使用 fetch 发送有凭证的跨域 GET、HEAD 和 POST 请求

使用 fetch 也一样。

fetch('http://b.local/', { method: 'POST', credentials: 'include' })

❌ 不允许:检查 XHR 响应

使用 XHR 或 fetch,您将无法读取您获得的响应。

❌ Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://b.local/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

我会Access-Control-Allow-Origin在一分钟内了解这件事是什么。

❌ 不允许:发送 PUT、PATCH、DELETE 等请求

默认情况下只允许特定的 HTTP 动词(GET、POST、HEAD 和 OPTIONS)。

fetch('http://b.local/', {method: 'PUT', credentials: 'include'});
❌ Access to fetch at 'http://b.local/' from origin 'http://a.local' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

这个错误有两个有趣的部分。它谈论的是预检请求和请求模式。我很快就会进行预检,但让我们先快速了解一下请求模式。

请求模式

Web 应用程序可以使用请求模式来防止意外泄漏请求中的不必要数据,例如,将模式显式设置为同源。

但是,它不能用于绕过任何安全控制。例如,如果我们将模式更改为错误消息中描述的“no-cors”,则仍然不会发送 PUT 请求;它只会导致不同的错误。

fetch('http://b.local/', {method: 'PUT', credentials: 'include', mode: 'no-cors'});
❌ Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': 'PUT' is unsupported in no-cors mode.

❌ 不允许:发送 JSON 请求

仅允许列入白名单的内容类型。这行不通。

fetch('http://b.local/', {
    method: 'POST', credentials: 'include', headers: {
        'Content-Type': 'application/json'
    },
});
❌ Access to fetch at 'http://b.local/' from origin 'http://a.local' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

再次与预检。好吧,现在我会告诉你这些Access-Control-Allow-东西是什么。

跨域资源共享 (CORS)

跨域资源共享,简称 CORS,是一种网站以受控方式部分退出同源策略的机制。

访问控制允许来源

例如,如果http://b.local 希望 http://a.local能够通过 fetch/XHR 响应读取其内容,则通过在 HTTP 响应中指定 CORS 标头,它可以这样做。

Access-Control-Allow-Origin: http://a.local/

您也可以使用通配符作为来源,但Access-Control-Allow-Credentials(下)不能是true. 此外,通配符不能包含任何其他文本,因此* .appsecmonkey.com 不起作用。要么全有,要么全无,一个完整的通配符或一个确切的起源。

访问控制允许凭据

默认情况下,CORS 不允许凭据请求(包括浏览器用户的 cookie)。毕竟,经过认证的 CORS 请求有效地赋予了被授予权限的网站对应用程序中浏览器用户数据的完全读写控制。

如果您仍想启用它,您可以Access-Control-Allow-Credentials像这样使用标头:

Access-Control-Allow-Credentials: true

访问控制允许标头

然后,如果您想允许 JSON 请求或其他未列入白名单的标头/值,您可以通过Access-Control-Allow-Headers标头执行此操作:

Access-Control-Allow-Headers: content-type

访问控制允许方法

最后,如果要启用 GET、POST、HEAD 和 OPTIONS 之外的其他 HTTP 动词,则必须使用Access-Control-Allow-Methods标头:

Access-Control-Allow-Methods: GET, POST, HEAD, PUT, PATCH, DELETE

事实上,即使你只想允许,例如 POST 请求,Access-Control-Allow-Methods如果有任何其他因素导致你的请求被预检,你仍然需要返回,我们将在下面讨论。

预检

始终发送带有列入白名单的 HTTP 动词、标头和内容类型的简单请求。尽管如此,如果响应不包含适当的Access-Control-Allow-Origin标头,则禁止网站访问响应数据。

但是浏览器如何知道是否允许发送 PUT 请求呢?如果“我可以发送 PUT 请求”这个问题的答案是对 PUT 请求的响应,那么这不会造成先有鸡还是先有蛋的问题吗?这是一个很好的问题,答案很简单:我们发送两个请求。

浏览器首先发送一个OPTIONS请求,然后查看该请求的响应标头。如果允许(在 中),则仅发送实际的 PUT 请求。PUTAccess-Control-Allow-Methods

第一个 OPTIONS 请求被恰当地命名为preflight request

CORS 在行动

让我们回顾一下我们之前所做的一项测试,但这一次,http://b.local/返回以下 HTTP 响应标头:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: GET, HEAD, POST, PUT
Access-Control-Allow-Origin: http://a.local

✅ 允许:使用 fetch 和读取响应发送凭据的跨域 GET、HEAD、POST 和 PUT 请求

现在我们已经完全控制了跨域页面。

fetch('http://b.local', {method: 'PUT', credentials: 'include', headers: {
        'Content-Type': 'application/json'
    }}).then(function (response) {
    return response.text();
}).then(function (html) {
    // This is the HTML from our response as a text string
    console.log(html);
});

<!doctype html>
<html lang=en>

<head>
    <meta charset=utf-8>
    <title>b.local</title>
    ...

☠ 安全影响:如果您指定这样的 CORS 标头,您将给予允许的来源完全控制您的网站,包括任何经过身份验证的用户数据和功能。同源政策可以保护您,因此在选择退出之前请仔细考虑。

网络套接字

✅ 允许:打开一个跨域 WebSocket 连接,从中读取和写入

这可能令人惊讶,但同源策略并没有限制 WebSockets。

☠ 安全影响:如果使用 WebSockets 的应用程序没有验证OriginWebSocket 握手中的 header 或执行其他一些 CSRF 保护机制,则恶意网站可能会打开 WebSocket 连接并将其用作浏览器用户。

结论

同源策略是 Web 浏览器安全模型的根源。它很旧,而且并不完美。因此,开发人员必须了解风险并在其应用程序中实施适当的防御措施。

通常,允许写入(例如,发送跨域 POST 请求),但不允许读取(例如,读取对这些请求的响应)。这意味着如果没有 CSRF 保护,网站就会陷入困境。

开发人员可以使用 CORS(跨源资源共享)标头部分放宽同源策略,但他们应该谨慎这样做,并尽可能完全避免使用 CORS。

在一些较新的浏览器中,同源策略也可以通过 CORP(跨域资源策略)和 COOP(跨域开放器策略)变得更严格。

最后,有点令人惊讶的是,WebSockets 根本不受同源策略的保护。如果您在实施时不小心,这可能会产生令人惊讶和不愉快的影响。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值