寫在前面的話
nginx 中主要的內容在前面的章節其實已經差不多了,接下都是一些小功能的實現以及關於 nginx 的優化問題。我們一起來探討以下,如何把我們的 nginx 打造成為企業級應用。
安全優化:隱藏版本號和服務名稱
我們在使用 curl 命令請求 nginx 的時候,甚至我們在訪問出現 404 的時候,都會打印出我們的服務名稱/版本號,如圖:
WEB 訪問:
這肯定是不好的,知道了版本號意味着黑客就能通過指定版本的漏洞對我們的服務器進行攻擊,甚至知道了你是啥服務也能夠針對性攻擊,如果是小漏洞還好,如果是大漏洞就炸穿。
解決辦法如下:
修改 /data/services/nginx/conf/fastcgi.conf:
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
該配置就是 curl 那里顯示的 nginx/1.16.0,我們只需要把這個改成想要字符:
fastcgi_param SERVER_SOFTWARE apache;
並且在主配置文件中加入配置:
...
http {
server_tokens off;
...
}
...
比如我們把它改成 apache 后重載配置:
WEB 訪問:
此時發現版本號確實被我們隱藏,但是服務器類型並沒有因為我們改為 apache 而成功。
如果像修改服務器名稱,則需要修改源碼包中的:
1. 修改 /data/packages/nginx/nginx-1.16.0/src/core/nginx.h
2. 修改 /data/packages/nginx/nginx-1.16.0/src/http/ngx_http_header_filter_module.c
3.修改 /data/packages/nginx/nginx-1.16.0/src/http/ngx_http_special_response.c
由於我們並不是添加插件或者編譯參數,所以回到 nginx 源碼根目錄直接編譯:
# 編譯 cd /data/packages/nginx/nginx-1.16.0 make # 備份 mv /data/services/nginx/sbin/nginx /data/backup/nginx/nginx_$(date +%F) # 更新 cp /data/packages/nginx/nginx-1.16.0/objs/nginx /data/services/nginx/sbin/ # 查看 /data/services/nginx/sbin/nginx -V
此時可以看到我們查了 nginx 的信息為:
重載配置,curl 測試:
訪問錯誤頁面:
可以看到我們第三步刪除了默認網頁中輸出的那行代碼,直接隱藏了服務器信息。這才是我們想要的。
安全優化:連接限制
大流量攻擊一直就是互聯網中比較常見的一種攻擊,最為簡單的就是黑客使用同一 IP 風控的對服務器發起請求或者傳輸文件導致服務器帶寬被耗盡,進而影響正常用戶的使用。
針對這樣類似的攻擊,nginx 是提供了 limit 模塊用於對用戶請求進行約束的。
也就是模塊:http_limit_conn_module(默認編譯)
參數 | 說明 |
---|---|
limit_conn_zone | 描述會話狀態存儲區域,只能在 http 段 配置段:http 語法:limit_conn_zone $variable zone=name:size; |
limit_zone | 和 limit_conn_zone 同等意思,已被棄用 |
limit_conn_log_level | 當達到最大限制連接數后,記錄日志的等級 配置段:http, server, location 語法:limit_conn_log_level info | notice | warn | error |
limit_conn | 指定每個給定鍵值的最大同時連接數,當超過時返回 503 配置段:http, server, location 語法:limit_conn zone_name number |
limit_conn_status | 指定當超過限制時,返回的狀態碼,默認是 503 配置段:http, server, location 語法:limit_conn_status code; |
limit_rate | 對每個連接的速率限制。參數 rate 的單位是字節/秒,設置為 0 將關閉限速 配置段:http, server, location, if in location 語法:limit_rate rate 所謂的限制網速,其實質就是限制該 IP 每個線程的網速 |
另外需要補充幾點:
1. 使用 limit_conn_zone 如:limit_conn_zone $binary_remote_addr zone=addr:10m;
說明:$binary_remote_addr 變量的固定長度是 4 個字節,我們定義了一個叫 addr 的空間,並給了這個空間 10M 的共享內存。如果是 32 位的平台上,1M 共享內存能夠保存 3.2 萬個 $binary_remote_addr 產生的存儲狀態,64 位則為 1.6 萬。如果訪問量過多導致超出了這個共享內存,新的訪問則會被返回 503 錯誤。
2. 使用 limit_conn 來限制每個 IP 的並發,例如限制每個 IP 只能有一個活動連接:
limit_conn_zone $binary_remote_addr zone=client_addr:10m; server { location / { limit_conn client_addr 1; } }
3. 我們也可以限制限制單個 IP 的活動連接數的同時也限制單個虛擬主機的總連接數:
limit_conn_zone $binary_remote_addr zone=client_addr:10m; limit_conn_zone $server_name zone=server_addr:10m; server { location / { limit_conn client_addr 10; limit_conn server_addr 100; } }
比如這樣允許每個 IP 開 10 個連接,總共支持 100 個連接。
說了這么多,我們來做做限制測試:limit-demo.conf
limit_conn_zone $binary_remote_addr zone=client_addr:10m; limit_conn_zone $server_name zone=server_addr:10m; server { listen 10003; server_name localhost; location ^~ /download { charset utf-8; alias /data/files/share; # 限制 limit_conn client_addr 2; limit_conn server_addr 2; limit_rate 200k; autoindex on; autoindex_exact_size on; autoindex_localtime on; if ($request_filename ~* ^.*?\.(txt|log|pdf|doc|docx|xls|xlsx|ppt|pptx|rar|zip|tar.gz|tar.xz|bz2|iso)$ ) { add_header Content-Disposition attachment; } } }
我們允許每個 IP 開兩個連接,且服務器只支持 2 個連接,限速每個連接下載速率 200K。
訪問測試:
點擊兩個下載:
可以看到限速到了 200K 每秒的下載速度!
此時已經占用了兩個連接,我們刷新頁面:
發現無法處理第三個請求,報錯 503!
我們去其它服務器上面下載:
還是報錯 503,因為我們只支持 2 個連接!你要是想使用只能等着別人處理完斷開。
安全優化:請求限制
上文中的限制下載,請求數能夠滿足我們的一部分需求,但是某些時候我們還要一些特殊的需求,比如限制服務器每秒鍾只能處理多少請求,請求的處理速率問題,這就需要另外一個限制模塊。
一次性限制連接池終究還是太狠了點,我們換一種更加溫柔的方式來進行限制,直接讓請求者每秒鍾只能請求處理這么多次。
模塊:http_limit_req_module(默認編譯)
參數 | 說明 |
---|---|
limit_req_zone | 設置一塊共享內存限制域用來保存鍵值的狀態參數。 配置段:http 語法:limit_req_zone $variable zone=name:size rate=rate; |
limit_req_log_level | 設置日志級別。 配置段:http, server, location 語法:limit_req_log_level info | notice | warn | error; |
limit_req_status | 設置拒絕請求的響應狀態碼,默認 503。 配置段:http, server, location 語法:limit_req_status code; |
limit_req | 設置對應的共享內存限制域和允許被處理的最大請求數閾值。 配置段:http, server, location 語法:limit_req zone=name [burst=number] [nodelay]; |
配置說明:
1. limit_req_zone 和前面的配置類似,定義共享內存空間,但是相對於 limit_conn_zone 多了處理速度,比如:
limit_req_zone $binary_remote_addr zone=req_client_addr:10m rate=1r/s;
在添加 rate,但是必須是整數,所以 2 秒處理一個只能寫為 30r/m。
2. limit_req 的使用我們舉個例子,限制每秒處理請求不超 1 個,且排隊的不超過 5 個。
limit_req_zone $binary_remote_addr zone=req_client_addr:10m rate=1r/s; server { location / { limit_req zone=req_client_addr burst=5; } }
如果希望超過的請求不被延遲,可以在最后加 nodelay 參數:
limit_req zone=req_client_addr burst=5 nodelay;
我們做個測試驗證一下:req-limit-demo.conf
limit_req_zone $binary_remote_addr zone=req_client_addr:10m rate=6r/m; server { listen 10004; server_name localhost; location ^~ /download { charset utf-8; alias /data/files/share; # 限制 limit_req zone=req_client_addr burst=2; fancyindex on; fancyindex_exact_size on; fancyindex_localtime on; if ($request_filename ~* ^.*?\.(txt|log|pdf|doc|docx|xls|xlsx|ppt|pptx|rar|zip|tar.gz|tar.xz|bz2|iso)$ ) { add_header Content-Disposition attachment; } } }
我們配置每個來源 IP 處理速率為 10 秒一個請求,訪問測試:
連續刷新:
可以看到請求卡住了,需要等待幾秒鍾。准確其實是 10 秒。
放在 linux 上面測試:
while true; do curl -I http://192.168.100.111:10004/download/;done
請求結果:
可以看到兩次請求成功的時間間隔 10 秒,這里就很准確了。
同時我們在這台機器上面再新開兩個窗口允許同樣的請求,再度運行這個命令會發現也會繼續執行。
但是當我門總共開到第三個窗口運行這個命令的時候:
由於是死循環,所以瘋狂的返回 503,這就是我們配置 burst 的作用。
我們加上 nodelay 參數,然后使用新命令訪問:
while true; do curl -I http://192.168.100.111:10004/download/; sleep 1;done
我們每秒請求一次:
可以發現,請求立即返回,因為 burst 的原因,前 3 個請求都完全沒問題,到第 4 個也就是第 4 秒的時候返回 503,后面幾個 503 之后會重新 200,如此循環。
這就是 nodelay 的作用,直接返回,不去等待。
安全優化:訪問控制
我們這里所說的訪問控制其實就是來源的 IP,在 nginx 我們可以設置類似白名單這樣的東西,只允許某些 IP 或者網段訪問或者拒絕訪問。
這其實是我們針對攻擊者最狠的方式,直接禁止他的 IP 訪問我們的服務。
同樣,在內網中或者外網中,我們只希望某些人能夠訪問,也能進行 IP 限制來解決。
相對來說,這是一個非常非常實用的功能,依賴於:ngx_http_access_module 模塊,默認安裝
主要就兩個參數:
參數 | 說明 |
---|---|
allow | 允許某個 ip 或者一個 ip 段訪問,如果指定 unix: ,那將允許 socket 的訪問。 配置段:http, server, location, limit_except 語法:allow address | CIDR | unix: | all; |
deny | 和 allow 相反。 配置段:http, server, location, limit_except 語法:deny address | CIDR | unix: | all; |
具體格式如下:
location / { deny 192.168.1.1; allow 192.168.1.0/24; allow 10.1.1.0/16; allow 2001:0db8::/32; deny all; }
測試新增配置:allow-deny-demo.conf
server { listen 10005; server_name localhost; default_type text/html; location / { allow 192.168.100.112; deny all; root /data/www/demo-80; index index.html index.htm; } }
使用 112 機器訪問:
使用 113 訪問:
安全優化:限制白名單
nginx 的白名單的實現依賴於:http_geo_module 和 http_map_module 模塊,這兩個模塊都是默認安裝的。
我們添加配置:whitelist-demo.conf
geo $remote_addr $grant { default 1; 127.0.0.1 0; 192.168.100.112 0; } server { listen 10007; server_name localhost; location ^~ /download { alias /data/files/share; charset utf-8; fancyindex on; fancyindex_exact_size off; fancyindex_localtime on; if ( $grant = 1 ){ return 403; } if ($request_filename ~* ^.*?\.(txt|log|pdf|doc|docx|xls|xlsx|ppt|pptx|rar|zip|tar.gz|tar.xz|bz2|iso)$ ) { add_header Content-Disposition attachment; } } }
說明:
1. 我們對來源的用戶 IP 進行映射,如果為 1,則返回 403,則默認不在白名單的都為 1。
2. 在 location 中對來源的映射值進行判斷,返回 403。
訪問測試:
不在白名單的服務器:
訪問測試配置完成!
修改配置:whitelist-demo.conf 實現限速白名單
geo $whiteiplist { default 1; 127.0.0.1 0; 192.168.100.112 0; } map $whiteiplist $limit { 1 $binary_remote_addr; 0 ""; } limit_conn_zone $limit zone=list_client_addr:10m; server { listen 10007; server_name localhost; location ^~ /download { alias /data/files/share; charset utf-8; fancyindex on; fancyindex_exact_size off; fancyindex_localtime on; limit_conn list_client_addr 1; if ( $limit != "" ) { limit_rate 200k; } if ($request_filename ~* ^.*?\.(txt|log|pdf|doc|docx|xls|xlsx|ppt|pptx|rar|zip|tar.gz|tar.xz|bz2|iso)$ ) { add_header Content-Disposition attachment; } } }
簡單的說明:
1. 使用 geo 生成了一個 list,我們給 list 中的每項賦值為 0 或者 1,且默認設置為 1。
2. 通過 map 建立 0 和 1 的對應關系,0 代表 $binary_remote_addr,1 則為 ""。所以上面的 default 其實就是 $binary_remote_addr。
3. 我們做了 limit_conn 的限制,這次給的鍵的名稱不是單獨的某個變量,而是 map 定義的映射關系。
4. 整體配置的意思是:在 geo 定義的列表中,我們默認所有限速,如果不限速,將 IP 加入 geo 列表並賦值為 0。這就是白名單。
在很多資料中我們會看到:limit_conn_zone 和 limit_req_zone 指令對於鍵為空值的將會被忽略
但是在我這個版本中一直沒有成功,所有我在外層多了個 if 判斷。
訪問測試白名單中的機器:
訪問不在白名單的機器:
小結
這部分內容是進行 nginx 安全優化的重點,平時可能不起作用,但是生效都是大作用。