需要进行防盗链的绕过,我们必须先要了解Iframe、Referer和XMLHttpRequest对象的基本知识
目录
Iframe
基本用法
sandbox 属性
loading 属性
Referer
Referrer-policy
设置referrer的两种方法
下面举三个将referrer设置为no-referrer的例子:
首先是没有设置no-referrer的,即有referer
这里是referer1中使用方法1设置了no-referer
这里是referer1中使用方法2设置了no-referer
XMLHttpRequest
简介
盗链
防盗链的工作原理
为什么我们要绕过防盗链?
绕过防盗链的几种方式
利用HTTPS网站盗链http资源网站(HTTPS->HTTP)
利用在头部设置meta
利用referrerpolicy="no-referrer"
利用iframe伪造请求referer
利用XMLHttpRequest
利用fetch
Iframe
<iframe>
标签用于在网页里面嵌入其他网页。
基本用法
<iframe>
标签生成一个指定区域,在该区域中嵌入其他网页。它是一个容器元素,如果浏览器不支持<iframe>
,就会显示内部的子元素。
<iframe src="https://www.example.com"
width="100%" height="500" frameborder="0"
allowfullscreen sandbox> //这里的sandbox可以保证安全
<p><a href="https://www.example.com">点击打开嵌入页面</a></p>
</iframe>
上面的代码在当前网页嵌入https://www.example.com
,显示区域的宽度是100%
,高度是500
像素。如果当前浏览器不支持<iframe>
,则会显示一个链接,让用户点击。
浏览器普遍支持<iframe>
,所以内部的子元素可以不写。
iframe
的属性如下:
-
allowfullscreen
:允许嵌入的网页全屏显示,需要全屏 API 的支持,请参考相关的 JavaScript 教程。 -
frameborder
:是否绘制边框,0
为不绘制,1
为绘制(默认值)。建议尽量少用这个属性,而是在 CSS 里面设置样式。 -
src
:嵌入的网页的 URL。 -
width
:显示区域的宽度。 -
height
:显示区域的高度。 -
sandbox
:设置嵌入的网页的权限,详见下文。 -
importance
:浏览器下载嵌入的网页的优先级,可以设置三个值。high
表示高优先级,low
表示低优先级,auto
表示由浏览器自行决定。 -
name
:内嵌窗口的名称,可以用于<a>
、<form>
、<base>
的target
属性。 -
referrerpolicy
:请求嵌入网页时,HTTP 请求的Referer
字段的设置。参见<a>
标签的介绍。
sandbox 属性
嵌入的网页默认具有正常权限,比如执行脚本、提交表单、弹出窗口等。如果嵌入的网页是其他网站的页面,你不了解对方会执行什么操作,因此就存在安全风险。
为了限制<iframe>
的风险,HTML 提供了sandbox
属性,允许设置嵌入的网页的权限,等同于提供了一个隔离层,即“沙箱”。
sandbox
可以当作布尔属性使用,表示打开所有限制。
<iframe src="https://www.example.com" sandbox>
</iframe>
sandbox
属性可以设置具体的值,表示逐项打开限制。未设置某一项,就表示不具有该权限。
以最小权限为原则
注意,不要同时设置allow-scripts
和allow-same-origin
属性,这将使得嵌入的网页可以改变或删除sandbox
属性。
loading 属性
<iframe>
指定的网页会立即加载,有时这不是希望的行为。
<iframe>
滚动进入视口以后再加载,这样会比较节省带宽。
loading
属性可以触发<iframe>
网页的懒加载。
该属性可以取以下三个值:
-
auto
:浏览器的默认行为,与不使用loading
属性效果相同。 -
lazy
:<iframe>
的懒加载,即将滚动进入视口时开始加载。 -
eager
:立即加载资源,无论在页面上的位置如何。
<iframe src="https://example.com" loading="lazy"></iframe>
上面代码会启用<iframe>
的懒加载。
有一点需要注意,如果<iframe>
是隐藏的,则loading
属性无效,将会立即加载。
Referer
一定程度上防御crsf(客户端请求伪造)
Referer请求包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面的链接进入的。
Referrer-policy
Referer-policy作用就是为了控制请求头部中的referer的内容包含了以下信息:
-
no-referer:整个referer首部会被移除,访问来源信息不随着请求一起发送。
-
no-referer-when-downgrade:在没有指定任何策略的情况下用户代理的默认行为。在同安全等级的情况下,发送文件的源为引用地址(HTTPS->HTTPS);在降级的情况下不会发送此首部(HTTPS->HTTP)
-
origin:在任何情况下,仅发送文件的源作为引用地址
-
orgin-when-cross-origin:对于同源的请求,会发送完整的URL作为引用地址,但是对于非同源请求仅仅发送文件的源
-
same-orgin:对于同源的请求会发送引用地址,但是对于非的同源请求则不发送引用地址信息。
-
strict-origin:在同等安全级别的情况下,发送文件的源作为引用地址(HTPPS->HTTPS),但是在降级的情况下不会发送(HTTPS->HTTP)
-
strict-origin-cross-origin,对于同源的请求,会发送完整的URL作为引用地址;在同安全等级的情况下,发送文件的源为引用地址(HTTPS->HTTPS);在降级的情况下不会发送此首部(HTTPS->HTTP)
-
unsafe-url:无论是同源请求还是非同源请求,都发送完整的URL(移除参数信息后)作为引用地址。(最不安全了)
设置referrer的两种方法
方法1:在html页面中的head标签中的meta中设置
<meta name="referer" content="no-referer">
方法2:在引入script的script标签的属性中设置referrerpolicy="no-referer"
下面举三个将referrer设置为no-referrer的例子:
首先是没有设置no-referrer的,即有referer
(1)首先编写referer1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- noreferer测试 -->
<div>aaa</div>
</body>
</html>
这里并没有什么特别的,就是在页面中有一个div标签,其中写着aaa
(2)编写referer2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<h1>hello world</h1>
<a href="./referer1.html">123</a>
这里使用链接的形式访问test.html网页
<body>
</body>
</html>
这里使用a标签尝试将referer1.html插入到a标签中
(3)测试
在浏览器的URL中访问referer2.html
这里是referer1中使用方法1设置了no-referer
在referer中增加这样一行:
<meta name="referrer" content="no-referrer">
然后使用上面相同的方式查看是否有referer
可以看到,这里已经没有referer了
这里是referer1中使用方法2设置了no-referer
(1)修改referer1页面为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./test.js" Referrerpolicy="no-referrer">
//这里使用no-referrer禁用referrer
</script>
<!-- noreferer测试 -->
<div>aaa</div>
</body>
</html>
(2)然后我们新建一个名为test.js的文件
内容为:
alert(document.cookie);
(3)然后访问referer1.html
可以看到,没有referer,由于没有了referer我们也可以看到成功的弹窗了
XMLHttpRequest
简介
浏览器与服务器之间,采用 HTTP 协议通信。用户在浏览器地址栏键入一个网址,或者通过网页表单向服务器提交内容,这时浏览器就会向服务器发出 HTTP 请求。
XMLHttpRequest
对象是 AJAX 的主要接口,用于浏览器与服务器之间的通信。尽管名字里面有XML
和Http
,它实际上可以使用多种协议(比如file
或ftp
),发送任何格式的数据(包括字符串和二进制)。
XMLHttpRequest
本身是一个构造函数,可以使用new
命令生成实例。它没有任何参数。
var xhr = new XMLHttpRequest();//即初始化时没有任何参数
一旦新建实例,就可以使用open()
方法指定建立 HTTP 连接的一些细节。
xhr.open('GET', 'http://www.example.com/page.php', true);//open方法
上面代码指定使用 GET 方法,跟指定的服务器网址建立连接。
第三个参数true
,表示请求是异步的。
然后,指定回调函数,监听通信状态(readyState
属性)的变化。
xhr.onreadystatechange = handleStateChange;
function handleStateChange() {
// ...
}
上面代码中,一旦XMLHttpRequest
实例的状态发生变化,就会调用监听函数handleStateChange
最后使用send()
方法,实际发出请求。
xhr.send(null);
上面代码中,send()
的参数为null
,表示发送请求的时候,不带有数据体。
如果发送的是 POST 请求,这里就需要指定数据体。
一旦拿到服务器返回的数据,AJAX 不会刷新整个网页,而是只更新网页里面的相关部分,从而不打断用户正在做的事情。
注意:AJAX 只能向同源网址(协议、域名、端口都相同)发出 HTTP 请求,如果发出跨域请求,就会报错
盗链
盗链是指自己的页面上展示以下并不在自己服务器上的一些内容,获别人的资源地址,绕过别人的资源展示页面,直接在自己的页面上向最终用户提供此内容,一般被盗链的都是图片、可执行文件、音频文件、压缩文件等资源。
通过盗链的手段可以减轻自己服务器的负担。
防盗链的工作原理
通过referere或者签名,网站可以检测目标网页访问的来源网页,如果是资源文件,则可以追踪到显示它网页地址,一旦检测到来源不是本站点,即进行阻止或者返回指定的页面
为什么我们要绕过防盗链?
因为通过referere或者签名,网站可以检测目标网页访问的来源网页,如果是资源文件,则可以追踪到显示它网页地址,一旦检测到来源不是本站点,即进行阻止或者返回指定的页面
三种情况下允许引用图片:
-
本网站
-
无referer信息的情况下。(服务器认为从浏览器直接访问图片的图片的URL,所以这种情况下能正常访问)
-
授权的网址
我们可以在https的网页中用http请求另一个https网站的资源。
此时可以不发送我们的referer字段,达到绕过防盗链的效果(由于浏览器的升级,现在这种操作已经被禁止了)
实现原理:在访问图片时让其无referrer
绕过防盗链的几种方式
利用HTTPS网站盗链http资源网站(HTTPS->HTTP)
现在这种方式已经无法复现了
利用在头部设置meta
(1)首先在refrrer1文件中添加一行
<img src="https://img-blog.csdnimg.cn/5ff039c8a8cd47c98e26d694854e8962.jpeg" alt="你这图有问题啊">
(2)尝试访问
很明显图片没有加载出来,因为csdn网站将发现我们的referer并不是一个合法的网站,不会让我们加载
(3)那么我们就可以在meta中设置no-referrer的方式来尝试
<meta name="referrer" content="no-referrer">
(4)再次访问
成功访问
利用referrerpolicy="no-referrer"
注:这种方式就是在script添加这样
<script src="这里是一个js页面" Referrerpolicy="no-referrer">
一句,呈现的效果和上面的meta一样,这里不再赘述
利用iframe伪造请求referer
内容参考:如何绕开referrer防盗链 - 掘金 (juejin.cn)
(1)修改referrer1.html
<img src="https://t7.baidu.com/it/u=4162611394,4275913936&fm=193&f=GIF" alt="你这图有问题啊">
(2)编辑test.js
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 = 200
iframe.height = 200
iframe.scrolling = "no"
iframe.frameBorder = "0"
wrap.appendChild(iframe)
}
showImg('https://t7.baidu.com/it/u=4162611394,4275913936&fm=193&f=GIF', document.querySelector('#container'));
运行代码可以看见,通过这种方式也可以实现隐藏referer的功能,因此用作不支持referrerPolicy
的一种替代方案。
在某些不支持javascript内联运行的场景下,这种方案也是不可行的,比如在chrome扩展程序,由于content_security_policy
,使用内联JavaScript会报错
(3)测试
成功盗用!
利用XMLHttpRequest
XMLHttpRequest对象提供了setRequestHeader方法,用于向请求头添加或修改字段。我们能不能手动将修改 referer字段呢?
(1)test.js
// 通过ajax下载图片
function loadImage(uri) {
return new Promise(resolve => { //使用异步下载
let xhr = new XMLHttpRequest(); //实例化xmlhttprequest
xhr.responseType = "blob"; //响应类型为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) { //监听事件
let img = document.createElement('img');
img.src = evt.target.result;
document.getElementById('container').appendChild(img)
};
reader.readAsDataURL(blob);
}
const imgSrc = "https://profile-avatar.csdnimg.cn/8f0b72eabecc42df84ef2cce29ed569a_qq_68163788.jpg!1";
loadImage(imgSrc).then(blob => {
handleBlob(blob);
});
(2)referrer1.html
<div id="container">aaa</div>
<script src="./test.js"></script>
(3)测试
上述代码运行时会发现控制台提示错误
根据结果可以看出,浏览器拒绝将Rerrer置为空
可以看见setRequestHeader设置referer响应头是无效的,这是由于浏览器为了安全起见,无法手动设置部分保留字段,不幸的是Referer恰好就是保留字段之一,详情列表参考Forbidden header name。
可见使用xmlhttprequest提供的方法用AJAX同源请求无法完成这一操作。
使用fetch可以解决这一问题。
利用fetch
Fetch是浏览器提供的一个全新的接口,用于访问和操作HTTP管道部分,例如请求和响应,该接口支持referrerPolicy
,因此也可以用来操作referer。
// 将下载下来的二进制大对象数据转换成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://img-blog.csdnimg.cn/5ff039c8a8cd47c98e26d694854e8962.jpeg";
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);
});
通过将配置参数redirect
置位空,可以看见本次请求已经不带referer了。