Referer的理解及防盗链

        HTTP请求的头信息里面,Referer是一个常见字段,提供访问来源的信息。很多开发者知道这个字段,但是说不清它的具有细节。本文详细介绍该字段。

一、Referer的含义

        现实生活中,购买服务或加入会员的时候,往往要求提供信息:“你是从哪里知道的我们?”,这叫做引荐人(referer),谁引荐了你?对公司来说很,这是很有用的信息。互联网也是一样,你不会无缘无故访问一个网页,总会有人告诉你,可以去那里看看。服务器也想知道,你的“引荐人”是谁?

        HTTP协议在请求(request)的头部信息里,设计了一个Referer字段,给出“引荐网页”的URL。

        比如说我们在谷歌浏览器上,从百度搜索关键字,然后跳转到我们搜索的网站,那么它的Referer就是百度的搜索链接。百度链接比较长,待上课关键之类的。如下图:

这里特别注意:Referer请求头可能会暴露用户的浏览历史、涉及到用户的隐私问题。

 二、Referrer-policy的值及用法

Referrer-policy可以一定程度上防御csrf漏洞

        目前很多网站的防盗链机制都是用头部定义Referrer来判断是都盗链。其实这个很容易破解,自己在请求时加上Referrer头部就行。

        在哪些情况下会设置引用头呢?一般来说,加载一个HTML页面之后,本HTML页面里面的JavaScript文件,CSS文件,画面文件都会设置Referrer。然后,点击这个HTML页面里的链接,跳转其他页面时,也会设置Referrer。

Referrer-policy的8个值:

  • no-referrer:整个referer首部会被移除,访问来源信息不随着请求一起发送。通俗易懂的来说就是所有请求不发送Referrer。
  • no-referrer-when-downgrade:在没有指定任意策略的情况下用户代理的默认行为。在同等级安全级别的情况下,引用页面的地址会被发送(HTTPS->HTTPS),但是在降级的情况下不会被发送(HTTPS->HTTP)。
  • origin:任意情况下,仅发送文件的源作为引用地址。源信息包括访问协议和域名。例如:https://example.com/page.html会将https://example.com/作为引用地址。
  • origin-when-cross-origin:对于同源的请求,会发送完整的URL作为引用地址,但是对于非同源请求仅发送文件的源。
  • same-origin:对于同源的请求会发送引用地址,但是对于非同源请求则不会发送引用地址信息。例如:如果设置成same-origin,那么aaa.com引用bbb.com的资源,就不会发送Referrer。

这里同源的意思是指同一个域名且同一协议。

  • strict-origin:再同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS),但是在降级的情况下不会发送(HTTPS->HTTP)。
  • strict-origin-when-cross-origin:对于同源的请求,会发送完整的URL作为引用地址;再同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS);在降级的情况下不发送此首部(HTTPS->HTTP)。
  • unsafe-url:无论是同源请求还是非同源请求,都发送完整的URL(移除参数信息之后)作为引用地址。(最不安全)。

三、Referer的设置

1.在HTML里设置meta

<meta name="referrer" content="origin">

如下图:

 或者用<a>、<area>、<img>、<iframe>、<script>、<link>元素上的referrerpolicy属性为其设置独立的请求策略。如下图:

<script src='/javascripts/test.js' referrerpolicy="no-referrer"></script>

 

 未加link元素的referrerpolicy属性会出现下图所示:

四、防盗链

 1.防盗链的工作原理

        通过referer或者签名,网站可以检测目标网页访问的来源网页,如果是资源文件,则可以追踪到显示它的网页地址,一旦检测到来源不是本站,即进行阻止或者返回指定的页面。

2.如何绕过图片防盗链

        现在很多网站是如何利用Referer来进行防图片盗链的呢?

其实是有三种情况下允许引用图片:

  • 本网站
  • 无Referer信息情况。(服务器认为是从浏览器直接访问的图片URL,所以这种情况下是能正常访问)
  • 授权的网址

通常情况下我们只能从情况2入手,通过设置Referer为空进行绕过防盗链。

(1)利用https网站盗链http资源网站,referer不会发送

先利用openssl生成自签名证书(可以参考这个https://github.com/zxl925768661/Blog/tree/main/HTTP%E7%9B%B8%E5%85%B3/Demos/referer/demo03)

 

只适用于旧版本的浏览器,https降为http 是一种非常不安全的行为,该操作浏览器会自动禁止。虽然降级失败但是可以成功访问/src/img/1.jpg.因为referer为空且白名单命中了192.168.0.103:8080 字段。
whiteList.indexof(referHostName) == 1 ,返回了正确的图

设置meta

<meta name="referrer" content="no-referrer" /> 

 (2)利用iframe伪造请求Referer

可以参考(如何绕开referrer防盗链 - 掘金)这个网址

 

 结果如下:

(3)利用XMLHTTPRequest

 XMLHttpRequest中setRequestHeader方法,用于请求头添加或修改字段。

// 通过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);
        // 通过setRequestHeader设置header不会生效
        // 会提示 Refused to set unsafe header "Referer"
        xhr.setRequestHeader("Referer", ""); 
        xhr.send();
    });
}
  

// 将下载下来的二进制大对象数据转换成base64,然后展示在页面上
function handleBlob(blob) {
    let reader = new FileReader();
    reader.onload = function(evt) {
        let img = document.createElement('img');
        img.src = evt.target.result;
        document.getElementById('container').appendChild(img)
    };
    reader.readAsDataURL(blob);
}

const imgSrc = "https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg";

loadImage(imgSrc).then(blob => {
    handleBlob(blob);
});

上述代码运行时会发现控制台提示错误:

Refused to set unsafe header "Referer"

可以看见setRequestHeader设置referer响应头是无效的,这是由于浏览器为了安全起见,无法手动设置部分保留字段,不幸的是Referer恰好就是保留字段之一。