跨域資源共享(CORS)安全性淺析
一、背景
提起瀏覽器的同源策略,大家都很熟悉。不同域的客戶端腳本不能讀寫對方的資源。但是實踐中有一些場景需要跨域的讀寫,所以出現(xiàn)了一些hack的方式來跨域。比如在同域內(nèi)做一個代理,JSON-P等。但這些方式都存在缺陷,無法完美的實現(xiàn)跨域讀寫。所以在XMLHttpRequest v2標(biāo)準(zhǔn)下,提出了CORS(Cross Origin Resourse-Sharing)的模型,試圖提供安全方便的跨域讀寫資源。目前主流瀏覽器均支持CORS。
二、技術(shù)原理
CORS定義了兩種跨域請求,簡單跨域請求和非簡單跨域請求。當(dāng)一個跨域請求發(fā)送簡單跨域請求包括:請求方法為HEAD,GET,POST;請求頭只有4個字段,Accept,Accept-Language,Content-Language,Last-Event-ID;如果設(shè)置了Content-Type,則其值只能是application/x-www-form-urlencoded,multipart/form-data,text/plain。說起來比較別扭,簡單的意思就是設(shè)置了一個白名單,符合這個條件的才是簡單請求。其他不符合的都是非簡單請求。
之所以有這個分類是因為瀏覽器對簡單請求和非簡單請求的處理機制是不一樣的。當(dāng)我們需要發(fā)送一個跨域請求的時候,瀏覽器會首先檢查這個請求,如果它符合上面所述的簡單跨域請求,瀏覽器就會立刻發(fā)送這個請求。如果瀏覽器檢查之后發(fā)現(xiàn)這是一個非簡單請求,比如請求頭含有X-Forwarded-For字段。這時候瀏覽器不會馬上發(fā)送這個請求,而是有一個preflight,跟服務(wù)器驗證的過程。瀏覽器先發(fā)送一個options方法的預(yù)檢請求。下圖是一個示例。如果預(yù)檢通過,則發(fā)送這個請求,否則就不拒絕發(fā)送這個跨域請求。
下面詳細(xì)分析一下實現(xiàn)安全跨域請求的控制方式。先看一下非簡單請求的預(yù)檢過程。瀏覽器先發(fā)送一個options方法的請求。帶有如下字段:
Origin: 普通的HTTP請求也會帶有,在CORS中專門作為Origin信息供后端比對,表明來源域。
Access-Control-Request-Method: 接下來請求的方法,例如PUT, DELETE等等
Access-Control-Request-Headers: 自定義的頭部,所有用setRequestHeader方法設(shè)置的頭部都將會以逗號隔開的形式包含在這個頭中
然后如果服務(wù)器配置了cors,會返回對應(yīng)對的字段,具體字段含義在返回結(jié)果是一并解釋。
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
然后瀏覽器再根據(jù)服務(wù)器的返回值判斷是否發(fā)送非簡單請求。簡單請求前面講過是直接發(fā)送,只是多加一個origin字段表明跨域請求的來源。然后服務(wù)器處理完請求之后,會再返回結(jié)果中加上如下控制字段:
Access-Control-Allow-Origin: 允許跨域訪問的域,可以是一個域的列表,也可以是通配符"*"。這里要注意Origin規(guī)則只對域名有效,并不會對子目錄有效。即http://foo.example/subdir/ 是無效的。但是不同子域名需要分開設(shè)置,這里的規(guī)則可以參照同源策略
Access-Control-Allow-Credentials: 是否允許請求帶有驗證信息,這部分將會在下面詳細(xì)解釋
Access-Control-Expose-Headers: 允許腳本訪問的返回頭,請求成功后,腳本可以在XMLHttpRequest中訪問這些頭的信息(貌似webkit沒有實現(xiàn)這個)
Access-Control-Max-Age: 緩存此次請求的秒數(shù)。在這個時間范圍內(nèi),所有同類型的請求都將不再發(fā)送預(yù)檢請求而是直接使用此次返回的頭作為判斷依據(jù),非常有用,大幅優(yōu)化請求次數(shù)
Access-Control-Allow-Methods: 允許使用的請求方法,以逗號隔開
Access-Control-Allow-Headers: 允許自定義的頭部,以逗號隔開,大小寫不敏感
然后瀏覽器通過返回結(jié)果的這些控制字段來決定是將結(jié)果開放給客戶端腳本讀取還是屏蔽掉。如果服務(wù)器沒有配置cors,返回結(jié)果沒有控制字段,瀏覽器會屏蔽腳本對返回信息的讀取。
三、安全隱患
大家注意這個流程。服務(wù)器接收到跨域請求的時候,并沒有先驗證,而是先處理了請求。所以從某種程度上來說。在支持cors的瀏覽器上實現(xiàn)跨域的寫資源,打破了傳統(tǒng)同源策略下不能跨域讀寫資源。
再一個就是如果程序猿偷懶將Access-Control-Allow-Origin設(shè)置為允許來自所有域的跨域請求。那么cors的安全機制幾乎就無效了。不過先別高興的太早。其實這里在設(shè)計的時候有一個很好的限制。xmlhttprequest發(fā)送的請求需要使用“withCredentials”來帶上cookie,如果一個目標(biāo)域設(shè)置成了允許任意域的跨域請求,這個請求又帶著cookie的話,這個請求是不合法的。(就是如果需要實現(xiàn)帶cookie的跨域請求,需要明確的配置允許來源的域,使用任意域的配置是不合法的)瀏覽器會屏蔽掉返回的結(jié)果。javascript就沒法獲取返回的數(shù)據(jù)了。這是cors模型最后一道防線。假如沒有這個限制的話,那么javascript就可以獲取返回數(shù)據(jù)中的csrf token,以及各種敏感數(shù)據(jù)。這個限制極大的降低了cors的風(fēng)險。
四、攻擊模型
從思路上講,有兩種類型的攻擊方式。一種是在攻擊者自己控制的網(wǎng)頁上嵌入跨域請求,用戶訪問鏈接,執(zhí)行了跨域請求,從而攻擊目標(biāo),比如訪問了內(nèi)網(wǎng)敏感資源。還有一種是正常的網(wǎng)頁被嵌入了到攻擊者控制頁面的跨域請求,從而劫持用戶的會話。
五、攻擊場景
先看第一種思路的攻擊場景:
1,復(fù)雜csrf。傳統(tǒng)的csrf都是利用html標(biāo)簽和表單來發(fā)送請求。沒有辦法實現(xiàn)一些復(fù)雜步驟的csrf,比如模擬購物,先加購物車,結(jié)算,填寫信息,等等。比如上傳文件。具體可以參考利用csrf上傳文件
2,訪問內(nèi)網(wǎng)敏感資源。這個在一定的條件下是可以實現(xiàn)的。比如內(nèi)網(wǎng)的服務(wù)器配置了
Access-Control-Allow-Origin: * 允許任何來自任意域的跨域請求
用戶訪問惡意網(wǎng)頁的時候,執(zhí)行了到內(nèi)網(wǎng)服務(wù)器192.168.1.123/password.txt的請求,腳本在接收到服務(wù)器返回之后,將內(nèi)容發(fā)送到攻擊者的服務(wù)器上。
第二種思路的場景:
1,交互式xss。參考揭密HTML5帶來的攻擊手法中講到的shell of the future工具。通過cors,繞過一些反會話劫持的方法,如HTTP-Only限制的cookie,綁定IP地址的會話ID等,劫持用戶會話。
2,程序猿在寫ajax請求的時候,對目標(biāo)域限制不嚴(yán)。有點類似于url跳轉(zhuǎn)。facebook出現(xiàn)過這樣一個案例。javascript通過url里的參數(shù)進行ajax請求。通過控制這個參數(shù)實現(xiàn)注入攻擊。
致謝
本文參考了nyannyannyan和gerionsecurity.com 的文章,表示感謝。