上一篇文章講了 Nginx 中的變數和運行原理,下麵就來說一個主要提供變數並修改變數的值的模塊,也就是我們要講的防盜鏈模塊:referer 模塊。 簡單有效的防盜鏈手段 場景 如果做過個人站點的同學,可能會遇到別人盜用自己站點資源鏈接的情況,這就是盜鏈。說到盜鏈就要說一個 HTTP 協議的 頭部,r ...
上一篇文章講了 Nginx 中的變數和運行原理,下麵就來說一個主要提供變數並修改變數的值的模塊,也就是我們要講的防盜鏈模塊:referer 模塊。
簡單有效的防盜鏈手段
場景
如果做過個人站點的同學,可能會遇到別人盜用自己站點資源鏈接的情況,這就是盜鏈。說到盜鏈就要說一個 HTTP 協議的 頭部,referer 頭部。當其他網站通過 URL 引用了你的頁面,用戶在瀏覽器上點擊 URL 時,HTTP 請求的頭部會通過 referer 頭部將該網站當前頁面的 URL 帶上,告訴伺服器本次請求是由誰發起的。
例如,在谷歌中搜索 Nginx 然後點擊鏈接:
在打開的新頁面中查看請求頭會發現,請求頭中包含了 referer 頭部且值是 https://www.google.com/。
像谷歌這種我們是允許的,但是有一些其他的網站想要引用我們自己網站的資源時,就需要做一些管控了,不然豈不是誰都可以拿到鏈接。
目的
這裡目的其實已經很明確了,就是要拒絕非正常的網站訪問我們站點的資源。
思路
- invalid_referer 變數
- referer 提供了這個變數,可以用來配置哪些 referer 頭部合法,也就是,你允許哪些網站引用你的資源。
referer 模塊
要實現上面的目的,referer 模塊可得算頭一號,一起看下 referer 模塊怎麼用的。
- 預設編譯進 Nginx,通過
--without-http_referer_module
禁用
referer 模塊有三個指令,下麵看一下。
Syntax: valid_referers none | blocked | server_names | string ...;
Default: —
Context: server, location
Syntax: referer_hash_bucket_size size;
Default: referer_hash_bucket_size 64;
Context: server, location
Syntax: referer_hash_max_size size;
Default: referer_hash_max_size 2048;
Context: server, location
valid_referers
指令,配置是否允許 referer 頭部以及允許哪些 referer 訪問。referer_hash_bucket_size
表示這些配置的值是放在哈希表中的,指定哈希表的大小。referer_hash_max_size
則表示哈希表的最大大小是多大。
這裡面最重要的是 valid_referers
指令,需要重點來說明一下。
valid_referers 指令
可以同時攜帶多個參數,表示多個 referer 頭部都生效。
參數值
- none
- 允許缺失 referer 頭部的請求訪問
- block:允許 referer 頭部沒有對應的值的請求訪問。例如可能經過了反向代理或者防火牆
- server_names:若 referer 中站點功能變數名稱與 server_name 中本機功能變數名稱某個匹配,則允許該請求訪問
- string:表示功能變數名稱及 URL 的字元串,對功能變數名稱可在首碼或者尾碼中含有 * 通配符,若 referer 頭部的值匹配字元串後,則允許訪問
- 正則表達式:若 referer 頭部的值匹配上了正則,就允許訪問
invalid_referer 變數
- 允許訪問時變數值為空
- 不允許訪問時變數值為 1
實戰
下麵來看一個配置文件。
server {
server_name referer.ziyang.com;
listen 80;
error_log logs/myerror.log debug;
root html;
location /{
valid_referers none blocked server_names
*.ziyang.com www.ziyang.org.cn/nginx/
~\.google\.;
if ($invalid_referer) {
return 403;
}
return 200 'valid\n';
}
}
那麼對於這個配置文件而言,以下哪些請求會被拒絕呢?
curl -H 'referer: http://www.ziyang.org.cn/ttt' referer.ziyang.com/
curl -H 'referer: http://www.ziyang.com/ttt' referer.ziyang.com/
curl -H 'referer: ' referer.ziyang.com/
curl referer.ziyang.com/
curl -H 'referer: http://www.ziyang.com' referer.ziyang.com/
curl -H 'referer: http://referer.ziyang.com' referer.ziyang.com/
curl -H 'referer: http://image.baidu.com/search/detail' referer.ziyang.com/
curl -H 'referer: http://image.google.com/search/detail' referer.ziyang.com/
我們需要先來解析一下這個配置文件。valid_referers
指令配置了哪些值呢?
valid_referers none blocked server_names
*.ziyang.com www.ziyang.org.cn/nginx/
~\.google\.;
- none:表示沒有 referer 的可以訪問
- blocked:表示 referer 沒有值的可以訪問
- server_names:表示本機 server_name 也就是 referer.ziyang.com 可以訪問
- *.ziyang.com:匹配上了正則的可以訪問
- www.ziyang.org.cn/nginx/:該頁面發起的請求可以訪問
- ~\.google\.:google 前後都是正則匹配
下麵就實際看下響應:
# 返回 403,沒有匹配到任何規則
➜ ~ curl -H 'referer: http://www.ziyang.org.cn/ttt' referer.ziyang.com/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
➜ ~ curl -H 'referer: http://image.baidu.com/search/detail' referer.ziyang.com/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
# 匹配到了 *.ziyang.com
➜ ~ curl -H 'referer: http://www.ziyang.com/ttt' referer.ziyang.com/
valid
➜ ~ curl -H 'referer: http://www.ziyang.com' referer.ziyang.com/
valid
# 匹配到了 server name
➜ ~ curl -H 'referer: http://referer.ziyang.com' referer.ziyang.com/
valid
# 匹配到了 blocked
➜ ~ curl -H 'referer: ' referer.ziyang.com/
valid
# 匹配到了 none
➜ ~ curl referer.ziyang.com/
valid
# 匹配到了 ~\.google\.
➜ ~ curl -H 'referer: http://image.google.com/search/detail' referer.ziyang.com/
valid
防盜鏈另外一種解決方案:secure_link 模塊
referer 模塊是一種簡單的防盜鏈手段,必須依賴瀏覽器發起請求才會有效,如果攻擊者偽造 referer 頭部的話,這種方式就失效了。
secure_link 模塊是另外一種解決的方案。
它的主要原理是,通過驗證 URL 中哈希值的方式防盜鏈。
基本過程是這個樣子的:
- 由伺服器(可以是 Nginx,也可以是其他 Web 伺服器)生成加密的安全鏈接 URL,返回給客戶端
- 客戶端使用安全 URL 訪問 Nginx,由 Nginx 的 secure_link 變數驗證是否通過
原理如下:
- 哈希演算法是不可逆的
- 客戶端只能拿到執行過哈希演算法的 URL
- 僅生成 URL 的伺服器,驗證 URL 是否安全的 Nginx,這兩者才保存原始的字元串
- 原始字元串通常由以下部分有序組成:
- 資源位置。如 HTTP 中指定資源的 URI,防止攻擊者拿到一個安全 URI 後可以訪問任意資源
- 用戶信息。如用戶的 IP 地址,限制其他用戶盜用 URL
- 時間戳。使安全 URL 及時過期
- 密鑰。僅伺服器端擁有,增加攻擊者猜測出原始字元串的難度
模塊:
- ngx_http_secure_link_module
- 未編譯進 Nginx,需要通過 --with-http_secure_link_module 添加
- 變數
- secure_link
- secure_link_expires
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
- 值為空字元串:驗證不通過
- 值為 0:URL 過期
- 值為 1:驗證通過
- secure_link_expires
- 時間戳的值
命令行生成安全鏈接
- 生成 md5
echo -n '時間戳URL客戶端IP密鑰' | openssl md5 -binary | openssl base64 | tr +/ - | tr -d =
- 構造請求 URL
/test1.txt?md5=md5生成值&expires=時間戳(如 2147483647)
Nginx 配置
- secure_link $arg_md5,$arg_expires;
- secure_link 後面必須跟兩個值,一個是參數中的 md5,一個是時間戳
- secure_link_md5 "$secure_link_expires$uri$remote_addr secret";
- 按照什麼樣的順序構造原始字元串
實戰
下麵是一個實際的配置文件,我這裡就不做演示了,感興趣的可以自己做下實驗。
server {
server_name securelink.ziyang.com;
listen 80;
error_log logs/myerror.log info;
default_type text/plain;
location /{
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;
}
return 200 '$secure_link:$secure_link_expires\n';
}
location /p/ {
secure_link_secret mysecret2;
if ($secure_link = "") {
return 403;
}
rewrite ^ /secure/$secure_link;
}
location /secure/ {
alias html/;
internal;
}
}
僅對 URI 進行哈希的簡單辦法
除了上面這種相對複雜的方式防盜鏈,還有一種相對簡單的防盜鏈方式,就是只對 URI 進行哈希,這樣當 URI 傳
- 將請求 URL 分為三個部分:/prefix/hash/link
- Hash 生成方式:對 “link 密鑰” 做 md5 哈希
- 用
secure_link_secret secret;
配置密鑰
命令行生成安全鏈接
- 原請求
- link
- 生成的安全請求
- /prefix/md5/link
- 生成 md5
echo -n 'linksecret' | openssl md5 –hex
Nginx 配置
secure_link_secret secret;
這個防盜鏈的方法比較簡單,那麼具體是怎麼用呢?大家都在網上下載過資源對吧,不管是電子書還是軟體,很多網站你點擊下載的時候往往會彈出另外一個頁面去下載,這個新的頁面其實就是請求的 Nginx 生成的安全 URL。如果這個 URL 被拿到的話,其實還是可以用的,所以需要經常的更新密鑰來確保 URL 不會被盜用。
今天這篇文章詳細講了防盜鏈的具體用法,最近的這兩篇文章都是說的已有的變數用法,下一篇文章講一下怎麼生成新的變數。
本文首發於我的個人博客:iziyang.github.io,所有配置文件我已經放在了 Nginx 配置文件,大家可以自取。