了解什么是同源策略 (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/
一般来说,允许写入,拒绝读取。具体如何应用取决于浏览器功能,所以让我们看一些示例。
网站可以通过多种方式获取另一个窗口的句柄。但是,您可以通过使用COOP(跨域 Opener 策略)和CSP(内容安全策略)frame-ancestors
指令来限制这一点。
这些方法包括:
window.open
.window.opener
如果网站被另一个框架使用,则使用。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 = "
Hacked
"; - ❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.
- console.log(handle.frames.length);
- ✅ 2
☠ 安全影响:能够对帧进行计数,可以进行帧计数跨站点泄漏攻击。
- console.log(handle.location.href)
- ❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.
handle.location.replace('https://www.example.com')
☠ 安全影响:您在网站上构建的网站可以通过window.opener属性获得一个窗口句柄。这意味着,如果您在网站的 iframe 中加载恶意网站,该框架可以将您网站的 URI 更改为例如网络钓鱼页面(克隆您的页面,例如窃取用户密码或让他们下载恶意的东西)。您可以使用沙盒 iframe来防止这种情况。
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')
- 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 不能直接访问该资源。但是,您可以使用CORP(跨源资源策略)来限制这一点。
此外,当嵌入资源时,嵌入资源站点的浏览器用户 cookie 会与请求一起发送。实际上,这允许网站发送凭证(带有 cookie)的跨站点 GET 和 HEAD 请求。
☠ 安全影响:如果您的网站允许通过 GET 请求(例如,转账、更改密码、删除帐户)执行操作(例如,它,当然,不应该)。
让我们看几个跨站点资源的示例。
- 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.
这可以。样式将呈现在页面上。
- console.log(document.styleSheets[0].cssRules);
- ❌ Uncaught DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules
- at
:1:25
以下是 的内容test.js
:
var x = 5
没有办法获得脚本的来源。
脚本有x
变量,记得吗?我们现在可以在我们的页面上使用它。
- console.log(x)
- 5
这本质上就是JSONP的工作方式(不要再使用它了,这从来都不是一个好主意,而且现在我们有更好的方法,你马上就会看到)。
☠ 安全影响:如果您的网站提供包含经过身份验证的用户数据的动态 JavaScript 文件,浏览器允许访问跨域脚本提供的数据/功能这一事实会导致 XSSI(跨站点脚本包含)攻击。所以不要做那样的事情。
在上一节中,我们了解了嵌入跨域资源如何允许恶意网站代表浏览器用户发送有凭据的 GET 请求。现在您将看到 HTML 表单如何使发送有凭据的POST请求成为可能。
☠ 安全影响:这种行为是CSRF漏洞如此普遍的主要原因。幸运的是,随着默认情况下开始启用SameSite Cookie,情况终于会有所改善。
假设我们在 http://a.local 上有以下表单,并且用户在http://b.local上有一个活动会话:
-
-
-
当用户单击“发送”按钮时,会向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
不知情的网络应用程序会发送钱,认为请求来自用户。
可以毫无问题地提交跨域多部分表单。只需像这样添加enctype
参数:
-
-
-
为指定application/json将enctype
不起作用。浏览器将回退到application/x-www-form-urlencoded。
-
-
☠ 安全影响:如果应用程序未能正确验证内容类型,它可能会将这种 POST 请求解释为有效的 JSON。此外,还有一些关于实现的草案enctype="json"
,尽管目前没有浏览器这样做。出于这些原因,如果它们使用基于 cookie 的会话管理,那么对例如 REST API 以及传统 Web 应用程序实施 CSRF 保护至关重要。
以下将起作用。你会得到一个错误,但是请求会被发送。您可以使用浏览器的开发人员工具进行验证,或者更好的是,在您的浏览器和网络服务器之间设置一个代理工具,例如OWASP ZAP,以真正了解发生了什么。
- let xhr = new XMLHttpRequest()
- xhr.withCredentials = true
- xhr.open('GET', 'http://b.local/')
- xhr.send()
使用 fetch 也一样。
fetch('http://b.local/', { method: 'POST', credentials: 'include' })
使用 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
在一分钟内了解这件事是什么。
默认情况下只允许特定的 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.
仅允许列入白名单的内容类型。这行不通。
- 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,是一种网站以受控方式部分退出同源策略的机制。
例如,如果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。
让我们回顾一下我们之前所做的一项测试,但这一次,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('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);
- });
-
-
-
-
b.local - ...
☠ 安全影响:如果您指定这样的 CORS 标头,您将给予允许的来源完全控制您的网站,包括任何经过身份验证的用户数据和功能。同源政策可以保护您,因此在选择退出之前请仔细考虑。
这可能令人惊讶,但同源策略并没有限制 WebSockets。
☠ 安全影响:如果使用 WebSockets 的应用程序没有验证Origin
WebSocket 握手中的 header 或执行其他一些 CSRF 保护机制,则恶意网站可能会打开 WebSocket 连接并将其用作浏览器用户。
同源策略是 Web 浏览器安全模型的根源。它很旧,而且并不完美。因此,开发人员必须了解风险并在其应用程序中实施适当的防御措施。
通常,允许写入(例如,发送跨域 POST 请求),但不允许读取(例如,读取对这些请求的响应)。这意味着如果没有 CSRF 保护,网站就会陷入困境。
开发人员可以使用 CORS(跨源资源共享)标头部分放宽同源策略,但他们应该谨慎这样做,并尽可能完全避免使用 CORS。
在一些较新的浏览器中,同源策略也可以通过 CORP(跨域资源策略)和 COOP(跨域开放器策略)变得更严格。
最后,有点令人惊讶的是,WebSockets 根本不受同源策略的保护。如果您在实施时不小心,这可能会产生令人惊讶和不愉快的影响。