ایمنسازی حرفهای Nginx: مسدودسازی جغرافیایی و مقابله با حملات (راهنمای ویژه توسعهدهندگان .NET)
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۱۲/۰۱ ۱۳:۰۰
آدرس: www.dntips.ir
geoip منسوخ شده است. روش استاندارد و مدرن، استفاده از ماژول ngx_http_geoip2_module است.sudo apt update sudo apt install libnginx-mod-http-geoip2 -y
GeoLite2-Country نیاز داریم. این دیتابیس برای تشخیص کشورها دقت بالایی دارد (دقت شهر برای هدف ما نیاز نیست).sudo mkdir -p /etc/nginx/geoip cd /etc/nginx/geoip # دانلود دیتابیس (از میرور گیتهاب برای دسترسی سریع) wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb
nginx.conf)444 استفاده میکنیم. این کد استاندارد HTTP نیست، بلکه مخصوص Nginx است و به سرور میگوید "بدون ارسال هیچ پاسخی، کانکشن را قطع کن". این کار دو مزیت دارد:/etc/nginx/nginx.conf را ویرایش کنید:http {
# ... سایر تنظیمات ...
# بارگذاری دیتابیس GeoIP2
geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {
$geoip2_country_code country iso_code;
}
# نقشهبرداری کد کشورها به وضعیت بلاک
map $geoip2_country_code $block_country {
default 0; # به صورت پیشفرض همه مجاز هستند
CN 1; # چین
RU 1; # روسیه
VN 1; # ویتنام
# سایر کدهای ISO کشورها را میتوانید اضافه کنید
}
# تعریف Rate Limit عمومی (توضیحات در بخش دوم)
limit_req_zone $binary_remote_addr zone=global:10m rate=15r/s;
}/etc/nginx/sites-available/default یا فایل کانفیگ پروژه داتنت شما)، شرط بررسی را اضافه کنید:server {
listen 80;
server_name yourdomain.com;
# بلاک کردن کشورها قبل از هر پردازشی
if ($block_country) {
return 444;
}
location / {
# اعمال محدودیت نرخ درخواست
limit_req zone=global burst=30 nodelay;
# تنظیمات پروکسی به سمت Kestrel
proxy_pass http://localhost:5000;
# ... سایر تنظیمات پروکسی ...
}
}rate=15r/s: اجازه ۱۵ درخواست در ثانیه به ازای هر IP.burst=30: اجازه میدهد ترافیک ناگهانی تا ۳۰ درخواست در صف قرار گیرد.nodelay: درخواستهای موجود در burst را بدون وقفه پردازش میکند (تجربه کاربری بهتر برای کاربران واقعی)، اما اگر از حد بگذرد، خطا میدهد./usr/local/bin/update-geoip.sh را ایجاد کنید:#!/usr/bin/env bash
set -euo pipefail
# تنظیم متغیرها
GEOIP_DIR="/etc/nginx/geoip"
TMP_DIR="$(mktemp -d)"
DB_NAME="GeoLite2-Country.mmdb"
URL="https://github.com/P3TERX/GeoLite.mmdb/raw/download/${DB_NAME}"
LOG="/var/log/geoip-update.log"
echo "[$(date)] Starting GeoIP update" >> "$LOG"
# دانلود در پوشه موقت
cd "$TMP_DIR"
wget -q --timeout=30 "$URL"
# Sanity Check: بررسی اینکه فایل دانلود شده سالم و غیرخالی باشد
if [ ! -s "$DB_NAME" ]; then
echo "[$(date)] Download failed or empty file" >> "$LOG"
exit 1
fi
# جایگزینی اتمیک (Atomic Replace)
# این روش تضمین میکند که Nginx هرگز فایل ناقص یا خراب را نمیخواند
mv "$DB_NAME" "$GEOIP_DIR/${DB_NAME}.new"
mv "$GEOIP_DIR/${DB_NAME}.new" "$GEOIP_DIR/$DB_NAME"
# بارگذاری مجدد سرویس (Reload نه Restart)
nginx -t && systemctl reload nginx
echo "[$(date)] GeoIP update successful" >> "$LOG"
rm -rf "$TMP_DIR"sudo chmod +x /usr/local/bin/update-geoip.sh sudo crontab -e
0 3 * * 1 /usr/local/bin/update-geoip.sh
wp-login.php, .env, .git و... میگردند./etc/fail2ban/filter.d/ ایجاد کنید.[Definition] failregex = limiting requests, excess:.* by zone.*client: <HOST> ignoreregex =
[Definition] failregex = ^<HOST> - .* "(GET|POST).*?(wp-login\.php|xmlrpc\.php|\.env|\.git|phpmyadmin).*" (404|403|444) ignoreregex =
[Definition] failregex = ^<HOST> - .* "(GET|POST).*" 4(03|04|44) ignoreregex =
/etc/fail2ban/jail.d/nginx.conf را ایجاد کنید. در اینجا سیاستهای سختگیرانهای برای محیط Production اعمال میکنیم.[nginx-req-limit] enabled = true filter = nginx-req-limit port = http,https logpath = /var/log/nginx/error.log maxretry = 3 findtime = 60 bantime = 1d # بن شدن به مدت یک روز [nginx-custom-bot] enabled = true filter = nginx-custom-bot port = http,https logpath = /var/log/nginx/access.log maxretry = 2 # حساسیت بالا: با دومین تلاش بن میشود findtime = 10m bantime = 7d # بن طولانی مدت (یک هفته) [nginx-4xx] enabled = true filter = nginx-4xx port = http,https logpath = /var/log/nginx/access.log maxretry = 10 findtime = 1m bantime = 6h
systemctl restart fail2ban
fail2ban-client status nginx-custom-bot چک کنید تا ببینید چه تعداد حمله دفع شده است.bantime = -1 (بن دائم) قرار ندهید؛ زیرا به مرور زمان دیتابیس Fail2Ban بسیار حجیم و کند میشود. بنهای طولانی (مثلاً یک هفته) کافی و موثر هستند.nginx -t را اجرا کنید تا از صحت Syntax مطمئن شوید.چکیده: افزایش بیرویه و ناگهانی درخواستهای کرولرهای وب (مانند خزندههای Meta/Facebook) میتواند منابع سرور را درگیر کرده و منجر به کاهش کارایی یا بروز اختلال در پاسخدهی به کاربران واقعی شود. اگرچه ابزارهایی مانند Fail2Ban جهت تحلیل لاگها و مسدودسازی IPها کاربرد دارند، اما اقدام در لایه وبسرور (Nginx) و لایه اپلیکیشن (.NET) راهکاری سریعتر، کمهزینهتر و بهینهتر محسوب میشود. در این مقاله فنی، نحوه شناسایی دقیق الگوی جدید User-Agent کرولر meta-externalagent و روشهای مسدودسازی آن در Nginx بررسی میشود. همچنین با اضافه کردن مباحث مربوط به بررسی اعتبار IP (IP Verification) و مدیریت خطای ۴۴۴، راهکاری کامل جهت حفاظت از زیرساختهای مبتنی بر داتنت ارائه خواهد شد.robots.txt، حجم انبوهی از درخواستها را به سوی سرور روانه کند. در چنین وضعیتی، اتکا به ابزارهایی نظیر Fail2Ban به تنهایی کافی نیست؛ زیرا Fail2Ban یک پردازش پسزمینه است که پس از ثبت درخواستها در لاگ فایل اقدام به مسدودسازی IP میکند. بنابراین، درخواستها تا زمان مسدود شدن کماکان از لایههای وبسرور و حتی پردازش برنامهنویسی عبور میکنند و منابع پردازشی سرور را مصرف خواهند کرد. بهترین رویکرد، رد کردن سریع درخواستها در اولین لایه ورودی (Nginx) و در صورت نیاز، مدیریت شفافتر آن در لایه Middleware داتنت است.facebookexternalhit یا Facebot در درخواستها دیده میشد. اما در نسخههای جدیدتر، این الگوی متنی به ساختارهای مشابه مرورگرهای واقعی مرورگرهای مرورگر مانند Chrome در Windows یا macOS تغییر یافته است:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 (compatible; meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 (compatible; meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler))
Chrome یا Windows کاملاً نادرست است، زیرا درخواستهای کاربران واقعی را مسدود خواهد کرد.meta-externalagent یا facebookexternalhit است.+(https://developers.facebook.com/)...) نیز ثابت است، اما تطبیق روی کلیدواژه اصلی برای تشخیص کافی و بهینهتر است.http/etc/nginx/nginx.conf) و درون بلاک http عبارت زیر را تعریف کنید:http {
# تعریف نقشه جهت شناسایی الگوی کرولر بدون حساسیت به حروف بزرگ و کوچک (~*)
map $http_user_agent $block_meta_crawler {
default 0;
~*meta-externalagent 1;
~*facebookexternalhit 1;
~*facebot 1;
}
...
}نکته: کاراکتر ~* در Nginx نشاندهنده بررسی عبارت با Regex بدون حساسیت به حروف کوچک و بزرگ (Case-Insensitive) است.serverserver مربوط به HTTPS)، شرط زیر را اضافه کنید:server {
listen 443 ssl http2;
server_name yourdomain.com;
# قطع بلافاصله اتصال بدون ارسال هدر یا پاسخ به درخواستهای مسدود شده
if ($block_meta_crawler) {
return 444;
}
...
}444 یک کد غیر استاندارد مخصوص Nginx است که به وبسرور دستور میدهد اتصال را بلافاصله بدون ارسال هیچگونه Response Header یا Body ببندد (Close Connection Without Response). این کار کمترین فشار ممکن را به سرور وارد کرده و پهنای باند را حفظ میکند.sudo nginx -t
syntax is ok و test is successful)، Nginx را مجدداً بارگذاری کنید:sudo systemctl reload nginx
curl -I \ -A "Mozilla/5.0 (compatible; meta-externalagent/1.1; +https://developers.facebook.com/docs/sharing/webmasters/crawler)" \ https://yourdomain.com/
curl: (52) Empty reply from server
robots.txtrobots.txt نیز از نظر اصول سئو و استاندارد وب توصیه میشود:User-agent: meta-externalagent Disallow: / User-agent: facebookexternalhit Disallow: /
.facebook.com تعیینکننده اصالت واقعی خزنده است.