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