如何繞開referrer防盜鏈

本文同步在我的博客shymean.com上,歡迎關注javascript

最近處理了一個與referer有關的需求,發現裏面仍是有一點門道的。所以在本篇文章整理了referer相關知識點,主要涉及圖片防盜鏈與如何繞開防盜鏈限制。php

參考:html

使用referer

Referer是HTTP請求頭的一個字段,包含了當前請求頁面的來源頁面的地址,經過該字段,咱們能夠檢測訪客是從哪裏來的。前端

那麼,referer到底有啥做用呢?java

交互優化

在某些web應用的交互中,右上角會提供一個返回按鈕,方便用戶返回上一頁 nginx

其實現通常也比較簡單web

<a href="javascript: history.back();"></a>
複製代碼

這種處理方式隱藏的一個問題是:若是用戶從其餘入口如分享連接等地方直接進來時,點擊這個按鈕是沒法返回。ajax

所以在點擊按鈕時,咱們能夠判斷document.referrer是否存在來優化交互:若是存在,則返回上一頁;若是不存在,則直接返回首頁。chrome

應該注意到上面寫的是referer,而在DOM中,使用的是referrer,這是所以請求頭中的referer是因爲歷史緣由致使的拼寫錯誤,而在DOM規範中進行了修正,所以致使當前拼法並不統一的問題~segmentfault

防盜鏈

當用戶訪問網頁時,referer就是前一個網頁的URL;若是是圖片的話,一般指的就是圖片所在的網頁。當瀏覽器向服務器發送請求時,referer就自動攜帶在HTTP請求頭了。

一個HTML頁面每每包含多種資源,這些資源經過標籤如scriptimglink等形式嵌套在HTML文檔中,一個完整頁面每每須要通過發送多條HTTP請求下載資源,而後才能正常展現。因爲HTML自己並無對嵌套資源的來源作限制,基於這樣的機制,盜鏈就成爲了一種手段。

下面是關於盜鏈的百度百科定義

盜鏈是指服務提供商本身不提供服務的內容,經過技術手段繞過其它有利益的最終用戶界面(如廣告),直接在本身的網站上向最終用戶提供其它服務提供商的服務內容,騙取最終用戶的瀏覽和點擊率。受益者不提供資源或提供不多的資源,而真正的服務提供商卻得不到任何的收益。

打個比方:A網站將本身的靜態資源如圖片或視頻等存放在服務器上。B網站在未經A容許的狀況下,使用A網站的圖片或視頻資源,放置到本身的網站中。因爲服務器資源是須要花錢的,這樣網站B盜取了網站A的空間和流量,而A沒有獲取任何利益卻承擔了資源使用費。B盜用A資源放到本身網站的行爲即爲盜鏈。

防盜鏈通常由下面幾種方式

  • 按期修改文件名稱或路徑
  • 經過referer,限制資源引用頁的來源
  • 經過cookie、session等進行身份認證
  • 圖片加水印等

這裏咱們主要關注一下referer的防盜鏈的原理。下面是nginx的防盜鏈配置

location ~* \.(gif|jpg|png)$ {
    valid_referers none blocked *.phptest.com;
    if ($invalid_referer) {
        return 403;
    }
}
複製代碼

這種方法是在server或者location段中加入:valid_referers。這個指令在referer頭的基礎上爲 $invalid_referer 變量賦值,其值爲0或1。若是valid_referers列表中沒有Referer頭的值, $invalid_referer將被設置爲1。

該指令支持noneblocked

  • 其中none表示空的來路,也就是直接訪問,好比直接在瀏覽器打開一個文件,
  • blocked表示被防火牆標記過的來路,*..com表示全部子域名。

經過referer,咱們能夠判斷請求的來源,從而決定服務器是否正常返回請求資源,達到控制請求的目的。

須要注意的是,在某些狀況下,即便用戶是正常訪問網頁或圖片,也是不會攜帶referer的,好比直接在瀏覽器地址欄直接輸入資源URL,或經過瀏覽器新窗口打開頁面等。這種訪問是正常的,若是強制如今某些白名單referer名單才能訪問資源,則可能誤傷這一部分正經常使用戶,這也是爲何有的防盜鏈檢測中容許Referer頭部爲空經過檢測的狀況。

既然如此,若是把referer隱藏掉,也能夠繞開部分站點防盜鏈的限制,下面讓咱們來看看如何實現隱藏referer的功能。

隱藏referer

參考

在利用部分站點防盜鏈限制容許referer爲空,或者咱們僅僅是不想讓服務器知道訪問來源時,咱們能夠隱藏referer。

referrerPolicy

以前瀏覽器在請求資源時,會按本身的默認規則來決定是否加上Referrer。後來W3C發佈了Referrer-Policy草案,運行開發者靈活地控制本身網站的referer策略。主要包含下面策略

  • no-referrer:任何狀況下都不發送 Referrer 信息;
  • no-referrer-when-downgrade (默認值):在沒有指定任何策略的狀況下瀏覽器的默認行爲
  • origin:在任何狀況下,僅發送文件的源做爲引用地址
  • origin-when-cross-origin: 對於同源的請求,會發送完整的URL做爲引用地址,可是對於非同源請求僅發送文件的源。
  • same-origin:對於同源的請求會發送引用地址,可是對於非同源請求則不發送引用地址信息。

上面只列舉了一部分可選策略,詳情可參考MDN文檔

所以,咱們能夠手動指定no-referrer來隱藏referer

