1

一种新的可应对空白 referer 的防盗链策略

 2 years ago
source link: https://blog.wolfogre.com/posts/anti-hotlinking-without-referer/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一种新的可应对空白 referer 的防盗链策略

2018/09/12.

Blog 1.0k+ 2

本文介绍一种防盗链策略,可以帮助图床在收到空白 referer 请求时,仍能较准确地区分是正常请求还是盗链请求。该策略是我个人通过观察、实践、拍脑袋想出来的,目前已经在本博客全面应用。实际使用效果证明,该策略可以抵御常见的反反盗链方法,但尚不确定有没有某种方法可以破解,也不确定有没有误杀的情况,故仅供读者参考。

在介绍这个方法之前,我会从头讲起我的遭遇,既然你找到这里,说明你也有过类似的遭遇,不妨读一下,应当会产生共鸣。但如果你确实时间有限,想开门见山,请跳转到强化防盗链

起因?还能有啥子起因,不就是我的博客图床被人盗链了呗。

一直以来,我都没有在博客里标注过“禁止转载”或者“转载务必注明出处”等字样,也没有搞“本博客遵循 XX 国际开源许可协议,若违反协议使用本文将依法追究”什么的。不是因为我不在乎,而是因为我很清醒,这里是中国,没听说过一个小博客网站的文章被违规转载了,还能替自己维权成功的事情。

如果是君子,转载的时候一定会标明转载自哪儿,并迁移图片,防止对原博客图床造成压力;如果是小人,甚至是机器人(见《靠爬其他网站内容来充实自己——那些恶心人的网站》),它才不管那么多,哪怕你写了“未经授权转载死全家”,他也会熟视无睹,继续 Ctrl C + Ctrl V,这样一来,图片链接仍然会使用原网站的图床,于是,盗链便发生了。

我的一篇文章在我没有授权的情况下被转载了几次,被我发现了,我找过去看了一下,发现图片仍然用的我的图床,很明显是无脑复制粘贴过去的。

image

你无脑转载我管不了,但你用我的图床就别怪我心狠手辣了。咬咬牙,决定升级一下图床的防盗链策略。

我的图床是我自己维护的,其工作原理并不复杂。

为了方便且免费实现全站 HTTPS,我放弃了使用 CDN 来加速图片,毕竟我的博客还没有那么大的访问量。目前我的博客图床用的域名是 image.wolfogre.com,这不是一个 CDN 地址,而是直接指向了我的服务器,而服务器那边做的事也很简单,就是将图片请求转发到七牛云的云存储,拿到图片返回给客户端,拓扑结构大致是:

客户端 --[https 请求]--> image.wolfogre.com --[http 请求]--> 七牛云

为了避免用户直接访问七牛云上的图片,我在七牛云上配置了 IP 白名单,只有我的服务器才能访问资源。这样一来,客户端要访问我的图片,就必须要经过我的服务器。这就是我设下的关卡,我会在这个关卡上开启 HTTPS 加密,并把每一次请求都记录到日志,这样我就有了一个平台,来检测滥用、防止滥用。

现在,我要强化这个关卡上的防盗链策略了。

首先,我做了一张图:

image

没错,我不是要单纯地让盗链的请求失败,而是要盗链的请求拿回这张魔性的图,以示警告。

针对有 referer

初步策略就是检查图片请求的 referer,这个大家都容易想到。如果不是来自 *.wolfogre.com 则重定向到魔性图片上,实际效果也很明显:

image

大家在用一些图床、云存储、CDN 服务时,想必都看到过“设置 referer 白名单”的功能,我这里的实现与它们并无不同。

不过大家也应该注意到过,这个功能有个额外选项叫“是否允许空白 referer”,正常情况下,这选项一般选“是”,因为一旦禁止空白 referer 请求,用户将不能在浏览器中直接打开图片,下载图片也有可能出现问题,所以大多数的图床都是允许空白 referer 请求的,而这便是弱点所在。

如何隐藏 referer

有没有办法隐藏请求的 referer 以实现反反盗链?有的,而且别想象的要简单。

首先要说明的是,网上有很多教程教你怎么伪造 referer,比如在代码哪儿改改,header 里加点东西什么的,就能骗过服务端了。但这不是这里要讨论的,要知道,作为客户端想怎么欺骗服务端都是可以的,但这不是盗链,而是恶意请求。这里讨论的是作为服务端,如何诱使客户端去欺骗另一个服务端,比如网站 A 诱使访问它的浏览器伪造 referer 去访问网站 B。

但可以放心,但凡是个没中过病毒的正常浏览器,是绝不会允许伪造 referer 的。

所以伪造 referer 是行不通的,只能退而求其次,选择隐藏 referer。

