במפת ההתקפות יש כתם סמיך מעל מדינה אחת, והכלל «לאסור אותה כולה» מציע את עצמו מאליו. הוא כמעט ולא ייתן דבר: החלפת מדינת היציאה עולה לתוקף כמה דקות, ואילו תחת האיסור ייפלו המבקרים שלכם ואתם עצמכם בנסיעה. גרוע מכך, סורק גלוי שמגיע מענן יחיד יעבור אחרי החסימה לכתובות ביתיות ויהפוך לבלתי ניתן להבחנה מבני אדם.

להלן חמישה צעדים שבאותו שרת עצמו אכן נותנים תוצאה: לנתק את הסורקים, להגביל את הקצב, לא לנתק את מה שנחוץ, ואם בכל זאת צריך — לחסום נכון. את הכול אפשר לבדוק ולהגדיר בישיבה אחת.

צעד 1. לראות מי באמת יוצר את הכתם

קודם כול אתרו את יומן הבקשות. בשרת שמארח כמה אתרים 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 ועוד דברים שאין לכם בכלל, זה סורק, ובו מטפל צעד 2. ב-nginx היומנים נמצאים ב-/var/log/nginx/, והשדות זהים.

צעד 2. 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

צעד 3. להגביל את הקצב

הסורק והזוחל מסגירים את עצמם במהירות, ללא קשר למדינה שממנה הם מגיעים. שלושים עמודים בדקה מכתובת אחת הם סף שאינו נוגע באדם שמדפדף בקטלוג:

[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

צעד 4. להגן על מה שאסור לנתק

לפני שמפעילים חסימות, רשמו את החריגים — אחרת הראשון שייפול תחתיהן יהיה דווקא הדרוש:

[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;
  • הכתובות של הודעות התשלום. ה-webhook של מערכות התשלום מגיעים משרתי AWS ו-Azure. אם תסגרו את העננים האלה כולם, אישורי התשלום יפסיקו להגיע, ותדעו על כך ביום שבו לקוח יכתוב שהכסף ירד אבל הגישה אינה. את הטווחים לוקחים מהתיעוד של ספק התשלומים שלכם.

צעד 5. אם חסימה בכל זאת נחוצה — חסמו תת-רשת, לא מדינה

התעבות יציבה מרשת אחת, שמחזיקה ימים ומלווה בבקשות מאותו סוג, היא מבצע יחיד שעובר על רשימה. מקרה כזה נחסם נקודתית ולזמן קצוב:

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'.

מה משתנה אחרי חמשת הצעדים האלה

סורקים מנותקים לפי כוונה ולא לפי מוצא, ונתפסים כבר בבקשה הראשונה. זוחלים נתקלים בתקרת הקצב. רובוט החיפוש והודעות התשלום ממשיכים לעבור. החסימה הרחבה היחידה שנותרה היא נקודתית, עם מועד ידוע וסיבה מובנת.

המפה אינה נעלמת בינתיים, אבל תפקידה משתנה: היא נחוצה לא כדי לסגור מדינות, אלא כדי להבחין בהתעבות עצמה ולהשוות אותה למה שהיה לפני שבוע. לשם כך דרושה היסטוריה ולא תצלום של היממה הנוכחית. איך נראית מפה כזאת לצד יומן החסימות ורשימת הפורטים הפתוחים, מראים עמודי ההדגמה שלהלן.