בידוד של אתר למפתחי אינטרנט

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

מהו בידוד של אתר?

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

בידוד אתרים היא תכונת אבטחה ב-Chrome שמספקת קו הגנה נוסף כדי להקטין את הסיכוי להצלחה של התקפות כאלה. הוא מבטיח שדפים מאתרים שונים תמיד מועברים לתהליכים שונים, שכל אחד מהם פועל בארגז חול שמגביל את הפעולות שהתהליך יכול לבצע. הוא גם מונע מהתהליך לקבל סוגים מסוימים של מידע רגיש מאתרים אחרים. כתוצאה מכך, כשמשתמשים ב-Site Isolation, קשה הרבה יותר לאתר זדוני להשתמש בהתקפות ספקולטיביות בערוץ צדדי כמו Spectre כדי לגנוב נתונים מאתרים אחרים. כשצוות Chrome ישלים אכיפה נוספת, בידוד האתרים יעזור גם במקרים שבהם דף של תוקף יכול להפר חלק מהכללים בתהליך שלו.

התכונה 'בידוד אתרים' מקשה על אתרים לא מהימנים לגשת למידע מהחשבונות שלכם באתרים אחרים או לגנוב אותו. היא מספקת הגנה נוספת מפני סוגים שונים של באגים באבטחה, כמו התקפות ערוץ צדדי של Meltdown ו-Spectre.

לפרטים נוספים על בידוד אתרים, אפשר לעיין במאמר שלנו בבלוג האבטחה של Google.

Cross-Origin Read Blocking (CORB)

גם כשכל הדפים באתרים שונים מועברים לתהליכים נפרדים, הדפים עדיין יכולים לבקש באופן לגיטימי משאבים משנה מסוימים באתרים שונים, כמו תמונות ו-JavaScript. דף אינטרנט זדוני יכול להשתמש ברכיב <img> כדי לטעון קובץ JSON עם מידע אישי רגיש, כמו היתרה בחשבון הבנק:

<img src="https://your-bank.example/balance.json" />
<!-- Note: the attacker refused to add an `alt` attribute, for extra evil points. -->

בלי Site Isolation, התוכן של קובץ ה-JSON יגיע לזיכרון של תהליך ה-renderer, ובשלב הזה ה-renderer יזהה שזה לא פורמט תמונה תקין ולא ייצור תמונה. אבל לאחר מכן, התוקף יכול לנצל נקודת חולשה כמו Spectre כדי לקרוא את מקטע הזיכרון הזה.

במקום להשתמש ב-<img>, התוקף יכול להשתמש גם ב-<script> כדי לכתוב את המידע הרגיש לזיכרון:

<script src="https://your-bank.example/balance.json"></script>

Cross-Origin Read Blocking (CORB) היא תכונת אבטחה חדשה שמונעת את כניסת התוכן של balance.json לזיכרון של תהליך ה-Renderer על סמך סוג ה-MIME שלו.

ננסה להסביר איך CORB עובד. אתר יכול לבקש משאבים משני סוגים מהשרת:

  1. מקורות נתונים כמו מסמכי HTML,‏ XML או JSON
  2. משאבי מדיה כמו תמונות, JavaScript,‏ CSS או גופנים

אתר יכול לקבל משאבי נתונים מהמקור שלו או ממקורות אחרים באמצעות כותרות CORS מתירות, כמו Access-Control-Allow-Origin: *. לעומת זאת, אפשר לכלול משאבי מדיה מכל מקור, גם בלי כותרות CORS מתירות.

CORB מונע מתהליך ה-renderer לקבל משאב נתונים ממקורות שונים (כלומר HTML,‏ XML או JSON) אם:

  • למשאב יש כותרת X-Content-Type-Options: nosniff
  • CORS לא מאפשר גישה למשאב באופן מפורש

אם כותרת X-Content-Type-Options: nosniff לא מוגדרת במשאב הנתונים שמגיע ממקורות שונים, CORB מנסה לרחרח את גוף התגובה כדי לקבוע אם הוא HTML,‏ XML או JSON. הדבר נחוץ כי יש שרתי אינטרנט שהוגדרו בצורה שגויה ומציגים תמונות כ-text/html, לדוגמה.

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

כדי לשפר את האבטחה וליהנות מ-CORB, מומלץ:

  • לסמן את התשובות באמצעות הכותרת הנכונה Content-Type. (לדוגמה, משאבי HTML צריכים להופיע בתור text/html, משאבי JSON עם סוג MIME של JSON ומשאבי XML עם סוג MIME של XML).
  • כדי לבטל את ההסכמה לניטור, משתמשים בכותרת X-Content-Type-Options: nosniff. בלי הכותרת הזו, Chrome מבצע ניתוח תוכן מהיר כדי לנסות לאשר שהסוג נכון, אבל מכיוון שהמערכת נוטה לאפשר תגובות כדי להימנע מחסימה של דברים כמו קובצי JavaScript, עדיף לבצע את הפעולה הנכונה בעצמכם.

לפרטים נוספים, אפשר לעיין במאמר על CORB למפתחי אתרים או בהסבר המפורט על CORB.

למה מפתחי אתרים צריכים להשתמש בבידוד אתרים?

ברוב המקרים, Site Isolation היא תכונה של הדפדפן שמתרחשת מאחורי הקלעים, ולא מוצגת ישירות למפתחי האתרים. לדוגמה, אין API חדש שחשוף לאינטרנט שצריך ללמוד. באופן כללי, דפי אינטרנט לא אמורים להבחין בהבדל בין הפעלה עם Site Isolation לבין הפעלה ללא Site Isolation.

