כותבים סקריפט שמאתר תוקפים בתוך קבצי לוג רגילים¶
אחד הכישורים הכי פחות "סקסיים" בעולם האבטחה - וגם אחד הכי חשובים - הוא לדעת לקרוא לוגים ולמצוא בהם את הסיפור האמיתי. שרתים מייצרים אלפי שורות לוג ביום, ורוב הזמן אף אחד לא מסתכל עליהן עד שכבר קרה משהו רע. בפרויקט הזה בונים סקריפט פייתון שעושה את העבודה הזאת אוטומטית - סורק קובץ לוג ומסמן פעילות שנראית חשודה.
למה בכלל ניתוח לוגים הוא מיומנות ליבה¶
צד ההתקפה מקבל את כל תשומת הלב, אבל צד ההגנה - מה שנקרא בלו-טים (blue team) - מבוסס במידה רבה על יכולת לזהות דפוסים חריגים בתוך כמות עצומה של רעש. תוקף שמנסה לפרוץ למערכת משאיר עקבות בלוגים כמעט תמיד - ניסיונות התחברות כושלים חוזרים, בקשות מוזרות לכתובות שלא אמורות להתבקש, או תווים חשודים בפרמטרים של URL. מי שיודע לחלץ את הסיפור הזה מתוך הרעש הרגיל שווה המון בכל צוות אבטחה.
מבנה הלוג שעובדים איתו¶
לצורך התרגול, אפשר לעבוד עם קובץ לוג של שרת web בפורמט הנפוץ (Common Log Format) או עם קובץ auth.log של לינוקס שמתעד ניסיונות התחברות. שורה טיפוסית בלוג web נראית ככה:
השורה הזאת כבר מספרת סיפור שלם - כתובת IP, שעה, בקשה שכוללת ניסיון הזרקת SQL קלאסי, וקוד תגובה 401 שאומר שהניסיון נכשל. הסקריפט שלנו צריך לזהות בדיוק את הדברים האלה.
שלב 1 - חילוץ נתונים עם regex¶
הצעד הראשון הוא לפרק כל שורה לרכיבים - כתובת IP, זמן, הבקשה עצמה, וקוד התגובה. ביטוי רגולרי (regex) עושה את זה בקלות:
import re
pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(.*?)\].*"(.*?)"\s(\d+)'
with open("access.log") as f:
for line in f:
match = re.match(pattern, line)
if match:
ip, timestamp, request, status = match.groups()
שלב 2 - צוברים לפי כתובת IP¶
תוקף בודד כמעט תמיד מייצר הרבה שורות מאותה כתובת IP בזמן קצר. עם מילון פייתון פשוט אפשר לספור כמה בקשות הגיעו מכל כתובת, וכמה מהן נכשלו:
from collections import defaultdict
failed_attempts = defaultdict(int)
if status == "401" or status == "403":
failed_attempts[ip] += 1
שלב 3 - מגדירים סף להתראה¶
עכשיו השלב שהופך רשימה של מספרים לכלי שימושי - סף (threshold). אם כתובת IP חצתה, נגיד, עשרה ניסיונות כושלים תוך דקה, זה כבר לא נראה כמו משתמש ששכח סיסמה - זה נראה כמו brute force. חשוב להבין שהמספר הזה הוא לא קדוש - הוא צריך להתאים לסביבה שלכם, ולעיתים דורש איטרציה כדי לא להציף אתכם בהתראות שווא.
for ip, count in failed_attempts.items():
if count > 10:
print(f"חשד ל-brute force מהכתובת {ip} - {count} ניסיונות כושלים")
שלב 4 - מזהים חתימות התקפה ידועות¶
מעבר לספירה, שווה לחפש בתוך שדה הבקשה עצמו ביטויים שמעידים על ניסיון ניצול - תבניות כמו ' OR '1'='1 שמעידות על הזרקת SQL, או ../../ שמעיד על ניסיון path traversal לצאת מתיקיית האתר. רשימה קטנה של חתימות כאלה, נבדקת מול כל שורה, כבר תופסת חלק גדול מהניסיונות הגסים והאוטומטיים שרצים באינטרנט כל הזמן.
המלכודות הנפוצות¶
הטעות הראשונה היא סף רגיש מדי, שמייצר כל כך הרבה התראות עד שכולם מתחילים להתעלם מהן - זה נקרא alert fatigue וזו בעיה אמיתית גם בארגונים גדולים. הטעות השנייה היא להסתכל רק על כשלונות ולהתעלם מהצלחות חשודות - למשל התחברות מוצלחת בשעה מוזרה מכתובת IP שמעולם לא נראתה קודם, שיכולה להעיד על סיסמה שכבר נגנבה.
מה מרוויחים מהפרויקט¶
מעבר לסקריפט עצמו, זו התנסות ראשונה בחשיבה של בלו-טים - לא רק "איך פורצים", אלא "איך מזהים שמישהו מנסה לפרוץ". זו מיומנות שנדרשת גם בעולם הפנטסט, כי כדי לדעת להיזהר מגילוי, קודם צריך להבין איך הגנה בכלל עובדת.
אם אתם רוצים להעמיק בצד הזה של אבטחת מידע לצד היכולות ההתקפיות, קורס בדיקות החדירה שלנו מכסה את שני הצדדים - איך תוקפים חושבים, ואיך מזהים אותם.
יש לכם רעיון לשפר את הסקריפט או שאלה על regex ספציפי? הקהילה בדיסקורד שמחה לעזור.