Nginx开启防盗链

Nginx开启防盗链

年轻人 154 2022-10-29

一、referer模块防盗

Nginx 用于实现防盗链功能的模块为 refer 模块,其依据的原理是: 如果网站盗用了你的图片,那么用户在点击或者查看这个盗链内容时,发送 http 请求的头部中的 referer 字段将为该盗版网站的 url。这样我们通过获取这个头部信息,知道 http 发起请求的页面,然后判断这个地址是否是我们的合法页面,不是则判断为盗链。

Nginx 的 referer 模块中有3个指令,用法分别如下:(引自Nginx doc)

Syntax: referer_hash_bucket_size size;
Default:
referer_hash_bucket_size 64;
Context: server, location
This directive appeared in version 1.0.5.

Syntax: referer_hash_max_size size;
Default:
referer_hash_max_size 2048;
Context: server, location
This directive appeared in version 1.0.5.

Syntax: valid_referers none | blocked | server_names | string …;
Default: —
Context: server, location

valid_referers 指令,它后面可以带上多个参数,表示多个 referer 头都是有效的。它的参数形式有:

  • none : 允许缺失 referer 头部的请求访问
  • blocked : 有 referer 这个字段,但是其值被防火墙或者是代理给删除了
  • server_names : 若 referer 中的站点域名和 server_names 中的某个域名匹配,则允许访问
    任意字符或者正则表达式

Nginx 会通过查看 referer 字段和 valid_referers 后面的 referer 列表进行匹配,如果匹配到了就将内置的变量$invalid_referer值设置为0,否则设置该值为1

官方示例如下:

valid_referers none blocked server_names
               *.example.com example.* www.example.org/galleries/
               ~\.google\.;

if ($invalid_referer) {
    return 403;
}

接下来我们开始配置,首先在html目录下创建一个img文件夹,放入一张test.jpg图片(由于我是docker运行的nginx,所以我的目录位置在/Users/docker/nginx/html/img/下)

配置好nginx.conf,localhost/test.jpg我们是可以正常访问的,如下:

接下来配置referer模块

location ~* \.(gif|jpg|jpeg|png|bmp|webp) {
	#因为我们是通过浏览器直接发起请求,所以referer为空,所以这里不能配置none参数,也就是不允许确实referer头部
    valid_referers blocked server_names domain.com ~\.baidu\.;
    if ($invalid_referer) {
           add_header Content-Type 'text/html; charset=utf-8';
           return 200 "Hi bro , you don't have access";
          #return 403;
    }
    root $doc_root/img;
}

重启测试


进入if语句,成功拦截请求 或者我们打开控制台进行如下操作:
 ~/ curl -H 'referer: http://domain.com/test.jpg' http://localhost/test.jpg --output /Users/yangkai/Downloads/123.jpg
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 60674  100 60674    0     0  3063k      0 --:--:-- --:--:-- --:--:-- 4232k
 ~/ curl -H 'referer: http://www.baidu.com' http://localhost/test.jpg --output /Users/yangkai/Downloads/baidu.jpg
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 60674  100 60674    0     0  2154k      0 --:--:-- --:--:-- --:--:-- 2693k

可以看到第一个http请求 referer 的值存在,并且匹配成功domain.com,第二个http请求匹配了后面的域名正则表达,都通过了 referer 校验,所以都下载成功图片。所以从侧面也应证了我们可以通过构造不同的referer头部字段来绕过nginx的referer模块校验,说明这种防盗方式极不靠谱。

二、secure_link 防盗链测试

在使用nginx的secure_link_module模块前可以通过nginx -V查看是否安装模块,如下:


Nginx 的 secure_link_module 模块中有3个指令,用法分别如下:(引自Nginx doc)

Syntax: secure_link expression;
Default: —
Context: http, server, location

Syntax: secure_link_md5 expression;
Default: —
Context: http, server, location

Syntax: secure_link_secret word;
Default: —
Context: location

通过文档中的例子可以得知,secure_link_secret word;这个参数 是用于非日期校验的,可以先不管,这里配置好nginx.conf

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 403;
       }
                
       # 时间过期
       if ($secure_link = "0") {
                return 410;
       }
                
      # 校验通过,访问对的静态资源
      root $doc_root/img;
}

重启nginx,这个时候我们访问test.jpg,可以发现403 Forbidden,说明安全模块生效。
当前时间为2022年10月29日02:38:43,我们找一个三点的过期时间,得到相应的时间戳为1666983600。按照 secure_link_md5 指令格式,使用如下 shell 命令生成 md5 值:

#这里的172.17.0.1是我docker容器中的网关,这里其实就是$remote_addr的值,
#因为我的nginx在dokcer容器中启动的,相当于走了一层代理,所以ip是172.17.0.1,
#如果是部署在linux下,这里的值应当为127.0.0.1
echo -n '1666983600/test.jpg172.17.0.1 secret' | \
    openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =
1kfMlSx5Y1pGKF6JyIyo3g

这样可以得到我们的安全访问 URL 为:

localhost/test.jpg?md5=1kfMlSx5Y1pGKF6JyIyo3g&expires=1666983600

再次到浏览器上访问时候,我就可以看到静态图片了。


此外,我们还可以等到3点之后,测试过期后的结果。在过期之后再用这个 URL 访问时无法查看图片,而且返回的是 410 的状态码,这说明 Nginx 成功检测到这个密钥值已经过期。