通過監控Nginx日誌來實時屏蔽高頻惡意訪問的IP
目前在我的VPS上主要通過兩種方式來限制ip的訪問次數。
- 通過Nginx的
limit_req
配置來限制同一ip在一分鐘內的訪問次數 - 通過Nginx deny封禁一天內超過指定次數的ip(需要一個監控腳本)
一、limit_req配置
Nginx 提供了兩個模塊:ngx_http_limit_req_module
和 ngx_http_limit_conn_module
,前者是限制同一ip在一段時間內的訪問總次數,後者是限制同一ip的並發請求次數。
我的配置主要如下:
http { limit_req_zone $binary_remote_addr zone=onelimit:10m rate=20r/m; server { ... location / { limit_req zone=onelimit burst=5 nodelay; limit_req_log_level warn; } } }
$binary_remote_addr 根據客戶端ip作為鍵值,zone設置唯一標識並設置存儲內存大小,每分鐘請求不超過20次,否則返回503錯誤。
burst=5 表示如果超過頻率限制後可緩沖的等待請求數。nodelay表示burst部分不需要等待,nginx會直接處理等待部分的請求。
limit_req_log_level warn 將匹配到的攔截請求日誌等級設置為warn級別。
看下日誌內容:
2019/03/31 17:57:32 [warn] 9672#9672: *431036 limiting requests, excess: 5.695 by zone "onelimit", client: 183.210.197.101, server: yun.xxx.com, request: "GET /download/ring/b649b722df3c4c86d405d8deb272a59b.mp3 HTTP/1.1", host: "yun.xxx.com", referrer: "http://m.xxx.com/id/61610.html" 2019/03/31 17:57:39 [warn] 9672#9672: *431038 limiting requests, excess: 5.267 by zone "onelimit", client: 183.210.197.101, server: yun.xxx.com, request: "GET /download/ring/b649b722df3c4c86d405d8deb272a59b.mp3 HTTP/1.1", host: "yun.xxx.com", referrer: "http://m.xxx.com/id/61610.html"
二、Nginx deny配置及監控腳本實現
limit_req
模塊不足的地方在於它只能控制瞬時請求的次數,每秒的請求數 (r/s) 或 每分鐘的請求數 (r/m)。這對於惡意訪問來源能比較容易的通過控制訪問頻率來繞過這個檢測,這種情況下我主要通過deny配置來直接禁止一天內超過指定次數的ip來源(比如一天訪問次數超過100次直接返回403)。設置步驟如下:
1. 創建blocksip.conf
在nginx.conf同級目錄下創建文件blocksip.conf,在http節點增加以下配置(也可以設置在server節點對單個網站起作用,或location節點只針對特定訪問路徑的限制)
include blocksip.conf;
可以在blocksip.conf文件中添加一條測試記錄,如服務器ip
deny 127.0.0.1;
然後重新加載nginx,nginx -s reload
,測試下是否生效。
2. 編寫監控腳本,定時更新blocksip.conf文件
根據nginx請求日誌來計算出各ip的top訪問次數,根據訪問次數將符合條件的ip加入到blocksip.conf文件中,然後重新加載nginx使配置生效即可。
PHP腳本blocksip.php:
<?php
$blockFile = "/etc/nginx/blocksip.conf";
$logs = ["/var/log/nginx/access-site1.log", "/var/log/nginx/access-site2.log"];
$blocks = file_get_contents($blockFile);
$n = 0;
foreach($logs as $log) {
$data = shell_exec("cat $log | awk -F ' ' '{print $1}'| sort | uniq -c | sort -n -r | head -n 20");
$lines = explode("\n", $data);
foreach($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
$line = explode(" ", $line);
list($num, $ip) = $line;
if ($num > 100) {
if (!stristr($blocks, $ip)) {
var_dump("[".date("Y-m-d H:i:s")."] New Match $ip");
$newBlock = "deny {$ip};" . PHP_EOL;
file_put_contents($blockFile, $newBlock, FILE_APPEND);
$n++;
} else {
var_dump("Blocked > $ip > $num");
}
}
}
}
if($n > 0) {
$r = shell_exec("nginx -s reload");
var_dump("nginx -s reload", $r);
}
3. 將監控腳本放入crontab定時執行
每十分鐘執行一次
*/10 * * * * /usr/bin/php /etc/nginx/blocksip.php >> /tmp/blocksip.log;
看下攔截日誌內容:
2019/03/31 17:57:26 [error] 9672#9672: *431024 access forbidden by rule, client: 59.80.44.46, server: yun.xxx.com, request: "GET /music/979744fb8eb9055f77f3db2a3f3189a8.mp3 HTTP/1.1", host: "yun.xxx.com"
2019/03/31 17:57:34 [error] 9672#9672: *431037 access forbidden by rule, client: 59.80.44.46, server: yun.xxx.com, request: "GET /music/979744fb8eb9055f77f3db2a3f3189a8.mp3 HTTP/1.1", host: "yun.xxx.com"
OK, 搞定!
通過監控Nginx日誌來實時屏蔽高頻惡意訪問的IP