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

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

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

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

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

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

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

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

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

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

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

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

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

כדי לעשות משהו מעניין עם הקובץ שבארגז החול, אנחנו צריכים לטעון אותו בהקשר שבו אפשר לטפל בו באמצעות קוד התוסף. כאן, sandbox.html נטען דף האירוע של התוסף (eventpage.html) באמצעות iframe. eventpage.js מכיל קוד ששולחת הודעה לארגז החול בכל פעם שמשתמש לוחץ על פעולת הדפדפן על ידי איתור השדה iframe בדף, והפעלת ה-method 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, היא טוענת את ספריית הכינויים, ויוצרת ויוצרת הודעה באופן שבו סרגלי היד מציעים:

<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>

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

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