nginx配置检测及安全配置
推薦一個開源程序gixy,https://github.com/yandex/gixy ,作用是來檢測Nginx配置文件中存在的問題(不是nginx –t 檢測的語法問題)
$uri導致的CRLF注入漏洞
下面兩種情景十分常見:
用戶訪問http://example.com/aabbcc,自動跳轉到https://example.com/aabbcc 用戶訪問http://example.com/aabbcc,自動跳轉到http://www.example.com/aabbcc
第二個場景主要是為了統一用戶訪問的域名,更加有益于SEO優化。
在跳轉的過程中,我們需要保證用戶訪問的頁面不變,所以需要從Nginx獲取用戶請求的文件路徑。查看Nginx文檔,可以發現有三個表示uri的變量:
$uri $document_uri $request_uri
解釋一下,1和2表示的是解碼以后的請求路徑,不帶參數;3表示的是完整的URI(沒有解碼)。那么,如果運維配置了下列的代碼:
location / {
return 302 https://$host$uri;
}
因為$uri是解碼以后的請求路徑,所以可能就會包含換行符,也就造成了一個CRLF注入漏洞。(關于CRLF注入漏洞,可以參考我的老文章 https://www.leavesongs.com/PENETRATION/Sina-CRLF-Injection.html )
這個CRLF注入漏洞可以導致會話固定漏洞、設置Cookie引發的CSRF漏洞或者XSS漏洞。其中,我們通過注入兩個即可控制HTTP體進行XSS,但因為瀏覽器認為這是一個300跳轉,所以并不會顯示我們注入的內容。
這個情況下,我們可以利用一些技巧:比如使用CSP頭來iframe的地址,這樣瀏覽器就不會跳轉,進而執行我們插入的HTML:
關于上述利用方法,可以參考我的另一篇文章《Bottle HTTP 頭注入漏洞探究》。
如何修復這個CRLF漏洞?正確的做法應該是如下:
location / {
return 302 https://$host$request_uri;
}
另外,由$uri導致的CRLF注入漏洞不僅可能出現在上述兩個場景中,理論上,只要是可以設置HTTP頭的場景都會出現這個問題。
目錄穿越漏洞
這個常見于Nginx做反向代理的情況,動態的部分被proxy_pass傳遞給后端端口,而靜態文件需要Nginx來處理。
假設靜態文件存儲在/home/目錄下,而該目錄在url中名字為files,那么就需要用alias設置目錄的別名:
location /files {
alias /home/;
}
此時,訪問http://example.com/files/readme.txt,就可以獲取/home/readme.txt文件。
但我們注意到,url上/files沒有加后綴/,而alias設置的/home/是有后綴/的,這個/就導致我們可以從/home/目錄穿越到他的上層目錄:
進而我們獲得了一個任意文件下載漏洞。
這個有趣的漏洞出現在了Pwnhub上一期比賽《尋找 SNH48》中,@Ricter師傅的題目。
如何解決這個漏洞?只需要保證location和alias的值都有后綴/或都沒有這個后綴。
Http Header被覆蓋的問題
眾所周知,Nginx的配置文件分為Server、Location、If等一些配置塊,并且存在包含關系,和編程語言比較類似。如果在外層配置的一些選項,是可以被繼承到內層的。
但這里的繼承也有一些特性,比如add_header,子塊中配置后將會覆蓋父塊中的add_header添加的所有HTTP頭,造成一些安全隱患。
如下列代碼,Server塊添加了CSP頭:
server {
...
add_header Content-Security-Policy "default-src 'self'";
add_header X-Frame-Options DENY;
location = /test1 {
rewrite ^(.*)$ /xss.html break;
}
location = /test2 {
add_header X-Content-Type-Options nosniff;
rewrite ^(.*)$ /xss.html break;
}
}
但/test2的location中又添加了X-Content-Type-Options頭,導致父塊中的add_header全部失效:
此時,test2的csp就完全失效了,我們成功觸發XSS:
總結
Nginx配置文件造成的漏洞絕不止這三種,比如之前特別火的解析漏洞( https://github.com/phith0n/vulhub/tree/master/nginx_parsing_vulnerability ),也和Nginx的配置有一定關系。
解決這類漏洞,最根本的方法是仔細閱讀官方文檔,文檔里說明了很多配置文件錯誤和正確的用法。最忌去百度網上的一些解決方法,很多錯誤就是一傳十十傳百,最后流傳開來的。
另外,本文開頭提到的工具gixy,我們也可以利用起來,網站上線前進行一下掃描,也許就能發現一些可能存在的問題。
網站安全配置Nginx防止網站被攻擊
網站安全配置(Nginx)防止網站被攻擊(包括使用了CDN加速之后的配置方法)
Nginx 有 2 個模塊用于控制訪問“數量”和“速度”,簡單的說,控制你最多同時有 多少個訪問,并且控制你每秒鐘最多訪問多少次, 你的同時并發訪問不能太多,也不能太快,不然就“殺無赦”。
HttpLimitZoneModule 限制同時并發訪問的數量
HttpLimitReqModule 限制訪問數據,每秒內最多幾個請求
請先檢查你的 nginx 是否有這 2 個模塊,否則~額~沒戲,一邊墻角哭去吧~~~
1.普通配置(Nginx 官方例子)
## 用戶的 IP 地址 $binary_remote_addr 作為 Key,每個 IP 地址最多有 50 個并發連接
## 你想開 幾千個連接 刷死我? 超過 50 個連接,直接返回 503 錯誤給你,根本不處理你的請求了
limit_conn_zone $binary_remote_addr zone=TotalConnLimitZone:10m ;
limit_conn TotalConnLimitZone 50;
limit_conn_log_level notice;
## 用戶的 IP 地址 $binary_remote_addr 作為 Key,每個 IP 地址每秒處理 10 個請求
## 你想用程序每秒幾百次的刷我,沒戲,再快了就不處理了,直接返回 503 錯誤給你
limit_req_zone $binary_remote_addr zone=ConnLimitZone:10m rate=10r/s;
limit_req_log_level notice;
## 具體服務器配置
server {
listen 80; location ~ .php$ {
## 最多 5 個排隊, 由于每秒處理 10 個請求 + 5個排隊,你一秒最多發送 15 個請求過來,再多就直接返回 503 錯誤給你
limit_req zone=ConnLimitZone burst=5 nodelay;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
}
## 用戶的 IP 地址 $binary_remote_addr 作為 Key,每個 IP 地址最多有 50 個并發連接
## 你想開 幾千個連接 刷死我? 超過 50 個連接,直接返回 503 錯誤給你,根本不處理你的請求了
limit_conn_zone $binary_remote_addr zone=TotalConnLimitZone:10m ;
limit_conn TotalConnLimitZone 50;
limit_conn_log_level notice;
## 用戶的 IP 地址 $binary_remote_addr 作為 Key,每個 IP 地址每秒處理 10 個請求
## 你想用程序每秒幾百次的刷我,沒戲,再快了就不處理了,直接返回 503 錯誤給你
limit_req_zone $binary_remote_addr zone=ConnLimitZone:10m rate=10r/s;
limit_req_log_level notice;
## 具體服務器配置
server {
listen 80;
location ~ .php$ {
## 最多 5 個排隊, 由于每秒處理 10 個請求 + 5個排隊,你一秒最多發送 15 個請求過來,再多就直接返回 503 錯誤給你
limit_req zone=ConnLimitZone burst=5 nodelay;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
}
這樣一個最簡單的服務器安全限制訪問就完成了,這個基本上你 Google 一搜索能搜索到 90% 的網站都是這個例子,而且還強調用“$binary_remote_addr”可以節省內存之類的廢話
你 Google 不到的配置
普通用戶瀏覽器 —–> 360網站衛士加速(CDN,360防 CC,DOS攻擊) ——> 阿里云加速服務器(我們自己建的CDN,阿里云盾) —-> 源服務器(PHP 程序部署在這里,iptables, nginx 安全配置)
可以看到,我們的網站中間經歷了好幾層的透明加速和安全過濾, 這種情況下,我們就不能用上面的“普通配置”。因為上面基于 源IP的限制 結果就是,我們把 360網站衛士 或者 阿里云盾 給限制了,因為這里“源IP”地址不再是 普通用戶的IP,而是中間 網絡加速服務器 的IP地址。我們需要限制的是 最前面的普通用戶,而不是中間為我們做加速的 加速服務器。
現在我們面對的最直接的問題就是, 經過這么多層加速,我怎么得到“最前面普通用戶的 IP 地址”呢?
(這里只說明結果,不了解 Http 協議的人請自行 Google 或者 Wikipedia http://zh.wikipedia.org/zh-cn/X-Forwarded-For )
當一個 CDN 或者透明代理服務器把用戶的請求轉到后面服務器的時候,這個 CDN 服務器會在 Http 的頭中加入 一個記錄
X-Forwarded-For : 用戶IP, 代理服務器IP
如果中間經歷了不止一個 代理服務器,像 www.bzfshop.net 中間建立多層代理之后,這個 記錄會是這樣
X-Forwarded-For : 用戶IP, 代理服務器1-IP, 代理服務器2-IP, 代理服務器3-IP, ….
可以看到經過好多層代理之后, 用戶的真實IP 在第一個位置, 后面會跟一串 中間代理服務器的IP地址,從這里取到用戶真實的IP地址,針對這個 IP 地址做限制就可以了,
經過多層CDN之后取得原始用戶的IP地址,nginx 配置
取得用戶的原始地址Shell
map $http_x_forwarded_for $clientRealIp {
## 沒有通過代理,直接用 remote_addr
"" $remote_addr;
## 用正則匹配,從 x_forwarded_for 中取得用戶的原始IP
## 例如 X-Forwarded-For: 202.123.123.11, 208.22.22.234, 192.168.2.100,...
## 這里第一個 202.123.123.11 是用戶的真實 IP,后面其它都是經過的 CDN 服務器
~^(?P<firstAddr>[0-9.]+),?.*$ $firstAddr; }
## 通過 map 指令,我們為 nginx 創建了一個變量 $clientRealIp ,這個就是 原始用戶的真實 IP 地址,
## 不論用戶是直接訪問,還是通過一串 CDN 之后的訪問,我們都能取得正確的原始IP地址
map $http_x_forwarded_for $clientRealIp {
## 沒有通過代理,直接用 remote_addr
"" $remote_addr; ## 用正則匹配,從 x_forwarded_for 中取得用戶的原始IP
## 例如 X-Forwarded-For: 202.123.123.11, 208.22.22.234, 192.168.2.100,...
## 這里第一個 202.123.123.11 是用戶的真實 IP,后面其它都是經過的 CDN 服務器
~^(?P<firstAddr>[0-9.]+),?.*$ $firstAddr; }
## 通過 map 指令,我們為 nginx 創建了一個變量 $clientRealIp ,這個就是 原始用戶的真實 IP 地址, ## 不論用戶是直接訪問,還是通過一串 CDN 之后的訪問,我們都能取得正確的原始IP地址
測試、測試
很多時候,你在網上搜到一堆配置,你照著做了,但是你怎么知道這個配置真的正確 ?是的,我們需要自己做一個有效的真實的測試,驗證它是正確的之后才真的采用它
Nginx 這種配置怎么測試呢? 用 Echo 模塊,如果你知道 Nginx 這個模塊的話。
以 www.bzfshop.net 網站為例, 我們首先測試這個 $clientRealIp 是否真的是我們客戶機的 IP 地址,在網站上增加一個訪問地址,比如 www.bzfshop.net/nginx-test,配置如下:
給 Nginx 增加一個測試地址Shell
server {
listen 80; server_name www.bzfshop.net;
## 當用戶訪問 /nginx-test 的時候,我們輸出 $clientRealIp 變量,看看這個變量
## 值是不是真的 用戶源IP 地址
location /nginx-test { echo $clientRealIp;
}
}
server {
listen 80;
server_name www.bzfshop.net;
## 當用戶訪問 /nginx-test 的時候,我們輸出 $clientRealIp 變量,看看這個變量
## 值是不是真的 用戶源IP 地址
location /nginx-test { echo $clientRealIp;
}
}
接下來,用你的瀏覽器訪問 www.bzfshop.net/nginx-test,這個時候會彈出框下載一個文件 nginx-test,下載完成用 notepad++ 打開,里面就是一個 IP 地址
訪問 www.ip138.com ,看看這個里面記錄的IP地址是否和 ip138 偵測的IP 一致?
通過這種方式,你就可以對 Nginx 的一些復雜配置做有效的測試。
經過測試,我們確認 通過多層CDN 之后,$clientRealIp 仍然是有效的原始用戶IP地址
根據用戶的真實 IP 做連接限制
下面是修改之后的 Nginx 配置:
CDN環境下 Nginx 的安全配置Shell
## 這里取得原始用戶的IP地址
map $http_x_forwarded_for $clientRealIp {
"" $remote_addr;
~^(?P<firstAddr>[0-9.]+),?.*$ $firstAddr;
}
## 針對原始用戶 IP 地址做限制
limit_req_zone $clientRealIp zone=ConnLimitZone:20m rate=10r/s;
#limit_req zone=ConnLimitZone burst=10 nodelay;
limit_req_log_level notice;
## 具體服務器配置
server {
listen 80; location ~ .php$ {
## 最多 5 個排隊, 由于每秒處理 10 個請求 + 5個排隊,你一秒最多發送 15 個請求過來,再多就直接返回 503 錯誤給你
limit_req zone=ConnLimitZone burst=5 nodelay;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
}
## 這里取得原始用戶的IP地址
map $http_x_forwarded_for $clientRealIp {
"" $remote_addr;
~^(?P<firstAddr>[0-9.]+),?.*$ $firstAddr;
}
## 針對原始用戶 IP 地址做限制
limit_conn_zone $clientRealIp zone=TotalConnLimitZone:20m ;
limit_conn TotalConnLimitZone 50;
limit_conn_log_level notice;
## 針對原始用戶 IP 地址做限制
limit_req_zone $clientRealIp zone=ConnLimitZone:20m rate=10r/s;
#limit_req zone=ConnLimitZone burst=10 nodelay;
limit_req_log_level notice;
## 具體服務器配置
server {
listen 80; location ~ .php$ {
## 最多 5 個排隊, 由于每秒處理 10 個請求 + 5個排隊,你一秒最多發送 15 個請求過來,再多就直接返回 503 錯誤給你 limit_req zone=ConnLimitZone burst=5 nodelay;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
}
總結
以上是生活随笔為你收集整理的nginx配置检测及安全配置的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信用卡账单日选几号好?这么设置可享受最长
- 下一篇: 免费在线录屏工具