שימוש ב-eval בתוספים ל-Chrome

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

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

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

למה כדאי להשתמש ב-Sandbox?

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

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

יצירת ארגז חול ושימוש בו.

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

הצגת רשימה של קבצים במניפסט

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

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

טעינת הקובץ שבארגז החול

כדי לבצע פעולה מעניינת בקובץ שנמצא בארגז החול, אנחנו צריכים לטעון אותו בהקשר שבו קוד התוסף יכול לטפל בו. במקרה הזה, sandbox.html נטען לדף האירוע (eventpage.html) של התוסף דרך iframe. eventpage.js מכיל קוד ששולח הודעה לארגז החול בכל פעם שלוחצים על פעולת הדפדפן על ידי מציאת iframe בדף והפעלת השיטה postMessage ב-contentWindow שלה. ההודעה היא אובייקט שמכיל שני מאפיינים: context ו-command. עוד רגע ניכנס לשניהם.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
למידע כללי על ה-API של postMessage, אפשר לעיין במסמכי התיעוד של postMessage ב-MDN . הוא שלם ושווה קריאה. באופן ספציפי, חשוב לשים לב לכך שניתן להעביר נתונים הלוך ושוב רק אם ניתן להציג אותם בסדרה. למשל, פונקציות לא כלולות.

ביצוע פעולה מסוכנת

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

<script src="handlebars-1.0.0.beta.6.js"></script>
<script id="hello-world-template" type="text/x-handlebars-template">
  <div class="entry">
    <h1>Hello, !</h1>
  </div>
</script>
<script>
  var templates = [];
  var source = document.getElementById('hello-world-template').innerHTML;
  templates['hello'] = Handlebars.compile(source);
</script>

זה לא נכשל! למרות ש-Handlebars.compile מסתיים בשימוש ב-new Function, הדברים פועלים בדיוק כצפוי ובסופו של דבר נוצרת תבנית מורכבת ב-templates['hello'].

החזר את התוצאה

התבנית הזו תהיה זמינה לשימוש על ידי הגדרת מעבד הודעות שמקבל פקודות מדף האירוע. נשתמש ב-command כדי לקבוע מה צריך לעשות (למשל, לעשות יותר מאשר רינדור, למשל יצירת תבניות? אולי לנהל אותם בצורה כלשהי?), וה-context יועבר ישירות לתבנית לצורך עיבוד. ה-HTML שעבר עיבוד יוחזר לדף האירוע כדי שהתוסף יוכל לעשות איתו משהו שימושי בהמשך:

<script>
  window.addEventListener('message', function(event) {
    var command = event.data.command;
    var name = event.data.name || 'hello';
    switch(command) {
      case 'render':
        event.source.postMessage({
          name: name,
          html: templates[name](event.data.context)
        }, event.origin);
        break;

      // case 'somethingElse':
      //   ...
    }
  });
</script>

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

מנגנון זה הופך את תהליך יצירת התבניות לפשוט, אבל כמובן שהוא אינו מוגבל ליצירת תבניות. אפשר להעביר לארגז חול כל קוד שלא פועל מהתיבה שבמדיניות מחמירה של Content Security Policy. למעשה, כדאי להשתמש ברכיבי ארגז החול של התוספים שלכם כדי שיפעלו בצורה תקינה כדי להגביל כל חלק בתוכנית לקבוצת ההרשאות הקטנה ביותר שנדרשת להפעלתו באופן תקין. המצגת כתיבת אפליקציות אינטרנט מאובטחות ותוספים ל-Chrome מ-Google I/O 2012 מספקת כמה דוגמאות טובות לשיטה הזו בפעולה, ושווה 56 דקות מזמנכם.