关于javascript:Referer和Referrer-Policy及图片防盗链

61次阅读

共计 9163 个字符,预计需要花费 23 分钟才能阅读完成。

Referer

Referer 申请头蕴含了以后申请页面的起源页面的地址,即示意以后页面是通过此起源页面里的链接进入的。服务端个别应用 Referer(注:正确英语拼写应该是 referrer,因为晚期 HTTP 标准的拼写错误,为了放弃向后兼容就始终连续下来)申请头辨认拜访起源,可能会以此统计分析、日志记录以及缓存优化等。

 注:Referer 申请头可能会裸露用户的浏览历史、波及到用户的隐衷问题。

Referrer-policy

Referrer-policy 作用就是为了管制申请头中 referer 的内容

蕴含以下值:

  • no-referrer : 整个 referee 首部会被移除,拜访起源信息不随着申请一起发送。
  • no-referrer-when-downgrade : 在没有指定任何策略的状况下用户代理的默认行为。在等同安全级别的状况下,援用页面的地址会被发送(HTTPS->HTTPS),然而在降级的状况下不会被发送 (HTTPS->HTTP).
  • origin: 在任何状况下,仅发送文件的源作为援用地址。例如  https://example.com/page.html 会将 https://example.com/ 作为援用地址。
  • origin-when-cross-origin: 对于同源的申请,会发送残缺的 URL 作为援用地址,然而对于非同源申请仅发送文件的源。
  • same-origin: 对于同源的申请会发送援用地址,然而对于非同源申请则不发送援用地址信息。
  • strict-origin:在等同安全级别的状况下,发送文件的源作为援用地址(HTTPS->HTTPS),然而在降级的状况下不会发送 (HTTPS->HTTP)。
  • strict-origin-when-cross-origin:对于同源的申请,会发送残缺的 URL 作为援用地址;在等同安全级别的状况下,发送文件的源作为援用地址(HTTPS->HTTPS);在降级的状况下不发送此首部 (HTTPS->HTTP)。
  • unsafe-url:无论是同源申请还是非同源申请,都发送残缺的 URL(移除参数信息之后)作为援用地址。(最不平安了)

