1. 程式人生 > >跨域資源共享(CORS)安全性淺析

跨域資源共享(CORS)安全性淺析

0×00 背景

提起瀏覽器的同源策略,大家都很熟悉。不同域的客戶端指令碼不能讀寫對方的資源。但是實踐中有一些場景需要跨域的讀寫,所以出現了一些hack的方式來跨域。比如在同域內做一個代理,JSON-P等。但這些方式都存在缺陷,無法完美的實現跨域讀寫。所以在XMLHttpRequest v2標準下,提出了CORS(Cross Origin Resourse-Sharing)的模型,試圖提供安全方便的跨域讀寫資源。目前主流瀏覽器均支援CORS。

0×01 技術原理

CORS定義了兩種跨域請求,簡單跨域請求和非簡單跨域請求。當一個跨域請求傳送簡單跨域請求包括:請求方法為HEAD,GET,POST;請求頭只有4個欄位,Accept,Accept-Language,Content-Language,Last-Event-ID;如果設定了Content-Type,則其值只能是application/x-www-form-urlencoded,multipart/form-data,text/plain。說起來比較彆扭,簡單的意思就是設定了一個白名單,符合這個條件的才是簡單請求。其他不符合的都是非簡單請求。

img

<img title="cors1.png" alt="img" src="http://image.3001.net/images/20131126/13854564871898.png%21small"/></p>

之所以有這個分類是因為瀏覽器對簡單請求和非簡單請求的處理機制是不一樣的。當我們需要傳送一個跨域請求的時候,瀏覽器會首先檢查這個請求,如果它符合上面所述的簡單跨域請求,瀏覽器就會立刻傳送這個請求。如果瀏覽器檢查之後發現這是一個非簡單請求,比如請求頭含有X-Forwarded-For欄位。這時候瀏覽器不會馬上傳送這個請求,而是有一個preflight,跟伺服器驗證的過程。瀏覽器先發送一個options方法的預檢請求。下圖是一個示例。如果預檢通過,則傳送這個請求,否則就不拒絕傳送這個跨域請求。

1385456488282.png%21small

<img src="http://image.3001.net/images/20131126/1385456488282.png%21small" style="float:none" title="cors2.png"/></p>

下面詳細分析一下實現安全跨域請求的控制方式。先看一下非簡單請求的預檢過程。瀏覽器先發送一個options方法的請求。帶有如下欄位:

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

然後如果伺服器配置了cors,會返回對應對的欄位,具體欄位含義在返回結果是一併解釋。

Access-Control-Allow-Origin: 
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:

然後瀏覽器再根據伺服器的返回值判斷是否傳送非簡單請求。簡單請求前面講過是直接傳送,只是多加一個origin欄位表明跨域請求的來源。然後伺服器處理完請求之後,會再返回結果中加上如下控制欄位:

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: 允許自定義的頭部,以逗號隔開,大小寫不敏感

然後瀏覽器通過返回結果的這些控制欄位來決定是將結果開放給客戶端指令碼讀取還是遮蔽掉。如果伺服器沒有配置cors,返回結果沒有控制欄位,瀏覽器會遮蔽指令碼對返回資訊的讀取。

0×02 安全隱患

大家注意這個流程。伺服器接收到跨域請求的時候,並沒有先驗證,而是先處理了請求。所以從某種程度上來說。在支援cors的瀏覽器上實現跨域的寫資源,打破了傳統同源策略下不能跨域讀寫資源。

再一個就是如果程式猿偷懶將Access-Control-Allow-Origin設定為允許來自所有域的跨域請求。那麼cors的安全機制幾乎就無效了。不過先別高興的太早。其實這裡在設計的時候有一個很好的限制。xmlhttprequest傳送的請求需要使用“withCredentials”來帶上cookie,如果一個目標域設定成了允許任意域的跨域請求,這個請求又帶著cookie的話,這個請求是不合法的。(就是如果需要實現帶cookie的跨域請求,需要明確的配置允許來源的域,使用任意域的配置是不合法的)瀏覽器會遮蔽掉返回的結果。javascript就沒法獲取返回的資料了。這是cors模型最後一道防線。假如沒有這個限制的話,那麼javascript就可以獲取返回資料中的csrf token,以及各種敏感資料。這個限制極大的降低了cors的風險。

0×03 攻擊模型


13854564897049.png%21small

<img src="http://image.3001.net/images/20131126/13854564897049.png%21small" style="float:none" title="cors3.png"/></p>

從思路上講,有兩種型別的攻擊方式。一種是在攻擊者自己控制的網頁上嵌入跨域請求,使用者訪問連結,執行了跨域請求,從而攻擊目標,比如訪問了內網敏感資源。還有一種是正常的網頁被嵌入了到攻擊者控制頁面的跨域請求,從而劫持使用者的會話。

0×04 攻擊場景

先看第一種思路的攻擊場景:

1,複雜csrf。傳統的csrf都是利用html標籤和表單來發送請求。沒有辦法實現一些複雜步驟的csrf,比如模擬購物,先加購物車,結算,填寫資訊,等等。比如上傳檔案。具體可以參考利用csrf上傳檔案

2,訪問內網敏感資源。這個在一定的條件下是可以實現的。比如內網的伺服器配置了

Access-Control-Allow-Origin: * 允許任何來自任意域的跨域請求

使用者訪問惡意網頁的時候,執行了到內網伺服器192.168.1.123/password.txt的請求,指令碼在接收到伺服器返回之後,將內容傳送到攻擊者的伺服器上。

第二種思路的場景:

1,互動式xss。參考揭密HTML5帶來的攻擊手法中講到的shell of the future工具。通過cors,繞過一些反會話劫持的方法,如HTTP-Only限制的cookie,繫結IP地址的會話ID等,劫持使用者會話。

2,程式猿在寫ajax請求的時候,對目標域限制不嚴。有點類似於url跳轉。facebook出現過這樣一個案例。javascript通過url裡的引數進行ajax請求。通過控制這個引數實現注入攻擊。

13854564904362.png%21small

<img src="http://image.3001.net/images/20131126/13854564904362.png%21small" style="float:none" title="cors4.png"/><br/></p>


0×05 致謝

本文參考了nyannyannyangerionsecurity.com 的文章,表示感謝。

[email protected]