משחקים בבטחה ב-IFrames בארגז חול

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

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

ההרשאה הנמוכה ביותר

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

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

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

מגעיל, אבל אימות

ה'ציוץ' של Twitter הוא דוגמה מצוינת לפונקציונליות שיכולה להיות מוטמעות בבטחה באתר באמצעות ארגז חול. Twitter מאפשר לכם להטמיע את לחצן באמצעות iframe עם הקוד הבא:

<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
        style="border: 0; width:130px; height:20px;"></iframe>

כדי להבין מה ניתן לנעול, נבחן בקפידה אילו יכולות שנדרשת כדי ללחוץ על הלחצן. ה-HTML שנטען למסגרת מריץ מעט JavaScript מהשרתים של Twitter, ויוצר חלון קופץ שמאוכלס במשתנה ממשק של ציוץ כשלוחצים עליו. לממשק הזה נדרשת גישה קובצי Cookie כדי לקשר את הציוץ לחשבון הנכון, ויש צורך ביכולת כדי לשלוח את טופס הציוץ. זה פחות או יותר; הפריים לא צריך טוענים יישומי פלאגין כלשהם, הם לא צריכים לנווט בחלון שברמה העליונה, או מספר ביטים אחרים של פונקציונליות. מאחר שהוא לא זקוק להרשאות האלה, נסיר אותן על ידי הרצה בארגז חול (sandboxing) של תוכן הפריים.

ארגז החול פועל על סמך רשימת היתרים. אנחנו מתחילים בהסרה של כל הרשאות אפשריות, ואז להפעיל מחדש יכולות ספציפיות על ידי הוספת דגלים ספציפיים לתצורה של ארגז החול. בווידג'ט של Twitter, החליט להפעיל JavaScript, חלונות קופצים, שליחת טפסים קובצי Cookie. כדי לעשות את זה, צריך להוסיף את המאפיין sandbox ל-iframe עם בערך הבא:

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
    src="https://platform.twitter.com/widgets/tweet_button.html"
    style="border: 0; width:130px; height:20px;"></iframe>

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

שליטה מפורטת על יכולות

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

