روی نقشهٔ حمله‌ها لکه‌ای پررنگ بالای یک کشور دیده می‌شود و قاعدهٔ «کل آن کشور را ببندیم» خودبه‌خود به ذهن می‌آید. تقریباً چیزی به دست نمی‌دهد: عوض‌کردن کشور خروج برای مهاجم چند دقیقه وقت می‌برد، در حالی که زیر این ممنوعیت بازدیدکنندگان خودتان می‌افتند و خودتان در سفر. بدتر آنکه خزندهٔ آشکاری که از یک ابر می‌آید، پس از بسته‌شدن به نشانی‌های خانگی کوچ می‌کند و دیگر از آدم‌ها قابل تشخیص نیست.

در ادامه پنج گام آمده که روی همان سرور واقعاً نتیجه می‌دهد: قطع‌کردن پویشگرها، محدودکردن سرعت، قطع‌نکردن آنچه لازم است و، اگر باز هم ضروری بود، بستن درست. همهٔ این‌ها را می‌توان در یک نشست بررسی و تنظیم کرد.

گام ۱. ببینید لکه را در واقع چه کسی می‌سازد

نخست گزارش درخواست‌ها را پیدا کنید. روی سروری که چند سایت دارد access.log بیشتر وقت‌ها خالی است و رکوردها به گزارش همان میزبان مجازی می‌روند:

