1. 程式人生 > >前端跨域安全

前端跨域安全

前言

在Web安全中有一條很重要的同源策略,規定了前端安全的基本原則。前端開發中為了能夠在不同頁面中進行資料傳遞,設計了多種跨域的資料傳遞方式,但是資料跨域傳遞在方便了開發的同時也帶來了一些安全問題。

一、同源策略

1.1 定義

同源是一種約定,它定義了從一個源載入的文件或指令碼如何與來自另一個源的資源進行互動,是一個用於隔離潛在惡意檔案的重要安全機制,也是瀏覽器最核心最基本的安全功能。

1.2 源的劃分

如果兩個頁面的協議、埠、域都相同,則兩個頁面具有相同的源。如下表所示:

注意:源的劃分中“域“並不是指資源物件所在的域,而是載入資源物件的域。例如a.com通過通過以下程式碼

<script src=http://b.com/b.js></script>

載入了b.com上的b.js,但是b.js是執行在a.com上的,所以b.js的源是a.com而不是b.com。

二、window.name

Windows物件是瀏覽器的窗體,很多時候它不受同源策略的限制,利用這個物件可以實現跨域跨頁面傳遞資料。其中,Windows的name屬性是在一個窗體的生命週期內所有頁面所共享的,每個頁面對window.name都有讀寫的許可權,window.name是持久存在一個視窗載入過的所有頁面中的,並不會因新頁面的載入而進行重置。

比如如下程式碼儲存為list.html

開啟它之後程式會彈出window.name的值,然後跳轉到http://127.0.0.1/test.html,

在test.html中的程式碼為

僅僅是一個彈窗,彈出來的window.name值域上一個頁面相同。

這說明window.name成功的從一個域傳遞到了另外一個域。這個地方怎麼進行漏洞利用呢?其中一個思路就是藉助這個特性縮短XSS Payload的長度輔助XSS進行攻擊。比如在一個可控的頁面先構造好XSS Payload,如下所示

<script type="text/javascript">
window.name = "alert(document.cookie)";
location.href = "http://www.xssedsite.com/xssed.php";
</script>

那麼在XSS的漏洞站點只需執行以下程式碼即可

Eval(name);

極其簡短。window.name的跨域本質上一種瀏覽器的特性,並不是安全漏洞,只是如果使用不當可能會有安全問題。在防禦上,當在一個域去接收另一個域的window.name的值的時候要對其進行檢測,此時它相當於一個輸入點,而開發中要堅信任何輸入都是不可信的原則。

三、postMessage

postMessage允許每一個Window(包括當前視窗、彈出視窗、iframe等)物件往其他的視窗傳送文字訊息,從而實現跨視窗訊息傳遞,且這個功能不受同源策略影響。postMessage語法如下:

otherWindow.postMessage(message, targetOrigin, [transfer]);

  • otherWindow:其他視窗的一個引用,比如iframe的contentWindow屬性、執行window.open返回的視窗物件。
  • message:將要傳送到其他 window的資料。
  • targetOrigin:通過視窗的origin屬性來指定哪些視窗能接收到訊息事件,其值可以是字串”*”(表示無限制)或者一個URI。
  • transfer :是一串和message 同時傳遞的 Transferable 物件

傳送視窗

監聽視窗(也就是test.html所在的頁面)

傳送視窗負責傳送訊息,監聽視窗繫結message事件,監聽其他視窗的訊息。這裡存在兩個安全問題,一是對於監聽視窗來說,監聽視窗作為資料的接收方會將資料進行二次處理或顯示,如果直接使用獲得資料,就會如同SQL語句之前沒有過濾導致注入一樣,會導致一些不可控的後果(比如XSS),所以監聽視窗在防禦上至少要做到以下兩點:

  1. 始終使用origin和source屬性驗證發件人的身份,防止攻擊者偽造傳送源傳送惡意資料;
  2. 要對接受到的message進行檢查,即使是許可的源也有可能傳送錯誤或惡意資料的,在使用之前要檢查其合法性。

二是對傳送視窗來說,我們知道在傳送資料之前需要宣告一個window物件(在傳送視窗的程式碼截圖中可以看到),它指定了我們的資料傳送到哪一個視窗,這裡很多人認為既然已經指定了視窗,targetOrigin就可以不用設定。但實際上這裡存在一個問題,比如當window所指定的視窗因為某種原因發生了跳轉,前往了其他連結,如果不指定targetOrigin資料依然會被髮送,這樣就可能導致資訊洩露。所以傳送視窗在防禦上要設定一個確切的targetOrigin,而不是空值。

四、Flash跨域

4.1 簡介

flash在跨域時唯一的限制策略就是crossdomain.xml檔案,該檔案限制了flash是否可以跨域讀寫資料以及允許從什麼地方跨域讀寫資料。位於.a.com域中的SWF檔案要訪問b.com的檔案時,SWF首先會檢查b.com伺服器目錄下是否有crossdomain.xml檔案,如果沒有,則訪問不成功;若crossdomain.xml檔案存在,且裡邊設定了允許a.com域訪問,那麼通訊正常。所以要使Flash可以跨域傳輸資料,其關鍵就是crossdomain.xml。

