【XSS技巧拓展】————2、再談同源策略
同源策略應該是學習 Web 安全時最最基礎的內容,時隔多年再回過頭來仔細溫習一下,發現了不少當初漏掉的細節,結合這麼多年的安全經驗,重新總結一下同源策略相關的內容。
0x00 何為同源策略
如果兩個頁面擁有相同的協議(http、https)、相同的埠(如果其中一個指定了埠)、相同的 host,那麼就可以認為這兩個頁面是同源的。簡單的講,同源策略就是同協議、同埠、同 host 這樣的一個三元組,這裡給出幾個例子:
我們將所有的 URL 都與第一個 URL 相比:
- 第二個與第一個 URL 是相同的,原因是滿足我們上面提到的三元組(協議、埠、host)
- 第三個與第一個相比,其協議不同;
- 第四個與第一個相比,其埠不同;
- 第五個與第一個相比,其 host 不同;
那麼這種限制有什麼作用呢?同源策略主要是限制了頁面最後那個的指令碼從另一個源載入資源時的行為,這對於防範惡意頁面是一種很好的防禦機制,如果惡意指令碼請求了非同源的一個東西,那麼這種行為就很可能因為同源策略的限制被瀏覽器拒絕,從而在某種程度上緩解了攻擊。
對於about:blank
和javascript:
這種特殊的 URL,他們的源應當是繼承自載入他們的頁面的源,他們本身並沒有『源』的概念。
0x01 HTTP 請求控制
我們已經知道了,瀏覽器會根據同源策略允許或拒絕載入某些資源,但是又一個問題由此而生,我們的網站通常會將靜態檔案(CSS
JS
, 圖片)等放置在 CDN 上,那麼 CDN 與當前域必然是不同源的,但是神奇的是,這些網站可以正常加載出他們需要的資源並展示給使用者,這裡為什麼又不受同源策略的影響呢?
再比如在使用XMLHttpRequest
的時候,又會因為同源策略的限制無法發出請求,那麼到底什麼情況下會觸發同源策略呢?總體來說,頁面跨域的行為主要會分為三類,分別是:
- Cross-origin write
- Cross-origin read
- Cross-origin embedding
在這三種行為之中,通常情況下只有Cross-origin read
是不被允許的,其餘的兩種是允許的,例如Cross-origin write
links
,重定向以及表單提交,Cross-origin embedding
中的資源嵌入。
那麼問題又來了,何種資源是允許嵌入的呢?MDN 文件中也給出了一些例子:
<script src="..."></script>
標籤嵌入跨域指令碼。語法錯誤資訊只能在同源指令碼中捕捉到。<link rel="stylesheet" href="...">
標籤嵌入 CSS。由於 CSS 的鬆散的語法規則,CSS 的跨域需要一個設定正確的Content-Type
訊息頭,不同瀏覽器有不同的限制。<img>
嵌入圖片。支援的圖片格式包括 PNG,JPEG,GIF,BMP,SVG,...<video>
和<audio>
嵌入多媒體資源。<object>
,<embed>
和<applet>
的外掛。@font-face
引入的字型。一些瀏覽器允許跨域字型(cross-origin fonts),一些需要同源字型(same-origin fonts)。<frame>
和<iframe>
載入的任何資源。站點可以使用 X-Frame-Options 訊息頭來阻止這種形式的跨域互動。
這樣一來,可以通過<img>
標籤載入檔案而不受同源策略的影響這件事情就明白了。
0x02 CORS 又是什麼東西
剛剛我們已經知道了,一些標籤嵌入外域資料的時候,是不會受到同源策略的影響,但是如果我們在<script></script>
指令碼中想要獲取外域的資料時,因為同源策略的干擾,就顯的格外麻煩。出於安全考慮,瀏覽器通常情況下都會限制從script
標籤內部發起的跨域 HTTP 請求,比如前文提到的XMLHttpRequest
,就會受到同源策略的影響,進而只能將資料發到同域內。
這裡需要說明一點,這裡的跨域請求可能不是瀏覽器直接攔截掉了,而是跨站請求發起了,但是返回結果被瀏覽器攔截了,請求實際上已經發送到了後端伺服器。在
Chrome
和Firefox
上,對於從https
到http
的跨域是會直接攔截,請求都無法傳送成功。
於是就有了 CORS 策略,允許 Web 應用程式進行跨域訪問。CORS 的全稱是cross-origin sharing stander
,跨域資源共享標準。簡單的講,這個標準允許在以下的幾個場景中發起跨域請求:
- 由
XMLHttpRequest
或Fetch
等發起的跨域請求; - Web 字型,通過 @font-face 進行跨域呼叫;
- WebGL 貼圖;
- 使用 drawImage 將 Images/video 畫面繪製到 canvas;
- 樣式表(使用 CSSOM);
- scripts;
在這些允許跨域的場景之中,我們作為安全人員,最關心的自然是scripts
部分。
0x03 進擊的 CORS
CORS
通過一些特殊的 HTTP 頭來確保哪些源站可以請求哪些資源,除此之外,如果這個請求會對伺服器的資料產生修改的可能
(這個說法並不是很準確,後面會詳細闡述),將會在跨域之前發起一個preflight
請求進行檢查,如果檢查不通過,那麼不會發起跨域請求。
首先要明確一點,並不是所有的請求都會觸發preflight
機制,這些不會觸發preflight
的請求被稱為simple request
。
符合下列條件的請求,將不會觸發preflight
機制並視為simple request
:
- GET 請求
- HEAD 請求
- Content-Type 為指定值的 POST 請求,包括
text/plain
,multipart/form-data
以及application/x-www-form-urlencode
- HTTP 首部欄位不能包含下列以外的值:
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
凡是不滿足上述條件的請求,將被視為preflight request
,並在發起跨域請求前,預先發起一個OPTIONS
請求進行檢查。在preflight request
的返回頭中,會包含一些關於是否允許發起跨域的資訊。例如我們看這個preflight
的例子,這個是向一個地址 POST XML 內容的請求:
在OPTIONS
請求中,發出了兩個特殊的 HTTP 頭,分別是Access-Control-Request-Method
和Access-Control-Request-Headers
,意思是告訴服務端,我接下來的請求中,會使用POST
方法,並且會攜帶兩個自定義的頭部欄位X-TEST
和Content-Type
(因為Content-Type
的內容並非是我們上文中提到的三種之一,所以被看做是自定義頭部)。
在返回頭中,要注意四個欄位:
從第一行開始,表示允許來自(http,foo.example,80)
這個源發來的資料,允許的方法為POST,GET,OPTIONS
,允許使用自定義的頭部X-TEST
和Content-Type
,該響應的有效時間是86400
秒,如果在這個期間內,客戶端無需為了同樣的跨域請求再次發起preflight request
,通常情況下,每個瀏覽器都有自己的最大時間以避免出現某些安全問題。
最後呢,神奇的是 CORS 還允許通過設定 Cookies 或 HTTP 認證來發送認證資訊。如果發起的是一個簡單請求,那麼不會經過preflight
,但是有一點需要注意,如果服務端返回的資訊中沒有Access-Control-Allow-Credentials: true
,瀏覽器就會攔截返回內容,不會將內容返還給呼叫者。