詳解XMLHttpRequest的跨域資源共享

wyzsk發表於2020-08-19
作者: nyannyannyan · 2013/07/09 11:49

0x00 背景


Browser Security-同源策略、偽URL的域這篇文章中提到了瀏覽器的同源策略,其中提到了XMLHttpRequest嚴格遵守同源策略,非同源不可請求。但是,在實踐當中,經常會出現需要跨域請求資源的情況,比較典型的例如某個子域名向負責進行使用者驗證的子域名請求使用者資訊等應用。

以往,有一種解決方案是利用JSONP進行跨域的資源請求,但這種方案存在著幾點缺陷:

1)只能進行GET請求
2)缺乏有效的錯誤捕獲機制。因此在XMLHttpRequest v2標準下,提出了CORS(Cross Origin Resourse-Sharing)的模型,試圖保證跨域請求的安全性。

現在,各大主流瀏覽器均支援CORS模型,其中IE 8, IE 9需要使用XDomainRequest進行跨域請求。

0x01 基本模型


雖然說允許了XMLHttpRequest的跨域請求,但是這種許可並不是無條件的。

瀏覽器在進行請求時也會判斷請求的合法性,驗證是透過伺服器的返回頭來進行的,這時就涉及到了具體的請求型別,所以在標準中定義了簡單跨域請求。

簡單跨域請求就是滿足下列條件的請求:

請求方法為GET或POST
請求方法中沒有設定請求頭(Accept, Accept-Language, Content-Language, Content-Type除外)
如果設定了Content-Type頭,其值為application/x-www-form-urlencoded, multipart/form-data或 text/plain

驗證簡單跨域請求與非簡單跨域請求合法性的區別就在於,驗證非簡單跨域請求前,瀏覽器向伺服器傳送一個OPTIONS方法的預檢請求來加以判斷,如果預檢失敗,實際請求將被丟棄。而簡單跨域請求瀏覽器會正常傳送請求,再對返回頭加以判斷以檢查請求的合法性。在檢查失敗時,瀏覽器將會阻止指令碼對返回內容的訪問。

http://zone.wooyun.org/content/4917中,LZ因為設定了X-Forward-For頭,所以瀏覽器先傳送了預檢請求,檢查失敗後丟棄了實際頭,所以造成了LZ的誤解。

0x02 請求時傳送的HTTP頭


簡單跨域請求並不會包含下面的HTTP頭。而預檢請求將會傳送以下HTTP頭

Origin: 普通的HTTP請求也會帶有,在CORS中專門作為Origin資訊供後端比對
Access-Control-Request-Method: 接下來請求的方法,例如PUT, DELETE等等
Access-Control-Request-Headers: 自定義的頭部,所有用setRequestHeader方法設定的頭部都將會以逗號隔開的形式包含在這個頭中

其他頭,例如實際請求的頭部,Cookie頭等都將不被包含在預檢請求中。

0x03 返回的HTTP頭


瀏覽器主要透過返回的這些HTTP頭判斷請求是否合法。值得注意的一點是,預檢請求透過並不代表請求一定會成功,如果預檢請求時伺服器返回的HTTP頭使瀏覽器判斷請求合法,從而發出了實際請求,但是實際請求的返回頭中含有的訪問控制頭顯示請求不合法時,瀏覽器仍會判定請求不合法,從而向指令碼隱藏返回的細節。

Access-Control-Allow-Origin: 允許跨域訪問的域,可以是一個域的列表,也可以是萬用字元"*"。這裡要注意Origin規則只對域名有效,並不會對子目錄有效。即http://foo.example/subdir/是無效的。但是不同子域名需要分開設定,這裡的規則可以參照那篇同源策略
Access-Control-Allow-Credentials: 是否允許請求帶有驗證資訊,這部分將會在下面詳細解釋
Access-Control-Expose-Headers: 允許指令碼訪問的返回頭,請求成功後,指令碼可以在XMLHttpRequest中訪問這些頭的資訊(貌似webkit沒有實現這個)
Access-Control-Max-Age: 快取此次請求的秒數。在這個時間範圍內,所有同型別的請求都將不再傳送預檢請求而是直接使用此次返回的頭作為判斷依據,非常有用,大幅最佳化請求次數
Access-Control-Allow-Methods: 允許使用的請求方法,以逗號隔開
Access-Control-Allow-Headers: 允許自定義的頭部,以逗號隔開,大小寫不敏感

無論是預檢請求或是實際請求,如果在Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Allow-Methods, Access-Control-Allow-Headers的檢查失敗,就會被視為請求失敗。

0x04 單獨談談Credentials


在跨域請求中,預設情況下,HTTP Authentication資訊,Cookie頭以及使用者的SSL證書無論在預檢請求中或是在實際請求都是不會被髮送的。

但是,透過設定XMLHttpRequest的credentials為true,就會啟用認證資訊機制。

雖然簡單請求還是不需要傳送預檢請求,但是此時判斷請求是否成功需要額外判斷Access-Control-Allow-Credentials,如果Access-Control-Allow-Credentials為false,請求失敗。

十分需要注意的的一點就是此時Access-Control-Allow-Origin不能為萬用字元"*"(真是便宜了一幫偷懶的程式設計師),如果Access-Control-Allow-Origin是萬用字元"*"的話,仍將認為請求失敗

即便是失敗的請求,如果返回頭中有Set-Cookie的頭,瀏覽器還是會照常設定Cookie

0x05 CORS中的安全隱患


最大的隱患就在於某些偷懶的程式設計師會將Access-Control-Allow-Origin設定為"*"從而使得這個CORS模型基本失效,但是由於Credentials模型的保護,很多網上的文章認為的資訊洩露問題其實並不存在。在這裡的風險其實是可以構造DDoS進行攻擊。

另外就是我們會發現,雖然無法得到返回值,簡單請求其實是發出的,所以POST請求是可以發出的。這時候,其實就和平常的CSRF一樣了,所以並不是說保證了跨域請求限定的域就可以不做CSRF防範了。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章