浏览器兼容性(https://caniuse.com/?search=r…):

如何设置 referer

  1. 在 HTML 里设置 meta 

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

    如下图:

  2. 或者用 \<a>、\<area>、\<img>、\<iframe>、\<script> 或者 \<link> 元素上的 referrerpolicy 属性为其设置独立的申请策略。
     
    如:
<script src='/javascripts/test.js' referrerpolicy="no-referrer"></script>



 

未加 referrerpolicy 属性的 link 元素:

盗链

盗链是指在本人的页面上展现一些并不在本人服务器上的一些内容,获取他人的资源地址,绕过他人的资源展现页面,间接在本人的页面上向最终用户提供此内容。个别被盗链的都是 图片、可执行文件、音视频文件、压缩文件 等资源。通过盗链的伎俩能够加重本人服务器的累赘

比方在本人页面里引入百度贴吧里的一张照片:

<body>
    <img src="https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg"> 
</body>

但实际上是无奈展现的(如下图),之所以无奈展现是因为百度的图片做过防盗链解决

防盗链的工作原理

通过 Referer 或者签名,网站能够检测指标网页拜访的起源网页,如果是资源文件,则能够追踪到显示它的网页地址 一旦检测到起源不是本站,即进行阻止或者返回指定的页面

绕过图片防盗链

那么当初的很多网站是如何利用 referer 来进行防图片盗链的呢?
三种状况下容许援用图片:

  1. 本网站。
  2. 无 referer 信息的状况。(服务器认为是从浏览器间接拜访的图片 URL,所以这种状况下能失常拜访)
  3. 受权的网址。

咱们只能从状况 2 动手,通过设置 referer 为空进行绕过防盗链。

利用 https 网站盗链 http 资源网站,refer 不会发送

先利用 openssl 生成自签名证书(具体可看 https://github.com/zxl9257686…)
client.js

let https = require("https");
let fs = require("fs");
let url = require("url");
let path = require("path");


var options = {
  hostname: "localhost",
  port: 8000,
  path: "/",
  method: "GET",
  rejectUnauthorized: false,
  key: fs.readFileSync("./keys/client.key"),
  cert: fs.readFileSync("./keys/client.crt"),
  ca: [fs.readFileSync("../ca/ca.crt")],
};

// 创立服务器
https.createServer(options, function (req, res) {let staticPath = path.join(__dirname, "src");
  let pathObj = url.parse(req.url, true);

  if (pathObj.pathname === "/") {pathObj.pathname += "index.html";}
  //  读取动态目录外面的文件,而后发送进来
  let filePath = path.join(staticPath, pathObj.pathname);
  fs.readFile(filePath, "binary", function (err, content) {if (err) {res.writeHead(404, "Not Found");
      res.end("<h1>404 Not Found</h1>");
    } else {res.writeHead(200, "Not Found");
      res.write(content, "binary");
      res.end();}
  });

}).listen(8080);

index.html

<div id="container">
    <img src="http://localhost:9999">
</div>

启动后果如下:
提醒:因为咱们应用了自签名的证书,拜访页面时可能会看到浏览器的证书正告,可能须要手动点击信赖以后证书,或者手动点击链接确认拜访该页面。例如 Chrome 揭示“您的连贯不是私密连贯”,并禁止你拜访。你能够间接在以后页面输出 thisisunsafe,不是在地址栏输出,而是间接敲击键盘输入,页面会主动刷新进入网页。

设置 meta

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

设置 referrerpolicy=”no-referrer”

以上已验证过,只是存在局部兼容性问题。

https://images.weserv.nl/?url=${你的图片地址}

因为网址是国外的速度有点慢成果还行,目标就是返回一个不受限制的图片,然而 GIF 格局会返回 jpg 也就是没有了动画成果。

利用 iframe 伪造申请 referer

内容参考 https://juejin.cn/post/684490…

function showImg(src, wrapper) {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 = 10
    iframe.height = 10
    iframe.scrolling = "no"
    iframe.frameBorder = "0"
    wrapper.appendChild(iframe)
}

showImg('https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg', document.querySelector('#container'))

后果如下:

客户端在申请时批改 header 头部

内容参考 https://juejin.cn/post/684490…

利用 XMLHttpRequest

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);
        // 通过 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 恰好就是保留字段之一,详情列表参考 Forbidden header name。

利用 fetch

// 将下载下来的二进制大对象数据转换成 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";


function fetchImage(url) {
    return fetch(url, {
        headers: {// "Referer": "", // 这里设置有效},
        method: "GET",  
        referrer: "", // 将 referer 置空
        // referrerPolicy: 'no-referrer', 
    }).then(response => response.blob());
}

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

通过将配置参数 referrer 置空,能够看见本次申请曾经不带 referer 了

或者设置 referrerPolicy 为 ”no-referrer”

服务器作防盗链图片直达

这里咱们应用 express
index.js

const express = require('express');
const app = express();

app.use('/img', require('./routers/index.js'))

app.listen(3000);

routers/index.js

var express = require('express');
var router = express.Router();
var request = require('request');

router.get('/', function(req, res, next) {
    var options = {
        method: 'GET',
        url: 'https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg',
        headers: {'Referer': '',}
    };
    request(options).pipe(res)
    
});

module.exports = router;

常见防盗链办法

防盗链个别有上面几种形式:

  1. 动静文件名,或者定期批改文件名称或门路
  2. 断定援用地址,个别是判断浏览器申请时 HTTP 头的 Referer 字段的值
  3. 应用登录验证,cookie
  4. 图片加水印

利用 nginx

ngx_http_referer_module 用于阻挡起源非法域名的申请 nginx 指令 valid_refers,全局变量 $invalid_refer 对资源的防盗链 nginx 配置为

location ~.*\.(gif|jpg|png|bmp|flv|swf|rar|zip)$
{
    valid_referers none blocked test.com *.test.com;   // 加 none 的目标是确保浏览器能够间接拜访资源
    if($invalid_referer)
    {
        #return 403;  // 间接返回 403
        rewrite ^/ http://www.test.com/403.jpg; // 返回指定提醒图片
    }
}

这种办法是在 server 或者 location 段中退出:valid_referers。这个指令在 referer 头的根底上为 $invalid_referer 变量赋值,其值为 0 或 1。如果 valid_referers 列表中没有 Referer 头的值,$invalid_referer将被设置为 1。
如果 $invalid_referer等于 1,在 if 语句中返回一个 403 给用户,这样用户便会看到一个 403 的页面, 如果应用上面的 rewrite,那么盗链的图片都会显示 403.jpg。
该指令反对 none 和 blocked:

  • 其中 none 示意空的去路,也就是间接拜访,比方间接在浏览器关上一个文件
  • blocked 示意被防火墙标记过的去路,*..com 示意所有子域名

然而传统的防盗链也会存在一些问题,因为 refer 是能够伪造的,所以能够应用加密签名的形式来解决这个问题。什么是加密签名?就是当咱们申请一个图片的时候,我要给它带一些签名过来,而后返回图片的时候咱们判断下签名是否正确,相当于对一个暗号。
更多内容请参考 https://zhuanlan.zhihu.com/p/…

服务器端判断 referer

咱们能通过比照 req.headers[‘referer’]和 req.url 中的 host 来确认资源申请是否是别的站点发来的。
接着,当咱们晓得了资源申请的起源,咱们就能通过一系列伎俩来决定是否响应申请以及怎么响应。
通常的做法是设置一个白名单,在白名单内的申请咱们就响应,否则就不响应。

let http = require("http");
let fs = require("fs");
let url = require("url");
let path = require("path");
// 白名单
const whiteList = ["localhost:8080"];

/**
 * 三种状况下容许援用图片:* 1. 本网站
 * 2. 无 referer 信息的状况。(服务器认为是从浏览器间接拜访的图片 URL,所以这种状况下能失常拜访)* 3. 受权的网址。(配置白名单)
 */

http
  .createServer(function (req, res) {let refer = req.headers["referer"] || req.headers["refer"];
    console.log('refer----', refer, req.url);
    res.setHeader("Access-Control-Allow-Origin", "*");
    if (refer) {let referHostName = url.parse(refer, true).host;
      let currentHostName = url.parse(req.url, true).host;
      console.log(referHostName, currentHostName, '--==')
      // 当 referer 不为空, 但 host 未能命中指标网站且不在白名单内时, 返回谬误的图
      if (
        referHostName != currentHostName &&
        whiteList.indexOf(referHostName) == -1
      ) {res.setHeader("Content-Type", "image/jpeg");
        fs.createReadStream(path.join(__dirname, "/src/img/403.jpg")).pipe(res);
        return;
      }
    }
    // 当 referer 为空时, 返回正确的图
    res.setHeader("Content-Type", "image/jpeg");
    fs.createReadStream(path.join(__dirname, "/src/img/1.jpg")).pipe(res);
    
  })
  .listen(9999);

利用 http 启动一个客户端:
client.js

let http = require("http");
let fs = require("fs");
let url = require("url");
let path = require("path");

// 创立服务器
http.createServer(function (req, res) {let staticPath = path.join(__dirname, "src");
  let pathObj = url.parse(req.url, true);

  if (pathObj.pathname === "/") {pathObj.pathname += "index.html";}
  //  读取动态目录外面的文件,而后发送进来
  let filePath = path.join(staticPath, pathObj.pathname);
  fs.readFile(filePath, "binary", function (err, content) {if (err) {res.writeHead(404, "Not Found");
      res.end("<h1>404 Not Found</h1>");
    } else {res.writeHead(200, "Not Found");
      res.write(content, "binary");
      res.end();}
  });
}).listen(8080);

index.html

<div id="container">
    <img src="http://localhost:9999">
</div>

别离启动客户端和服务器:

如果咱们批改下服务器端 whiteList:

// 白名单
const whiteList = [];

重启服务器端,拜访客户端后 咱们发现响应后果变成了 403 图片:

避免网址被 iframe

在页面底部或其它专用部位退出如下代码:

// 用 js 办法检测地址栏域名是不是以后网站绑定的域名,如果不是,则跳转到绑定的域名上来,这样就不怕网站被他人 iframe 了
if(window!=parent) {window.top.location.href = window.location.href;}

注:
以上代码地址:https://github.com/zxl9257686…

参考资料:

https://developer.mozilla.org…

https://developer.mozilla.org…

https://juejin.cn/post/684490…

https://juejin.cn/post/684490…

https://www.cnblogs.com/wangy…

正文完
 0