网上有教一种使用 iframe 来隐藏图片请求 referer 的方法,虽然有效果,但其实根本不用那么麻烦。

HTML 原生就有关闭 referer 的指令,且大多数浏览器都支持,见 Referrer-Policy

我就碰到个这么干的,当我开启了 referer 检测之后,发现仍有个盗链的网站能够正常显示图片,原因就是它在 <img> 标签里声明了 no-referrer

<img alt="image" class="has" src="http://……" referrerPolicy="no-referrer">

啧啧,厉害厉害。看到这里,我恨不得一刀切拉倒:没有 referer 的请求统统判为盗链。

事实上,如果你懒得细究,我也建议你就禁止空白 referer 访问算了,毕竟你也看到了,隐藏 referer 去盗链图片,实现起来实在太简单了。

而如果你想细究,且看我想到的一个方法。

强化防盗链

首先,空白 referer 不一定就意味着盗链,我们得理一理哪些是敌军哪些是友军,再决定是拉意大利炮还是拉意大利面。

  • 敌军:使用各种手段隐藏 referer,在非我军的网页上使用我军的图片;
  • 友军:直接用浏览器访问图片或只用其他工具下载图片。

可见,其核心区别就是图片是不是放在 <img> 标签里的,而这正是在无 referer 的情况下,甄别请求是否来自盗链的关键。

Accept 是一个鲜被关注的请求头部,用来告知服务端哪些内容类型是客户端可以处理的,且有权重的概念,表示“客户端更期待服务端返回哪种类型”,很显然,<img> 标签引起的请求“更期待返回图片”,而直接访问地址的请求则“更期待返回 HTML”(浏览器无法提前知道目标是一张图片),而使用一些工具下载图片则倾向于没有任何期待,给啥都行。

以下是我做的几个实验,可以说明这个问题。

Chrome 浏览器因 <img> 标签而发起的请求:

Host: image.wolfogre.com
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: https://blog.wolfogre.com/about/
User-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

Chrome 浏览器直接访问图片地址而发起的请求:

Host: image.wolfogre.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
User-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

使用 curl 下载图片而发起的请求:

Host: image.wolfogre.com
Accept: */*
User-agent: curl/7.60.0

可见,相比于其他情况,因 <img> 标签而发起的请求明显地更倾向于“接收一张图片”,根据这一情况,修改后的防盗链策略为:

  1. 如果有 referer:

    1. referer 在白名单内,判定为正常请求;
    2. referer 不在白名单内,判定为盗链;
  2. 如果没有 referer:

    1. accept 内容最靠前的是“image”,则判定为盗链;
    2. 其他默认判定为正常请求。

以上只是策略的介绍,而具体怎么实施,要看图床是怎么实现的、使用的什么 HTTP 服务器。目前我的图床用的 HTTP 服务器是 Caddy,这里仅展示 Caddy 的配置,其他类型的 HTTP 服务器,比如 Nginx、Apache 可以参考实现:

https://image.wolfogre.com {
    root /opt/caddy/stc/image/ #静态文件位置,放魔性图片 anti-hotlinking.png
    redir 307 { #针对有 referer 的请求检查盗链
        if_op and
        if {path} not /anti-hotlinking.png #如果不是访问魔性图片
        if {>Referer}empty not empty #且 referer 非空
        if {>Referer} not_has wolfogre.com #且 referer 不是来自白名单
        / /anti-hotlinking.png #则重定向到魔性图片
    }
    redir 307 { #针对没有 referer 的请求检查盗链
        if_op and
        if {path} not /anti-hotlinking.png #如果不是访问魔性图片
        if {>Referer}empty is empty #且 referer 为空
        if {>Accept} starts_with image #且 accept 优先为 image
        / /anti-hotlinking.png #则重定向到魔性图片
    }
    proxy / http://cdn.image.wolfogre.com { 
        except /anti-hotlinking.png #除了访问魔性图片
        header_upstream Host cdn.image.wolfogre.com #其他访问都代理到七牛云
    }
}

误杀与漏杀

平心而论,这个策略相比于禁止空白 referer 访问,已经柔和了很多,只是在默认允许空白 referer 访问的基础上,针对特殊情况增加了防护策略,所以个人认为其误杀的可能性较低。

漏杀则是有可能的,如果用户使用的浏览器并没有按权重提供 Accept,服务端就可能将盗链误判为正常请求。但只要只要针对绝大多数浏览器能够正常判定为盗链,对盗链者来说已经是致命的打击了。

而这方法有没有其他漏洞,尚不得而知,我会持续观察日志,看有没有缺乏考虑的地方。

我的前端知识很有限,所以真心不敢保证这个策略能胜任在生产环境,所以如果读者深谙前端,发现了这个策略的破绽,欢迎指出,一起讨论。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK