1. 什么是referer?
跳转过去记得按一下f12点击网络请求详情,再刷新一下,就可以看见referer字段:
当我们尝试在浏览器内部直接输入这熟悉的网址时,此时刷新后则是这样一番景象:
于是你就明白了referer的基本用途,它是存在于http请求头内部的用于标识访问者来源网页的标识字段。通常在普通用户的访问下是不会出现的,常常出现于各个网页之间的相互跳转。
说到这里你想到了什么,各个网页?嗯…记得这块在网页里面引用别人的东西好像还挺多的。直接把人家的图片地址写下来,就能显示,可方便了。对,这种行为就是盗图,当然只要是可以在网页上访问的网络资源,基本上都会面临这样一种情况:被盗取资源。盗取链接与防止盗取链接形成了一个经久不衰的话题。那么今天我们就通过几个小例子来体会一下盗图与防盗图的斗争吧。好好的理解一下关于referer字段的故事。
Referer请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用Referer(注:
正确英语拼写应该是referrer,由于早期HTTP规范的拼写错误,为了保持向后兼容就一直延续下来
)请求头识别访问来源,可能会以此统计分析、日志记录以及缓存优化等。
真有人为了这事情发博客吐槽…哈哈 详细历史见吐槽内容
2. 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(移除参数信息之后)作为引用地址。(最不安全了)
3.设置referrer
可以在HTML里面设置meta
标签
<meta name="referrer" content="origin">
也可以用、、
2. 配置server端
server目录下创建server.js用于建立服务
let https = require("https");let fs = require("fs");let url = require("url");let path = require("path");// 白名单const whiteList = ["192.168.2.169:80"];const options = {key: fs.readFileSync("./keys/server.key"),cert: fs.readFileSync("./keys/server.crt"),};https.createServer(options, 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);
监听的是9999端口,用于模拟被偷服务器,在其对应的src目录下放上相应的资源
3.配置client端
服务端的nodejs文件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("404 Not Found
");} else {res.writeHead(200, "OK");res.write(content, "binary");res.end();}});}).listen(8080);
配置client首页文件
[root@blackstone client]# cat ./src/index.html<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>client</title></head><body><h1>client页面</h1><div id="container"><!-- <img src="https://192.168.2.169:9999/" referrerpolicy="no-referrer"> --><img src="http://192.168.2.169:9999"></div><!-- <script src="js/fetchImg.js"></script> --></body></html>
4.在浏览器上测试盗链情况
依次分别运行server和client
[root@blackstone server]# node server.js[root@blackstone client]# node client.js
使用火狐浏览器测试访问:
可以看到,尽管我们尝试进行盗链,但是因为浏览器的安全限定,无法显示偷出来的图片,我们掏出超低版本的浏览器试试。。。经测试暂时无果,这个古老的方案大抵是被禁用掉了。
点击此处获取老版本浏览器
4.2 使用meta
<meta name="referrer" content="no-referrer" />
可以清晰的看到,设置后发出的https请求已经没了referer字段,正常请求到了图片
4.3 设置referrerpolicy=“no-referrer”
在标签旁设置上这个属性就行
<img src="https://192.168.2.169:9999/" referrerpolicy="no-referrer">
4.4 利用iframe伪造请求referer
function showImg(src, wrapper ) {let url = new URL(src);let frameid = 'frameimg' + Math.random();window.img = `<img class="aligncenter" src="https://img.maxssl.com/uploads/?url=${url}" alt="图片加载失败,请稍后再试"/> `;// 构造一个iframeiframe = document.createElement('iframe')iframe.id = frameidiframe.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 = 10iframe.height = 10iframe.scrolling = "no"iframe.frameBorder = "0"wrapper.appendChild(iframe)}showImg('https://192.168.2.169:9999', document.querySelector('#container'))
4.5 客户端在请求时修改header头部
4.5.1 利用XMLHttpRequest
XMLHttpRequest中setRequestHeader方法,用于向请求头添加或修改字段。我们能不能手动将修改 referer字段呢?
演示代码:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>client</title></head><body><h1>client页面</h1><div id="container"></div></body><script src="./03.js"></script></html>
// 通过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);});
可以看见setRequestHeader设置referer响应头是无效的,这是由于浏览器为了安全起见,无法手动设置部分保留字段,不幸的是Referer恰好就是保留字段之一,详情列表参考Forbidden header name。
可见使用xmlhttprequest提供的方法用AJAX同源请求无法完成这一操作。使用fetch可以解决这一问题。
4.5.2 利用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://192.168.2.169:9999";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);});
可以看到这里的请求明显没了referer字段
4.6 服务器做图片中转
更加"刑"的方法就是直接搭建一个中转服务器,代理盗链者对目标资源进行正常请求,并将获取到的资源进行转发。
5.应对策略
当然,在上面罗列了那么多的绕过防盗链手法中,大部分的方法就是人为或者自动的取消掉自己的referer头部伪装成普通用户的正常访问去获取资源。要解决这个问题可以从以下几个方面去开展:
1.动态文件名,定期更换文件名称或者路径
2.判定引用地址,一般是判断浏览器请求时HTTP头的Referer字段的值
3.使用登录验证,cookie
4.图片加水印
5.可以购买一些安全服务对服务器的请求进行过滤