<!-- phptest2.com是我本地的一個測試域名, 下同 -->
<img src="http://phptest2.com/upload/1.png" width="200" referrerPolicy="no-referrer" alt="">
複製代碼

或者在建立image對象的時候,指定referrerPolicy策略

const img = new Image()
img.referrerPolicy = 'no-referrer'
複製代碼

此時打開開發者工具就能夠看見該圖片的請求已經再也不攜帶對應的referer了。總結一下,通常有下面幾種設置Referer策略的方式:

  • 經過 http 響應頭中的 Referrer-Policy 字段
  • 經過 meta 標籤,name 爲 referrer
  • 經過a、area、img、iframe、link元素的 referrerpolicy 屬性。
  • 經過a、area、link元素的 rel=noreferrer 屬性。

須要注意的是目前referrerPolicy仍處於提案的草稿階段,瀏覽器兼容性並非特別好。

在請求時修改header頭部

XMLHttpRequest對象提供了setRequestHeader方法,用於向請求頭添加或修改字段。咱們能不能手動將修改 referer字段呢?

// 經過ajax下載圖片
function loadImage(uri) {
    return new Promise(resolve => {
        let xhr = new XMLHttpRequest();
        xhr.responseType = "blob";
        xhr.onload = function() {
            resolve(xhr.response);
        };

        xhr.open("GET", uri, true);
        xhr.setRequestHeader("Referer", ""); // 經過setRequestHeader設置header不會生效
        xhr.send();
    });
}

// 將下載下來的二進制大對象數據轉換成base64,而後展現在頁面上
function handleBlob(blob) {
    let reader = new FileReader();
    reader.onload = function(evt) {
        img.src = evt.target.result;
    };
    reader.readAsDataURL(blob);
}

const imgSrc = "http://phptest2.com/upload/1.png";
loadImage(imgSrc).then(blob => {
    handleBlob(blob);
});
複製代碼

上述代碼運行時會發現控制檯提示錯誤

Refused to set unsafe header "Referer"

能夠看見setRequestHeader設置referer響應頭是無效的,這是因爲瀏覽器爲了安全起見,沒法手動設置部分保留字段,不幸的是Referer剛好就是保留字段之一,詳情列表參考Forbidden header name

所以在經過AJAX設置referer宣告失敗,那咱們能夠換一個方式從瀏覽器加載圖片,好比試試Fetch呢?

Fetch是瀏覽器提供的一個全新的接口,用於訪問和操做HTTP管道部分,該接口支持referrerPolicy,所以也能夠用來操做referer。

function fetchImage(url) {
    return fetch(url, {
        headers: {
            // "Referer": "", // 這裏設置無效
        },
        method: "GET", 
        mode: "cors",
        redirect: "follow",
        referrer: "" // 將referer置空,此處寫成no-referrer貌似會把路徑替換成 host + 'no-referrer'字符串形式
    }).then(response => response.blob());
}
loadImage(imgSrc).then(blob => {
    handleBlob(blob);
});
複製代碼

經過將配置參數redirect置位空,能夠看見本次請求已經不帶referer了。

使用iframe

下面是一種經過iframe來實現隱藏referer的方式,,整個過程有點魔性。大體實現以下

const putNoRefererImage = (() => {
    let iframe
    /* src: 圖片地址 wrap:須要加載圖片的容器 */
    return function (src, wrap) {
        if (iframe) {
            iframe.remove()
        }

        let url = new URL(src);
        let frameid = 'frameimg' + Math.random();
        window.img = `<img id="tmpImg" width=400 src="${url}" alt="圖片加載失敗,請稍後再試"/> `;

        // 構造一個iframe
        iframe = document.createElement('iframe')
        iframe.id = frameid
        iframe.src = "javascript:parent.img;" // 經過內聯的javascript,設置iframe的src
        // 校訂iframe的尺寸,完整展現圖片
        iframe.onload = function () {
            var img = iframe.contentDocument.getElementById("tmpImg")
            if (img) {
                iframe.height = img.height + 'px'
                iframe.width = img.width + 'px'
            }
        }
        iframe.width = 200
        iframe.height = 200
        iframe.scrolling = "no"
        iframe.frameBorder = "0"
        wrap.appendChild(iframe)
    }
})();

putNoRefererImage(imgSrc, document.body);
複製代碼

運行代碼能夠看見,經過這種方式也能夠實現隱藏referer的功能,所以用做不支持referrerPolicy的一種替代方案。

在某些不支持javascript內聯運行的場景下,這種方案也是不可行的,好比在chrome擴展程序,因爲content_security_policy,使用內聯JavaScript會報錯

Refused to execute JavaScript URL because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.

小結

本文總結了referer的做用,以及利用referer實現防盜鏈的配置。因爲部分站點的防盜鏈配置容許referer爲空,所以能夠利用這一點,經過隱藏referer,來達到繞開防盜鏈的目的。

接着介紹了幾種前端隱藏referer的實現方式,從技術上來看,referrerPolicy是近乎完美的選擇,因爲存在兼容性限制,所以能夠經過fetchiframe等方式來實現。

瞭解了防盜鏈,以及如何繞開防盜鏈,咱們才能更好的保證本身站點資源的安全性。除了採用referer防盜鏈,咱們還能夠採用身份認證、按期修改資源路徑等方式避免盜鏈。最後,用一句老話結束:

當你凝視深淵的時候 深淵也在凝視你。

相關文章
相關標籤/搜索