מניעת CSRF, XSSI וזליגת מידע בין מקורות.
למה צריך לבודד את משאבי האינטרנט?
אפליקציות אינטרנט רבות חשופות למתקפות ממקורות שונים, כמו Cross-site Request Forgery (CSRF), הכללת סקריפט חוצה-אתרים (XSSI), התקפות תזמון, דליפות מידע ממקורות שונים או התקפות ספקולטיביות בערוץ צדדי (Spectre).
כותרות בקשות לאחזור מטא-נתונים מאפשרות לפרוס מנגנון הגנה מעמיק יותר – מדיניות בידוד של משאבים – כדי להגן על האפליקציה מפני התקפות נפוצות כאלה ממקורות שונים.
לעיתים קרובות משאבים שנחשפים על ידי אפליקציית אינטרנט מסוימת נטענים רק על ידי האפליקציה עצמה, ולא על ידי אתרים אחרים. במקרים כאלה, פריסה של מדיניות בידוד משאבים שמבוססת על כותרות בקשה של אחזור מטא-נתונים מצריכה מעט מאמץ, ובמקביל מגינה על האפליקציה מפני התקפות בין אתרים.
תאימות דפדפן
יש תמיכה בכותרות של בקשות Fetch Metadata בכל מנועי הדפדפנים המודרניים.
רקע
יש הרבה מתקפות חוצות-אתרים שאפשר לבצע כי האינטרנט פתוח כברירת מחדל ושרת האפליקציות לא יכול להגן על עצמו בקלות מפני תקשורת שמקורה באפליקציות חיצוניות. התקפה טיפוסית ממקורות שונים היא זיוף בקשה במספר אתרים (CSRF) שבה תוקף מפתה משתמש לאתר שנמצא בשליטתו, ולאחר מכן שולח טופס לשרת שאליו המשתמש מחובר. מכיוון שהשרת לא יכול לדעת אם הבקשה הגיעה מדומיין אחר (באתרים שונים), והדפדפן מחבר קובצי cookie באופן אוטומטי לבקשות באתרים שונים, השרת יבצע את הפעולה שהתוקף ביקש בשם המשתמש.
התקפות חוצות-אתרים אחרות, כמו הכללה של סקריפטים חוצי-אתרים (XSSI) או דליפות מידע ממקורות שונים, דומות באופיין ל-CSRF ומבוססות על טעינת משאבים מאפליקציית יעד במסמך שנמצא בשליטת התוקף ודליפת מידע על האפליקציות של היעד. מכיוון שאפליקציות לא יכולות להבדיל בקלות בין בקשות מהימנות לבקשות לא מהימנות, הן לא יכולות לבטל תעבורת נתונים זדונית בין אתרים.
היכרות עם אחזור מטא-נתונים
כותרות בקשה של אחזור מטא-נתונים הן תכונת אבטחה חדשה בפלטפורמת אינטרנט שנועדה לעזור לשרתים להגן על עצמם מפני התקפות ממקורות שונים. מתן מידע על ההקשר של בקשת HTTP בקבוצה של כותרות Sec-Fetch-*
מאפשר לשרת התגובה להחיל מדיניות אבטחה לפני עיבוד הבקשה. כך המפתחים יכולים להחליט אם לאשר או לדחות בקשה על סמך האופן שבו היא נשלחה וההקשר שבו ייעשה שימוש בה, וכך ניתן להשיב רק לבקשות לגיטימיות שנשלחו על ידי האפליקציה שלהם.
Sec-Fetch-Site
Sec-Fetch-Site
מציין לשרת מאיזה אתר נשלחה הבקשה. הדפדפן מגדיר את הערך הזה לאחד מהערכים הבאים:
same-origin
, אם הבקשה נשלחה על ידי אפליקציה שלכם (למשלsite.example
)same-site
, אם הבקשה נשלחה על ידי תת-דומיין של האתר (למשל,bar.site.example
)none
, אם הבקשה נגרמה באופן מפורש על ידי אינטראקציה של המשתמש עם סוכן המשתמש (למשל, לחיצה על סימנייה)cross-site
, אם הבקשה נשלחה על ידי אתר אחר (למשלevil.example
)
Sec-Fetch-Mode
Sec-Fetch-Mode
מציין את המצב של הבקשה. סוג זה תואם כמעט לסוג הבקשה ומאפשר לכם להבחין בין טעינות משאבים לבין בקשות ניווט. לדוגמה, יעד של navigate
מציין בקשת ניווט ברמה העליונה, ואילו no-cors
מציין בקשות למשאבים, כמו טעינת תמונה.
Sec-Fetch-Dest
Sec-Fetch-Dest
חושף את היעד של בקשה (למשל, אם התג script
או img
גרמו לבקשה של משאב בדפדפן).
איך משתמשים במטא-נתונים של אחזור כדי להגן מפני התקפות מאתרים שונים
המידע הנוסף שכותרות הבקשות האלה מספקות הוא פשוט, אבל ההקשר הנוסף מאפשר לבנות לוגיקת אבטחה עוצמתית בצד השרת, שנקראת גם 'מדיניות בידוד של משאבים', באמצעות כמה שורות קוד.
הטמעת מדיניות בידוד משאבים
מדיניות בידוד משאבים מונעת מאתרים חיצוניים לבקש את המשאבים שלכם. חסימת תנועה כזו מקלה על נקודות חולשה נפוצות באינטרנט באתרים שונים, כמו CSRF, XSSI, התקפות תזמון ודליפות מידע ממקורות שונים. אפשר להפעיל את המדיניות הזו לכל נקודות הקצה של האפליקציה, והיא תאפשר את כל בקשות המשאבים שמגיעות מהאפליקציה שלכם, וגם ניווטים ישירים (דרך בקשת HTTP GET
). אפשר לבטל את ההסכמה לשימוש בלוגיקה הזו לגבי נקודות קצה שאמורות להיטען בהקשר של אתרים שונים (למשל, נקודות קצה שנטענות באמצעות CORS).
שלב 1: אפשר בקשות מדפדפנים שלא שולחים מטא-נתונים לאחזור
מכיוון שלא כל הדפדפנים תומכים באחזור מטא-נתונים, עליך לאפשר בקשות שלא קובעות את הכותרות של Sec-Fetch-*
על ידי בדיקת הנוכחות של sec-fetch-site
.
if not req['sec-fetch-site']:
return True # Allow this request
שלב 2: אפשר לשלוח בקשות באותו אתר או ביוזמת הדפדפן
כל בקשה שלא מגיעה מהקשר בין מקורות (כמו evil.example
) תתקבל. באופן ספציפי, הבקשות הן:
- מגיעות מהאפליקציה שלכם (למשל, בקשה מסוג same-origin שבה בקשות
site.example
site.example/foo.json
תמיד יתקבלו). - מגיעות מתת-הדומיינים שלכם.
- נגרמות באופן מפורש מאינטראקציה של המשתמש עם סוכן המשתמש (למשל: ניווט ישיר או לחיצה על סימנייה וכו').
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
return True # Allow this request
שלב 3: מפעילים ניווט פשוט ברמה העליונה ו-iframes
כדי לוודא שעדיין אפשר יהיה לקשר לאתר שלכם מאתרים אחרים, עליכם לאפשר ניווט פשוט (HTTP GET
) ברמה העליונה.
if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
# <object> and <embed> send navigation requests, which we disallow.
and req['sec-fetch-dest'] not in ('object', 'embed'):
return True # Allow this request
שלב 4: ביטול ההסכמה לנקודות קצה שנועדו לשרת תנועה בין אתרים (אופציונלי)
במקרים מסוימים, האפליקציה עשויה לספק משאבים שאמורים להיטען בין אתרים. צריך לפטור את המשאבים האלה לפי נתיב או לפי נקודת קצה. דוגמאות לנקודות קצה כאלה:
- נקודות קצה (endpoints) שמיועדות לגישה ממקורות שונים: אם האפליקציה שלכם מציגה נקודות קצה שמופעלות בהן
CORS
, צריך לבטל את ההסכמה לבידוד של משאבים באופן מפורש כדי לוודא שבקשות מאתרים שונים לנקודות הקצה האלה עדיין אפשריות. - משאבים ציבוריים (למשל, תמונות, סגנונות וכו'): אפשר גם לפטור משאבים ציבוריים ולא מאומתים שצריך לטעון ממקורות שונים באתרים אחרים.
if req.path in ('/my_CORS_endpoint', '/favicon.png'):
return True
שלב 5: דחייה של כל הבקשות האחרות שרלוונטיות לאתרים שונים ולא לניווט
כל בקשה אחרת מאתרים שונים תידחה בהתאם למדיניות הזו בנושא בידוד משאבים, וכך האפליקציה שלך תגן על האפליקציה מפני התקפות נפוצות בין אתרים.
דוגמה: הקוד הבא מדגים הטמעה מלאה של מדיניות בידוד משאבים חזקה בשרת או כתוכנה לעיבוד נתונים ביניים, כדי לדחות בקשות משאבים זדוניות פוטנציאליות באתרים שונים, תוך מתן אפשרות לבקשות ניווט פשוטות:
# Reject cross-origin requests to protect from CSRF, XSSI, and other bugs
def allow_request(req):
# Allow requests from browsers which don't send Fetch Metadata
if not req['sec-fetch-site']:
return True
# Allow same-site and browser-initiated requests
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
return True
# Allow simple top-level navigations except <object> and <embed>
if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
and req['sec-fetch-dest'] not in ('object', 'embed'):
return True
# [OPTIONAL] Exempt paths/endpoints meant to be served cross-origin.
if req.path in ('/my_CORS_endpoint', '/favicon.png'):
return True
# Reject all other requests that are cross-site and not navigational
return False
פריסת מדיניות בידוד משאבים
- מומלץ להתקין מודול כמו קטע הקוד שלמעלה כדי לתעד ולעקוב אחרי התנהגות האתר, ולוודא שההגבלות לא משפיעות על תנועה לגיטימית.
- תיקון הפרות פוטנציאליות על ידי החרגת נקודות קצה לגיטימיות שמגיעות ממקורות שונים.
- כדי לאכוף את המדיניות, משחררים בקשות שלא עומדות בדרישות.
זיהוי ותיקון של הפרות מדיניות
מומלץ לבדוק את המדיניות ללא השפעה צדדית. לשם כך, צריך להפעיל אותה קודם במצב דיווח בקוד בצד השרת. לחלופין, אפשר ליישם את הלוגיקה הזו בתווכה, או בשרת proxy הפוך שמתעד כל הפרה של המדיניות אם היא חלה על תנועה בסביבת הייצור.
מניסיון ההשקה של מדיניות בנושא בידוד משאבים של מטא-נתונים של אחזור ב-Google, רוב האפליקציות תואמות כברירת מחדל למדיניות כזו, ונדיר שנדרש לפטור נקודות קצה כדי לאפשר תנועה בין אתרים.
אכיפת מדיניות בנושא בידוד משאבים
אחרי שמוודאים שהמדיניות לא משפיעה על תנועה לגיטימית בסביבת הייצור, מוכנים לאכוף הגבלות. כך אפשר להבטיח שאתרים אחרים לא יוכלו לבקש את המשאבים שלכם ולהגן על המשתמשים מפני מתקפות באתרים שונים.
קריאה נוספת
- מפרט כותרות של בקשות למטא-נתונים ב-W3C
- Fetch Metadata Playground
- שיחת Google I/O: אבטחת אפליקציות אינטרנט באמצעות תכונות פלטפורמה מודרניות (Slides)