בהינתן iframe עם מאפיין Sandbox ריק, המסמך הממוסגר יוגדר כארגז חול במלואו, בכפוף ל- להגבלות הבאות:

  • JavaScript לא יפעל במסמך הממוסגר. מעבר לזה, JavaScript נטען באופן מפורש באמצעות תגי סקריפטים, אך גם באמצעות רכיבי handler של אירועים מוטבעים ו-JavaScript: כתובות URL. זה אומר גם שתוכן שכלול בתגי noscript יוצגו, בדיוק כאילו המשתמש השבית את הסקריפט בעצמו.
  • המסמך הממוסגר נטען למקור ייחודי, כלומר כל בדיקות מקור זהה ייכשלו. מקורות ייחודיים לא תואמים אף פעם למקורות אחרים, אפילו את עצמם. בין ההשפעות האחרות, המשמעות היא שבמסמך לא גישה לנתונים שמאוחסנים בקובצי cookie של מקור כלשהו או במנגנוני אחסון אחרים (אחסון DOM, Indexed DB וכו').
  • המסמך הממוסגר לא יכול ליצור חלונות או תיבות דו-שיח חדשים (דרך window.open או למשל, target="_blank").
  • אי אפשר לשלוח טפסים.
  • יישומי הפלאגין לא ייטענו.
  • המסמך הממוסגר יכול לנווט רק בעצמו, ולא ברמה העליונה שלו ברמה העליונה. הגדרה של window.top.location גורמת לחריגה, ולחיצה על קישור עם לא תהיה השפעה על target="_top".
  • תכונות שמופעלות באופן אוטומטי (רכיבי טפסים שמתמקדים באופן אוטומטי, הפעלה אוטומטית סרטונים וכדומה) חסומים.
  • לא ניתן לקבל את נעילת המצביע.
  • המערכת מתעלמת מהמאפיין seamless ב-iframes שהמסמך הממוסגר מכיל.

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

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

  • allow-forms מאפשר שליחת טפסים.
  • allow-popups מאפשר חלונות קופצים (מזעזע!).
  • allow-pointer-lock מאפשרת נעילה (מפתיע!) של מצביע העכבר.
  • allow-same-origin מאפשר למסמך לשמור את המקור שלו. דפים נטענו מ-https://example.com/ עדיין תהיה לך גישה לנתוני המקור הזה.
  • השירות allow-scripts מאפשר הפעלת JavaScript וגם מאפשר לתכונות יופעלו באופן אוטומטי (מכיוון שיישום שלהן יכול להיות טריוויאלי).
  • הפקודה allow-top-navigation מאפשרת למסמך לצאת מהפריים באמצעות בניווט בחלון שברמה העליונה.

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

  • הרכיב allow-scripts נדרש, מכיוון שהדף שנטען למסגרת מריץ חלק JavaScript לטיפול באינטראקציה של משתמשים.
  • השדה allow-popups נדרש, מכיוון שהלחצן מקפיץ טופס ציוץ בפורמט חלון.
  • השדה allow-forms הוא שדה חובה, מאחר שטופס הציוץ צריך להיות ניתן להגשה.
  • allow-same-origin הוא הכרחי, כי אחרת קובצי cookie של twitter.com היו לא יהיה נגיש, והמשתמש לא הצליח להתחבר כדי לפרסם את הטופס.

דבר חשוב שחשוב לציין הוא שהסימונים של ארגז החול (Sandbox) שחלים על מסגרת הם גם יחולו על כל החלונות או המסגרות שנוצרו ב-Sandbox. המשמעות היא שיש לנו כדי להוסיף את allow-forms לארגז החול של המסגרת, למרות שהטופס קיים רק. החלון הקופץ שהמסגרת קופצת.

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

הפרדת הרשאות

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

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

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

הרצה בטוחה בארגז חול ל-eval()

באמצעות הרצה בארגז חול (sandboxing) ב-API של postMessage, את ההצלחה של המודל הזה קל מאוד ליישם באינטרנט. חלקים של האפליקציה שלך יכולה לגור בארגזי iframe בארגזי חול, ומסמך ההורה יכול למתווך את התקשורת ביניהם על ידי פרסום הודעות והאזנה תשובות מדויקות. מבנה כזה מבטיח שניצול כל חלק של לגרום נזק מינימלי. יש גם יתרון בכך שצריך לאלץ אותך ליצור נקודות שילוב ברורות, כדי שתדעו בדיוק איפה אתם צריכים להיות חשוב לאמת את הקלט והפלט. בואו נסתכל לדוגמה על צעצוע, רק כדי לראות איך זה עובד.

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

<!-- frame.html -->
<!DOCTYPE html>
<html>
    <head>
    <title>Evalbox's Frame</title>
    <script>
        window.addEventListener('message', function (e) {
        var mainWindow = e.source;
        var result = '';
        try {
            result = eval(e.data);
        } catch (e) {
            result = 'eval() threw an exception.';
        }
        mainWindow.postMessage(result, event.origin);
        });
    </script>
    </head>
</html>

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

ב-handler, אנחנו תופסים את המאפיין source של האירוע, שהוא ההורה. חלון. נשתמש במספר הזה כדי לשלוח חזרה את התוצאה של העבודה הקשה שעשינו, בוצע. לאחר מכן נעשה את העבודה הקשה, ונעביר את הנתונים שקיבלנו eval() השיחה נחשפה לבלוק של ניסיון, אבל אפשר לבצע פעולות אסורות בתוך iframe בארגז חול (sandbox), ייצרו לעיתים קרובות חריגים של DOM; ניפגש האלה ולדווח על הודעת שגיאה ידידותית במקום זאת. לבסוף, אנחנו מפרסמים את התוצאה. בחזרה לחלון ההורה. זה דברים די פשוטים.

גם ההורה לא מורכב בצורה דומה. אנחנו ניצור ממשק משתמש קטנטן עם textarea לקוד, ו-button להפעלה, ונשלוף frame.html באמצעות בארגז החול iframe, מאפשר רק ביצוע של הסקריפט:

<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
        id='sandboxed'
        src='frame.html'></iframe>

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

window.addEventListener('message',
    function (e) {
        // Sandboxed iframes which lack the 'allow-same-origin'
        // header have "null" rather than a valid origin. This means you still
        // have to be careful about accepting data via the messaging API you
        // create. Check that source, and validate those inputs!
        var frame = document.getElementById('sandboxed');
        if (e.origin === "null" &amp;&amp; e.source === frame.contentWindow)
        alert('Result: ' + e.data);
    });

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

function evaluate() {
    var frame = document.getElementById('sandboxed');
    var code = document.getElementById('code').value;
    // Note that we're sending the message to "*", rather than some specific
    // origin. Sandboxed iframes which lack the 'allow-same-origin' header
    // don't have an origin which you can target: you'll have to send to any
    // origin, which might alow some esoteric attacks. Validate your output!
    frame.contentWindow.postMessage(code, '*');
}

document.getElementById('safe').addEventListener('click', evaluate);

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

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

עם זאת, שימו לב שצריך להיזהר מאוד כשעובדים עם תוכן ממוסגר שמגיע מאותו המקור כמו ההורה. אם דף https://example.com/ ממסגר דף אחר באותו מקור באמצעות ארגז חול (sandbox) שכולל את הדגל allow-same-origin וגם את הדגלים allow-scripts, אז הדף הממוסגר יכול להגיע אל ההורה ולהסיר את מאפיין ה-Sandbox לגמרי.

הפעלה בארגז החול

Sandboxing זמין עבורך כעת במגוון דפדפנים: Firefox 17 ואילך, IE10+, ו-Chrome בזמן הכתיבה (לקניון, כמובן, יש קובץ עדכני טבלת תמיכה). המערכת מחילה את sandbox ל-iframes הנכללים, מאפשרים לך להעניק הרשאות מסוימות של התוכן שהם מציגים, רק את ההרשאות הדרושות כדי התוכן כדי לפעול כראוי. כך יש לכם הזדמנות להפחית את הסיכון המשויך להכללת תוכן של צד שלישי, מעל ומעבר כבר אפשרית בעזרת Content Security מדיניות.

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

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

קריאה נוספת

  • "הפרדת הרשאות באפליקציות HTML5" הוא מאמר מעניין שעוסק בתכנון של מסגרת קטנה, ואת היישום שלו בשלוש אפליקציות HTML5 קיימות.

  • ארגז חול יכול להיות עוד יותר גמיש בשילוב עם שני iframe חדשים אחרים מאפיינים: srcdoc, ו-seamless. השיטה הראשונה מאפשרת לאכלס מסגרת בתוכן ללא התקורה בקשת HTTP, והשנייה מאפשרת לסגנון לזרום לתוכן הממוסגר. לשניהם יש כרגע תמיכת דפדפן די גרועה (Chrome ו-WebKit) ללילה). אבל יהיה שילוב מעניין בעתיד. תוכלו: לדוגמה, תגובות ב-Sandbox על כתבה באמצעות הקוד הבא:

        <iframe sandbox seamless
                srcdoc="<p>This is a user's comment!
                           It can't execute script!
                           Hooray for safety!</p>"></iframe>