sudo ls -lS /var/log/apache2/*access*.log /var/log/apache2/*requests*.log 2>/dev/null

در همهٔ فرمان‌های بعدی همان پرونده‌ای گذاشته می‌شود که در بالای این فهرست آمده است. نخست نشانی‌های صدرنشین یک شبانه‌روز، سپس اینکه آنکه در بالاست دقیقاً چه می‌خواهد:

sudo awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -20
sudo grep '^203\.0\.113\.' /var/log/apache2/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20

فرمان دوم به پرسش اصلی پاسخ می‌دهد. اگر در خروجی ده‌ها صفحهٔ مختلف از سایت خودتان باشد، این گردآورندهٔ محتواست: بی‌آزار است و همین که بازدیدکننده شمرده نشود کافی است. اگر آنجا /wp-login.php و /.env و /phpmyadmin و چیزهای دیگری باشد که اصلاً ندارید، این پویشگر است و گام ۲ به آن می‌پردازد. در nginx گزارش‌ها در /var/log/nginx/ هستند و میدان‌ها همان‌اند.

گام ۲. jail برای درخواست به چیزی که ندارید

مسیر درخواست را نمی‌توان جعل کرد — برخلاف نشانی و User-Agent، خودِ همان نیت است. پالایهٔ /etc/fail2ban/filter.d/web-probes.local:

[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) /(wp-login\.php|wp-admin|xmlrpc\.php|\.env|\.git/|phpmyadmin|administrator/)
ignoreregex =

jail در /etc/fail2ban/jail.d/web-probes.local:

[web-probes]
enabled  = true
port     = http,https
filter   = web-probes
logpath  = /var/log/apache2/*access.log
maxretry = 1
findtime = 1h
bantime  = 1d

اینجا maxretry = 1 بجاست: مراجعهٔ تصادفی به پنل مدیریت دیگران وجود ندارد. اعمال کنید و مطمئن شوید که پالایه سطرهای گزارش خودتان را می‌گیرد:

sudo fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/web-probes.local
sudo systemctl reload fail2ban && sudo fail2ban-client status web-probes

گام ۳. سرعت را محدود کنید

پویشگر و خزنده با سرعتشان لو می‌روند، فارغ از اینکه از کدام کشور می‌آیند. سی صفحه در دقیقه از یک نشانی آستانه‌ای است که به آدمی که فهرستی را ورق می‌زند برنمی‌خورد:

[http-flood]
enabled  = true
port     = http,https
filter   = apache-overflows
logpath  = /var/log/apache2/site_access.log
maxretry = 30
findtime = 60
bantime  = 1h

نکتهٔ تعیین‌کننده این است که در logpath کدام گزارش نوشته شده است. اگر زیر آن الگو گزارش خودِ پنل مدیریت یا بخش مدیریت هم بیفتد، خودتان را می‌بندید: یک صفحه از آن ده‌ها درخواست برای سبک‌ها و اسکریپت‌ها به دنبال می‌آورد و سی‌تا با دو کلیک پر می‌شود. به همین دلیل به پنل پروندهٔ گزارش جداگانه‌ای می‌دهند که با الگوی jail نمی‌خواند، و به پرونده‌های ایستا مدت انباره‌گذاری طولانی، تا مرورگر در هر جابه‌جایی دوباره آن‌ها را نخواهد. نشانه، اگر باز هم رخ داد: HTML می‌رسد، ظاهر نمی‌رسد و صفحه معلق می‌ماند. برداشتن مسدودسازی:

sudo fail2ban-client set http-flood unbanip 203.0.113.10

گام ۴. از آنچه نباید قطع شود محافظت کنید

پیش از روشن‌کردن مسدودسازی‌ها استثناها را بنویسید — وگرنه نخستین چیزی که زیر آن‌ها می‌افتد همان چیز لازم است:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24 66.249.64.0/19

چه چیزی حتماً باید در این سطر باشد و چرا:

  • نشانی خودتان یا زیرشبکهٔ دفتر. رایج‌ترین علت ازدست‌دادن دسترسی به سرور، قاعدهٔ خودتان است نه حمله؛
  • بازهٔ 66.249.64.0/19. ربات جست‌وجوی Google از روی User-Agent شناخته می‌شود، اما آن بخشش که JavaScript را اجرا می‌کند با یک Chrome/Linux معمولی می‌آید و خود را معرفی نمی‌کند. هر قاعده‌ای از جنس «ترافیک مراکز داده را ببند» دقیقاً به همان می‌خورد و پس از آن صفحه‌ها بی‌صدا از نمایه بیرون می‌افتند. نشانی مشکوک را می‌توان با پرس‌وجوی وارونه بررسی کرد: dig -x 66.249.66.1 +short باید نامی در ناحیهٔ googlebot.com برگرداند؛
  • نشانی‌های اعلان پرداخت. وب‌هوک‌های سامانه‌های پرداخت از سرورهای AWS و Azure می‌آیند. اگر این ابرها را یکسره ببندید، تأییدهای پرداخت دیگر نمی‌رسند و روزی از آن باخبر می‌شوید که مشتری بنویسد پول کسر شده ولی دسترسی ندارد. بازه‌ها را از مستندات ارائه‌دهندهٔ پرداختتان بردارید.

گام ۵. اگر باز هم بستن لازم است — زیرشبکه را ببندید، نه کشور را

تراکم پایدار از یک شبکه که روزها دوام می‌آورد و با درخواست‌های یک‌شکل همراه است، یک مجری واحد است که از روی فهرستی پیش می‌رود. چنین موردی به‌صورت نقطه‌ای و زمان‌دار بسته می‌شود:

sudo ufw deny from 198.51.100.0/22 to any
sudo ufw status numbered

با فرمان دوم شمارهٔ قاعده را به خاطر بسپارید: برداشتن مسدودسازی بعداً از همان راه انجام می‌شود، sudo ufw delete <شماره>. با خودتان بر سر مدتی توافق کنید — مثلاً یک ماه — و برداشتن آن را در تقویم بنویسید. قاعده‌هایی که «برای همیشه» گذاشته می‌شوند، یک سال بعد به فهرستی بدل می‌شوند که کسی جرئت دست‌زدن به آن را ندارد، چون هیچ‌کس یادش نیست آن نشانی‌ها به چه سبب به آنجا رسیده‌اند.

اینکه کدام زیرشبکه را ببندید از روی نشانی گام نخست به دست می‌آید: whois 198.51.100.17 | grep -iE 'route|cidr|netname'.

پس از این پنج گام چه تغییر می‌کند

پویشگرها بر پایهٔ نیت قطع می‌شوند نه بر پایهٔ خاستگاه، و از نخستین درخواست گرفتار می‌شوند. خزنده‌ها به سقف سرعت می‌خورند. ربات جست‌وجو و اعلان‌های پرداخت همچنان رد می‌شوند. تنها مسدودسازی گسترده‌ای که مانده، نقطه‌ای است، با مدتی معلوم و دلیلی روشن.

نقشه در این میان جایی نمی‌رود، اما نقشش عوض می‌شود: به آن نیاز هست نه برای بستن کشورها، بلکه برای اینکه خودِ تراکم دیده شود و با یک هفتهٔ پیش سنجیده شود. برای این کار تاریخچه لازم است نه عکسی از شبانه‌روز جاری. اینکه چنین نقشه‌ای کنار گزارش مسدودسازی‌ها و فهرست درگاه‌های باز چگونه است، در صفحه‌های نمایشی پایین دیده می‌شود.