介紹如何設定 NGINX 網頁伺服器組態,防範 DDoS 阻斷服務攻擊。
DDoS 阻斷服務攻擊是一種網路攻擊手法,透過大量的網路連線與請求,使伺服器的網路或系統資源耗盡,讓服務暫時中斷或停止,導致正常使用者無法正常存取伺服器上的服務。
OSI 第七層應用層的 DDoS 攻擊通常都是由特別設計過的程式,對特定的系統所發動的,例如讓程式同時建立大量的連線,讓伺服器的連線數量達到上限,無法接受後來正常的連線。
這類 DDoS 攻擊的連線通常會有以下幾種特徵,了解這些特徵對於 DDoS 的防禦工作會有幫助:
User-Agent
標頭內容可能會是非標準的值。Referer
標頭內容可能會非常可疑。NGINX 伺服器可以限制特定網址的請求發送頻率,例如對於每個 IP 位址在存取 /login.html
的時候,限制每分鐘只能送出 30 個請求,也就是說每個請求之間至少間隔 2 秒:
# 定義一個名稱為 one 的 zone,儲存使用者 IP 位址,限制請求頻率為每分鐘 30 次 limit_req_zone $binary_remote_addr zone=one:10m rate=30r/m; server { location /login.html { # 以 zone 限制請求頻率 limit_req zone=one; } }
在這段設定中,limit_req_zone
建立一個名稱為 one
的共享記憶體空間(zone),用來儲存指定的鍵值(key),在這個例子中鍵值就是使用者的 IP 位址($binary_remote_addr
),而在 /login.html
的設定中則使用 limit_req
引用這個 zone 來限制請求頻率。
NGINX 伺服器也可以限制每個 IP 位址可建立連線數量的上限值,避免攻擊程式占用大量的網路連線,例如限制每個 IP 位址在存取 /store/
之下的資料時,最多只能建立 10
條連線:
# 定義一個名稱為 addr 的 zone,儲存使用者 IP 位址 limit_conn_zone $binary_remote_addr zone=addr:10m; server { location /store/ { # 限制同時連線數上限值為 10 limit_conn addr 10; } }
這裡的 limit_conn_zone
會建立一個名稱為 addr
的 zone,儲存使用者的 IP 位址,而 limit_conn
則會依據 addr
中的 IP 位址資料,限制每個 IP 位址最多只能同時建立 10
連線。
有些正常連線在主要資料傳輸完畢之後,還是依然繼續保持連線狀態,攻擊者可以利用這種方式保持大量閒置的連線,讓伺服器滿載。
client_body_timeout
與 client_header_timeout
可以用來設定 NGINX 伺服器寫入網頁資料與標頭的最長間隔時間,這兩個設定的預設值都是 60
秒,以下設定可以將此兩個值改為 10
秒,也就是當等待 10
秒沒有寫入資料時,讓 NGINX 伺服器中斷該連線:
server { # 設定網頁內容寫入間隔上限值 client_body_timeout 10s; # 設定網頁標頭寫入間隔上限值 client_header_timeout 10s; }
如果我們可以確認出攻擊者的 IP 位址,可以直接透過 NGINX 的 IP 位址黑名單功能,阻擋這些來源的 IP 位址。例如阻擋 123.123.123.1
到 123.123.123.16
這段區間的 IP 位址:
location / { # 阻擋指定 IP 網段 deny 123.123.123.0/28; }
也可以個別指定每一個要阻擋的來源 IP 位址:
location / { # 個別指定 IP 位址黑名單 deny 123.123.123.3; deny 123.123.123.5; deny 123.123.123.7; }
如果我們的 NGINX 伺服器只開放給特定 IP 網段的使用者存取,可以先以 allow
設定允許存取的來源 IP 位址,再加上 deny all
封鎖其他不在白名單內的 IP 位址:
location / { # 設定允許連線的來源 IP 位址 allow 192.168.1.0/24; # 阻擋其他不在白名單內的 IP 位址 deny all; }
若有使用 ngx_http_proxy_module
,可以考慮將 proxy_cache_use_stale
設定為 updating
,可讓資料在更新的時候,不要重複送出更新的請求。
另外亦可注意在設定 proxy_cache_key
的時候,不要將 $query_string
納入其中,以免攻擊者送出亂數的 $query_string
導致快取塞爆快取空間。
如果攻擊者對特定的網頁發動攻擊,我們可以暫時阻擋特定網頁的請求:
# 阻擋特定網頁的所有請求 location /foo.php { deny all; }
如果發現攻擊的請求中,User-Agent
都呈現奇怪的內容,也可以依據 User-Agent
將含有特定字眼的請求阻擋掉:
location / { # 阻擋 User-Agent 含有 foo 或 bar 的請求 if ($http_user_agent ~* foo|bar) { return 403; } }
在 NGINX 設定檔中,我們可以透過 http_
加上 HTTP 的標頭名稱來取用對應的 HTTP 標頭資訊,例如 $http_user_agent
就是 User-Agent
的資訊,透過這樣的方式,我們可以使用任意的 HTTP 標頭資訊來篩選請求。