עם זאת, יש כמה חריגים לכלל הזה. להפעלת Site Isolation יש כמה תופעות לוואי עדינות שעשויות להשפיע על האתר שלכם. אנחנו שומרים על רשימת בעיות מוכרות בנושא בידוד אתרים, ומפרטים בהמשך על הבעיות החשובות ביותר.

הפריסה של הדף המלא כבר לא סינכרונית

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

לדוגמה, נניח שיש אתר בשם fluffykittens.example שמתקשר עם ווידג'ט של רשת חברתית שמתארח ב-social-widget.example:

<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  iframe.width = 456;
  iframe.contentWindow.postMessage(
    // The message to send:
    'Meow!',
    // The target origin:
    'https://social-widget.example'
  );
</script>

בהתחלה, הרוחב של <iframe> בווידג'ט של הרשתות החברתיות הוא 123 פיקסלים. אבל לאחר מכן, הדף FluffyKittens משנה את הרוחב ל-456 פיקסלים (מפעיל את הפריסה) ושולח הודעה לווידג'ט החברתי, עם הקוד הבא:

<!-- https://social-widget.example/ -->
<script>
  self.onmessage = () => {
    console.log(document.documentElement.clientWidth);
  };
</script>

בכל פעם שהווידג'ט החברתי מקבל הודעה דרך ה-API של postMessage, הוא מתעד ביומן את רוחב הרכיב <html> ברמה הבסיסית.

איזה ערך רוחב מתועד ביומן? לפני שהתכונה 'בידוד אתר' הופעלה ב-Chrome, התשובה הייתה 456. הגישה למאפיין document.documentElement.clientWidth מאלצת את הפריסה, שהיתה סינכרנית לפני שהופעל בידוד האתרים ב-Chrome. עם זאת, כשהתכונה 'בידוד של אתר' מופעלת, עיצוב מחדש של ווידג'ט חברתי ממקורות שונים מתבצע עכשיו באופן אסינכרוני בתהליך נפרד. לכן, התשובה יכולה להיות עכשיו גם 123, כלומר הערך הישן של width.

אם דף משנה את הגודל של <iframe> ממקורות שונים ואז שולח אליו postMessage, יכול להיות שבמסגרת המקבלת לא יהיה ידוע עדיין הגודל החדש שלה כשהיא תקבל את ההודעה, אם מופעלת Site Isolation. באופן כללי, הדבר עלול לגרום לשיבושים בדפים אם הם מניחים ששינוי בפריסת הדף מופץ באופן מיידי לכל המסגרות בדף.

בדוגמה הספציפית הזו, פתרון חזק יותר יהיה להגדיר את width בפריים ההורה, ולזהות את השינוי ב-<iframe> על ידי האזנה לאירוע resize.

יכול להיות שזמן הקצאת הזמן לטיפול באירועי פריקת נתונים יפוג בתדירות גבוהה יותר

כשמסגרת מנווטת או נסגרת, המסמך הישן וגם כל מסמכי המסגרת המשנית שמוטמעים בו מפעילים את הטיפול שלהם ב-unload. אם הניווט החדש מתרחש באותו תהליך עיבוד (למשל, לניווט מאותו מקור), מנהלי האירועים unload של המסמך הישן ותיבות ה-frame המשניות שלו יכולים לפעול במשך זמן ארוך באופן שרירותי לפני שהם מאפשרים לניווט החדש להתחייב.

addEventListener('unload', () => {
  doSomethingThatMightTakeALongTime();
});

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

עם זאת, גם בלי Site Isolation, חלק מהניווטים במסגרת הראשית מתבצעים בין תהליכים, וזה משפיע על ההתנהגות של ה-handler של הסרת נתונים שנטענו. לדוגמה, אם מנווטים מ-old.example אל new.example על ידי הקלדה של כתובת ה-URL בסרגל הכתובות, תהליך הניווט אל new.example מתבצע בתהליך חדש. מנהלי ההוצאה משימוש של old.example ושל המסגרות המשניות שלו פועלים בתהליך old.example ברקע, אחרי שהדף new.example מוצג, ומנהלי ההוצאה משימוש הישנים מסתיימים אם הם לא מסתיימים תוך פרק זמן קצוב. יכול להיות שמטפלי ההוצאה משימוש לא יסתיימו לפני חלוף הזמן הקצוב לתפוגה, ולכן התנהגות ההוצאה משימוש פחות מהימנה.

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

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

אלה ההשלכות הבסיסיות של הפעלת Site Isolation. צוות Chrome פועל לשיפור האמינות של מנהלי פריקה בתרחישים לדוגמה נפוצים, ככל האפשר. אנחנו מודעים גם לבאגים שבהם מנהלי פריקה של מסגרות משנה עדיין לא יכולים להשתמש בתכונות מסוימות, ואנחנו פועלים כדי לפתור אותם.

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

addEventListener('pagehide', () => {
  const image = new Image();
  img.src = '/end-of-session';
});

גישה טובה יותר וחזקה יותר לאור השינוי הזה היא להשתמש ב-navigator.sendBeacon במקום זאת:

addEventListener('pagehide', () => {
  navigator.sendBeacon('/end-of-session');
});

אם אתם רוצים יותר שליטה בבקשה, אתם יכולים להשתמש באפשרות keepalive של Fetch API:

addEventListener('pagehide', () => {
  fetch('/end-of-session', {keepalive: true});
});

סיכום

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

תודה ל-Alex Moshchuk,‏ Charlie Reis,‏ Jason Miller,‏ Nasko Oskov,‏ Philip Walton,‏ Shubhie Panicker ו-Thomas Steiner שקראו טיוטה של המאמר הזה ונתנו משוב.