היום למדתי: בדיקת Origin כחלק מהגנת CSRF
מתקפת CSRF היא מתקפה שבה אתר זדוני מעתיק טופס שמופיע באתר חוקי אליו. משתמש שמחובר בטאב אחד לאתר החוקי (למשל לאתר הבנק שלו) מגיע לאתר הזדוני, ממלא פרטים בטופס שנראה תמים לגמרי אבל בעצם הטופס הזה הוא עותק של הטופס מהאתר החוקי (לדוגמה מאתר הבנק) שגורם למשתמש לבצע פעולה שהוא לא התכוון.
הגנת CSRF בריילס (ולמעשה באופן כללי, פשוט היום אנחנו מדברים על המימוש בריילס) מורכבת מיצירת שדה מיוחד ונסתדר בטופס שמקבל ערך ייחודי כל פעם שמשתמש גולש לאתר, ובהגשת הטופס הערך שהוגש בטופס נבדק אל מול הערך המתאים שיוצר עבור אותו משתמש. בצורה כזאת אנחנו מונעים העתקה של הטופס - כי לכל משתמש ולכל טעינה של העמוד יש ערך חדש אקראי לאותו שדה "הגנה". אם אתר זדוני יעתיק את הטופס הוא לא ידע מה לכתוב שם.
קל לראות את המנגנון הזה כשאנחנו מנסים להגיש טופס באמצעות curl. עבור מערכת ריילס לדוגמה אני יכול לנסות להגיש טופס באופן הבא:
curl -X POST http://localhost:3000/todos \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "commit=Create+Todo"
אבל זה לא עובד ומדפיס את השגיאה בלוג של השרת:
ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.):
בשביל שזה יעבוד אני צריך למשוך את הערך של טוקן ההגנה בבקשת GET ולהוסיף אותו לבקשת POST יחד עם העוגיה שאיתה הטוקן יוצר, כלומר:
TOKEN=$(curl http://localhost:3000/todos/new \
-c cookies.txt \
| sed -n 's/.*name="authenticity_token" value="\([^"]*\)".*/\1/p')
curl -X POST http://localhost:3000/todos \
-b cookies.txt \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "authenticity_token=$TOKEN" \
-d "commit=Create+Todo"
כל זה טוב ויפה רק שמסתבר שזה לא מכסה את כל המקרים. דפדפנים באופן אוטומטי מוסיפים כותרת בשם Origin לכל בקשה להגשת טופס שכוללת את כתובת האתר ממנו הוגש הטופס. בנוסף לכל הבדיקה שתיארתי כאן החל מריילס 5.1 ריילס מבצע בדיקה נוספת כדי לוודא שה Origin זהה לאתר שלכם - אבל רק אם כותרת Origin באמת נשלחה. זאת הסיבה שפקודת ה curl הצליחה, אין כותרת Origin ולכן מסתפקים בבדיקת הטוקן. נשים לב מה קורה כשאנחנו מצרפים כותרת Origin ואיך ההתנהגות משתנה. תחילה Origin נכון:
curl -X POST http://localhost:3000/todos \
-b cookies.txt \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Origin: http://localhost:3000" \
-d "authenticity_token=$TOKEN" \
-d "commit=Create+Todo"
הכל עובד והבקשה הצליחה. עכשיו Origin שגוי:
curl -X POST http://localhost:3000/todos \
-b cookies.txt \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Origin: http://demo:3000" \
-d "authenticity_token=$TOKEN" \
-d "commit=Create+Todo"
הפעם הבקשה נכשלה עם השגיאה:
ActionController::InvalidAuthenticityToken (HTTP Origin header (http://demo:3000) didn't match request.base_url (http://localhost:3000)):
ניתן לבטל את בדיקת ה Origin בעת בדיקת CSRF (למשל אם אתם יודעים שיש לכם קונפיגורציית רשת בעייתית ואיזשהו פרוקסי בדרך מחליט לשנות אותה) באמצעות ההגדרה:
config.action_controller.forgery_protection_origin_check = true
בקובץ הגדרת הסביבה הבעייתית. למרות שכמובן תמיד עדיף לתקן את הגדרות הסביבה הבעייתיות ולא לבטל הגנות.
נ.ב. דפדפנים היום מגדירים עוגיות Same Site כברירת מחדל מה שמייצר עוד שכבת הגנה נגד מתקפות CSRF. אפשר לקרוא על זה בפוסט קודם שכתבתי בנושא כאן:
https://www.tocode.co.il/blog/2025-09-samesite-cookies-and-csrf-protection