Nginx 防盗链配置
1. 什么是盗链?
百科的解释如下:
盗链是指服务提供商自己不提供服务的,通过技术手段绕过其它有利益的最终界面(如广告),直接在自己的网站上向最终提供其它服务提供商的服务,骗取最终的浏览和点击率。受益者不提供资源或提供很少的资源,而真正的服务提供商却得不到任何的收益。
盗链在如今的互联网世界无处不在,盗图,盗视频、盗等等,都是通过正规网站的、视频、等的 url 地址,直接放到自己网站上使用而未经授权。 盗资源是黑产界以最小成本最高利益的常用手段。比如笔者最近考虑买房,在贝壳网上有房源的真是户型图以及VR。某些房产中介直接会盗用贝壳网上的真实户型图来骗取点击。因此,对于任何大型网站而言,做好防盗措施,避免自身利益受损是至关重要的。 在代理这类静态资源(、视频、等)时,可以通过配置实现防盗连的。
2. 如何防盗链?
前面介绍到,盗链是直接使用正规网站保存、视频等的 URL 以相应的资源。最简单的防盗想法就是根据客户端请求资源时所携带的一些关键信息来验证请求的合法性,比如客户端 IP、请求 URL 中携带的 referer,如果不合法则直接拒绝请求。此外,由于这些基础信息都可以伪造,因此这样的基础手段也不一定安全。此外,还有、使用 cookie 等其他防盗连手段。另外,针对特定场景,比如流媒体直播中还有更为高级的防盗手段时间戳防盗链、swf 防盗链、回源鉴权防盗链等。
3. 中防盗链配置
用于实现防盗链的模块为 refer 模块,其依据的原理是: 如果网站盗用了你的,那么在点击或者查看这个盗链时,发送 http 请求的头部中的 referer 字段将为该盗版网站的 url。这样我们通过这个,知道 http 发起请求的,然后判断这个地址是否是我们的合法,不是则判断为盗链。 的 referer 模块中有3个指令,分别如下:
: referer_hash_bucket_size size;Default: referer_hash_bucket_size 64;Context: server, location: referer_hash_max_size size;Default: referer_hash_max_size 2048;Context: server, location: valid_referers none | blocked | server_names | string ...;Default: —Context: server, location
最重要的是 valid_referers 指令,它后面可以带上多个参数,表示多个 referer 头都是有效的。它的参数形式有:
none: 允许缺失 referer 头部的请求访问
blocked: 有 referer 这个字段,但是其值被防火墙或者是代理给了
server_names: 若 referer 中的域名和 server_names 中的某个域名匹配,则允许访问
任意字符或者正则表达式
会通过查看 referer 字段和 valid_referers 后面的 referer 列表进行匹配,如果匹配到了就将内置的变量$invalid_referer值设置为0,否则设置该值为1
这样简单的 防盗链配置如下:
... location / { valid_referers none blocked *.domain.pub www.domain.com/ server_names ~\.baidu\.; if ($invalid_referer) { return ; } return 200 "valid\n"; } ...
前面这种简单检查 referer 头部值的防盗链过于脆弱,盗用者很容易通过伪造 referer 的值轻而易举跳过防盗措施。在 中有一种更为高级的防盗方式,即基于 secure_link 模块,该模块能够检查请求的权限以及是否过期,多用于下载服务器防盗链。这个模块认未编译进 ,需要在源码编译时候使用 --with-secure_link_module 。
该模块的通过验证 URL 中的哈希值的方式防盗链。它的防盗过程如下:
由服务器或者 安全的加密后的 URL, 返回给客户端;
客户端使用安全的 URL 访问 ,等资源,由 的 secure_link 变量判断是否验证通过;
secure_link 模块中总共有3个指令,其格式和说明分别如下:
: secure_link expression;Default: —Context: http, server, location: secure_link_md5 expression;Default: —Context: http, server, location: secure_link_secret word;Default: —Context: location
通过配置 secure_link, secure_link_md5 指令,可实现对进行权限以及过期检查判断的。
和 referer 模块中的 $invalid_referer 变量一样,secure_link 模块也是通过内置变量 KaTeX parse error: Expected 'EOF', got '判' at position 14: secure\_link 判?断验证是否通过。secure_link 的值有如下三种情况:
空字符串: 验证不通过
0: URL 过期
1: 验证通过
通常使用这个模块进行 URL 校验,我们需要考虑的是如何合法的 URL ?另外,需要在 中做怎样的配置才可以校验这个 URL?
对于第问题,合法的 URL 和 指令 secure_link_md5 有关。例如:
secure_link_md5 "$secure_link_expires$uri$remote_addr secret";
如果 中secure_link_md5 是上述配置,那么合法 url 的命令如下:
# 2020-02-05 21:00:00 转换成时间戳为1580907600 echo -n '1580907600/test.png127.0.0.1 secret' | \ openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =
通过上述命令,我们得到了 md5 值:cPnjBG9bAZvY_jbPOj13mA,这个非常重要。接下来,构造合的 URL 和指令 secure_link 相关。如果 secure_link 指令的配置如下:
secure_link $arg_md5,$arg_expires;
那么我们的请求的 url 中必须带上 md5 和 expires 参数,例如:
http://180.76.152.113:9008/test.png?md5=cPnjBG9bAZvY_jbPOj13mA&expires=1580907600
对于 中的校验配置示例如下:
location ~* .(gif|jpg|png|swf|flv|mp4)$ { secure_link $arg_md5,$arg_expires; secure_link_md5 "$secure_link_expires$uri$remote_addr secret"; # 空字符串,校验不通过 if ($secure_link = "") { return ; } # 时间过期 if ($secure_link = "0") { return 410 "URL过期,请重新"; } root /root/test; }
在 的配置中,除了前面提到的 secure_link 和 secure_link_md5 指令外,我们对通过校验和校验失败的情况进行了处理。接下来请看实验部分。
4. 案例实战
在 .conf 中加入如下防盗配置:
... http { ... server { listen 9008; location / { valid_referers none blocked *.domain.pub www.domain.com/ server_names ~\.baidu\.; if ($invalid_referer) { return ; } return 200 "valid\n"; } } ... } ...
重新加载或者启动 后,我们进行如下操作:
[shen@shen Desktop]$ curl -H 'referer: http://www.domain.com/test' http://180.76.152.113:9008 <html> <head><title> Forbidden</title></head> <body> <center><h1> Forbidden</h1></center> <hr><center>/1.17.6</center> </body> </html> [shen@shen Desktop]$ curl -H 'referer: http://www.domain.com/' http://180.76.152.113:9008 valid [shen@shen Desktop]$ curl -H 'referer: ' http://180.76.152.113:9008 valid [shen@shen Desktop]$ curl http://180.76.152.113:9008 valid [shen@shen Desktop]$ curl -H 'referer: http://www.domain.pub/test' http://180.76.152.113:9008 valid
第 http 请求 referer 的值存在,但是没有匹配后面的域名,所以返回。其余的请求中 referer 值要么不存在,要么没有这个头部,要么匹配了后面的域名正则表达,都通过了 referer 校验,所以都返回 “valid” 字符串。我们通过构造不同的 referer 头部字段成功的绕过了 的referer 模块校验,也说明了这种防盗的方式极不靠谱。
我们准备静态, 名为 test.png,放到搭建了 的服务器上,全路径为 /root/test/test.png。
我们准备 配置如下:
... http { ... server { listen 8000; location / { # return 200 "$remote_addr"; root /root/test; } } server { listen 8001; location ~* .(jpg|png|flv|mp4)$ { secure_link $arg_md5,$arg_expires; secure_link_md5 "$secure_link_expires$uri$remote_addr secret"; # 空字符串,校验不通过 if ($secure_link = "") { return ; } # 时间过期 if ($secure_link = "0") { return 410; } # 校验通过,访问对的静态资源 root /root/test; } } } ...
首先,在浏览器上访问8000端口我们可以对应的 $remote_addr 变量值(打开 return 的注释配置),结果为103.46.244.69, 这是客户端请求时的对外 IP。访问浏览器上访问8000端口,URI=/test.png, 可以看到这个静态。
接下来,我们在访问8001端口,URI=/test.png时,可以发现返回,说明安全模块生效。
当前时间为2020年02月05日晚上9点半,我们找过期时间晚上10点,得到相应的时间戳为1580911200。按照 secure_link_md5 指令格式,使用如下 shell 命令 md5 值:
[shen@shen Desktop]$ echo -n '1580911200/test.png103.46.244.69 secret' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d = KnJx3J6fN_0Qc1W5TqEVXw
这样可以得到我们的安全访问 URL 为:
# 访问静态资源test.png的安全URL为: http://180.76.152.113:8001/test.png?md5=KnJx3J6fN_0Qc1W5TqEVXw&expires=1580911200
再次到浏览器上访问时候,我就可以看到静态了。
此外,我们还可以等到10点之后,测试过期后的结果。在过期之后再用这个 URL 访问时无法查看,而且返回的是 410 的状态码,这说明 成功检测到这个密钥值已经过期。
5. 小结
本节我们介绍了一些防盗链的知识,然后开始介绍 中的防盗链配置。一般的 防盗链手段都是通过 referer 字段来判断请求的来源地,由此去判定请求是否合法。但是该字段容易伪造,所以很少用该实现防盗。而 的 secure_link 模块主要是使用 hash 算法加密方式,一般用于、视频下载,下载 URL,安全性高。此外,我们也可以使用一些第三方的模块增强 的防盗链,比如常用的第三放模块ngx_http_accesskey_module 可用于实现下载的防盗。