4.2 位置

自flash 10以後,如有跨域訪問需求,必須在目標域的根目錄下放置crossdomain.xml檔案,且該根目錄下的配置檔案稱為“主策略檔案”。若不存在主策略檔案,則該域將禁止任何第三方域的flash跨域請求。主策略檔案對全站的跨域訪問起控制作用。也可以單獨在某路徑下放置僅對該路徑及其子路徑生效的crossdomain.xml配置檔案,這需要在flash的AS指令碼中使用如下語句來載入該配置檔案:

Security.loadPolicyFile(“xxx.com/subdir/crossdomain.xml”)

4.3 配置

cross-domain-policycross-domain-policy元素是跨域策略檔案crossdomain.xml的根元素。它只是一個策略定義的容器,沒有自己的屬性。子元素有:

site-control(確認是否可以允許載入其他策略檔案)
allow-access-from(確認能夠讀取本域內容的flash檔案來源域)
allow-access-from-identity(有特定證書的來源跨域訪問本域上的資源)
allow-http-request-headers-from(授權第三方域flash向本域傳送使用者定義的http頭)

4.4 漏洞利用

Flash跨域進行資訊讀取能否利用成功,主要還是看crossdomain.xml檔案的配置,例如某站的crossdomain.xml檔案

這樣就可以進行跨域進行資訊讀取,我們利用github上的專案:

github.com/nccgroup/CrossSiteContentHijacking

來進行漏洞利用,將原始碼下載下來進行本地部署,型別選擇Flash。(這裡注意瀏覽器要支援Flash播放)

輸入target點選Retrieve Contents在下方可以看到成功讀取到個人資訊頁面的內容。

這個工具只能證明漏洞存在,真正的利用情境需要重新構造特定的惡意頁面。就像CSRF需要構造一個頁面誘導使用者點選一樣,Flash跨域也需要構造類似的情景,不過傳統的CSRF是寫入型的攻擊,而利用Flash跨域可以進行讀取操作(為什麼一定要和CSRF對比呢?因為CSRF的利用是需要使用者的登陸憑證的,使用者在登陸狀態下去訪問惡意頁面才能執行指定的操作,不過CSRF一般都是通過表單提交資料包,瀏覽器自動攜帶cookie,因為js是無法跨域的,這也是為什麼CSRF只能進行寫入操作。Flash跨域有著同樣的道理,也需要使用者的登陸憑證(Cookie)才能訪問敏感頁面, 其中的不同點在於Flash指令碼可以操作cookie傳送表單,進而可以執行讀取操作。以上工具跨域獲得的資料是在測試賬號已經登陸的狀態下顯示的資料,本身模擬了一個受害者在登陸狀態下訪問惡意頁面的情景!)。其根本原因在於crossdomain.xml允許任意源的Flash發起請求,而Flash請求會攜帶Cookie,這樣就可以在使用者不知情的情景下利用使用者的登陸憑證訪問特定的敏感頁面,再通過Flash指令碼中的程式碼將獲取的敏感資料傳送的攻擊者的伺服器中。簡單的一句話就是可以將Flash跨域理解成讀取型CSRF。在防禦上其實很簡單,在設定crossdomain.xml檔案的時候僅配置信任的源,一定不要使用domain=”*”這樣的寫法

五、CORS

5.1 簡介

CORS是一個W3C標準,全稱是”跨域資源共享”(Cross-origin resource sharing)。CORS需要瀏覽器和伺服器同時支援,瀏覽器一旦發現AJAX請求跨源,就會自動新增一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊。

5.2 簡單請求

瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。若請求滿足所有下述條件,則該請求可視為“簡單請求”:

使用下列HTTP方法之一:

  • GET
  • HEAD
  • POST

HTTP頭資訊不能超過以下幾種欄位型別:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

其中Content-Type的值只能是以下三種:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

前面已經說過這些跨域請求與正常的請求在程式碼上沒有太多的區別,瀏覽器會幫助我們完成CORS的檢驗過程,示例如下

上述程式碼執行之後會跨域訪問我的blog,這時瀏覽器會自動在http的頭中加上Origin欄位,表明請求的來源。

當返回包中攜帶了Access-Control-Allow-Origin欄位,就表示完成了一次CORS訪問控制。

本例中,服務端返回的 Access-Control-Allow-Origin: * 表明,該資源可以被任意外域訪問。如果服務端僅允許來自 http://127.0.0.1 的訪問,該首部欄位的內容如下:

Access-Control-Allow-Origin: http://127.0.0.1

5.3 非簡單請求

非簡單請求使用了不同的HTTP方法與引數,並且非簡單請求會在正式通訊之前用OPTIONS方法發起一個“預檢請求”,該“預檢請求”會詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP方法和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的請求,否則就報錯。當請求滿足下述任一條件時,即應首先發送“預檢請求”:

使用下列HTTP方法之一:

  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE
  • PATCH

HTTP頭資訊超過了以下幾種欄位型別:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

其中Content-Type的值以下三種之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

如下程式碼會首先發起一個“預檢請求”:

上面的程式碼使用 POST 請求傳送一個 XML 文件,該請求包含了一個自定義的請求首部欄位(X-PINGOTHER: pingpong)。另外,該請求的 Content-Type 為 application/xml。因此,該請求需要首先發起“預檢請求”。

返回結果

瀏覽器會根據返回結果決定是否進行正式通訊,上述資料包表明伺服器允許任意的源通過POST、GET、OPTIONS方法攜帶X-PINGOTHER 與 Content-Type頭欄位進行通訊,響應的有效時間為 86400 秒,也就是 24 小時,如下是正式的通訊資料包

5.4 身份憑證

CORS請求預設不傳送Cookie和HTTP認證資訊,想要傳送身份憑證跨源請求中必須開啟withCredentials屬性,如下所示

上述程式碼的XMLHttpRequest請求中在第14行打開了withCredentials屬性,從而瀏覽器會向伺服器傳送cookie。

但是想要完成整個通訊還需要伺服器在頭資訊中返回:

Access-Control-Allow-Credentials: true

以及Access-Control-Allow-Origin的值不得為“*”,必須指定明確的、與請求網頁一致的域名,否則瀏覽器會拒絕返回的資料,比如我們可以在burp的返回包中看到伺服器返回的資料,但是瀏覽器卻沒有任何顯示

身份憑證是CORS的跨域的條件之一,withCredentials開啟後會降低攻擊者的攻擊成本。

5.5 漏洞利用

實際上,由於CORS的“身份憑證”的特性,使得很難繞過CORS進行跨域資源讀取。想要通過CORS獲取到敏感資訊需要像CSRF那樣誘導使用者去訪問構造的惡意頁面,然後頁面中的js程式碼發起跨域請求獲取資訊,再將資訊傳送到攻擊者伺服器,也可稱之為讀取型的CSRF。但是這裡需要滿足CORS的幾個身份憑證的特性,否則只能獲取一些不需要身份憑證的價值不大的資訊。

同樣利用github上的專案:

github.com/nccgroup/CrossSiteContentHijacking

來進行漏洞利用

Type選擇CORS Windows,Target Page選擇想要獲取資訊的目標連結(我自己的Blog),如果需要傳送POST資料在POST Data填上資料即可,然後點選Retrieve Contents,頁面會跳轉到/ContentHijacking.html,可以看到跨域獲取的資訊

注意頭資訊需要返回下面這兩個欄位才能利用成功

不過也不是完全這麼雞肋,有些內網資源,不需要攜帶身份憑證但是外網不可訪問就可以利用這個方法嘗試讀取。這裡舉個例子,比如某企業內部搭建了一個私有的git伺服器(不需要登陸),開發人員在上面進行程式碼管理。即使攻擊者獲取了git伺服器的地址也無法訪問,如果這時存在CORS漏洞,攻擊者就可以構造特定的惡意頁面,誘導企業人員去訪問,企業人員會從內網發起這個請求並把結果返回到惡意頁面上,再通過惡意頁面的程式碼將資料傳送到攻擊者手中。

另外我們知道再CSRF的防禦中在敏感頁面設定Token(一個隨機序列,跟隨資料包傳送,防止攻擊者猜測資料包請求引數,從而無法進行CSRF發攻擊)是常用的方法,一旦完成跨域訪問即可獲取頁面原始碼,那麼這個隨機序列Token就可以被我們獲取了,從而可以構造CSRF所需要的請求引數,防禦也就喪失了。

在防禦上,資料介面在配置返回的頭資訊時要指定可信的Access-Control-Allow-Origin源,而不要設定成*,例如php中這樣的寫法是不安全的:

header(“Access-Control-Allow-Origin: *”);

六、總結

跨域即是前端開發的一種訴求也是也是前端防禦的一個要點,除了以上的幾種較為典型的跨域方法外還有其他的技巧,大家可以自己補充。它就像XSS、CSRF、釣魚等前端攻擊一樣,主要目標是正常使用者而不是伺服器。跨域在攻擊中造成的危害並不像SQl注入、命令執行那樣巨大,其防禦也不復雜,只需要在使用的時候採用了正確的方法即可避免這些問題。具體的點主要是:

  1. 配置檔案:儘量使用白名單的思路設定允許跨域的源
  2. 代價編寫:不要為了方便開發簡化引數,嚴格按照說明文件編寫
  3. 資料輸入點進行檢測
  4. 最小特權原則

但是並不能忽視這類安全問題,畢竟任何一個突破口都有撕開整條安全防線的可能。

七、宣告

文章旨在普及網路安全知識,提高小夥伴的安全意識的同時介紹常見漏洞的特徵、挖掘技巧等。若讀者因此做出危害網路安全的行為後果自負,與合天智匯及本人無關,特此宣告。