Menggunakan eval() di iframe dalam sandbox

Sistem ekstensi Chrome menerapkan Kebijakan Keamanan Konten (CSP) default yang cukup ketat. Batasan kebijakannya sederhana: skrip harus dipindahkan ke luar baris ke file JavaScript terpisah, pengendali peristiwa inline harus dikonversi untuk menggunakan addEventListener, dan eval() dinonaktifkan.

Namun, kami menyadari bahwa berbagai library menggunakan konstruksi seperti eval() dan eval seperti new Function() untuk pengoptimalan performa dan kemudahan berekspresi. Library template sangat rentan terhadap gaya implementasi ini. Meskipun beberapa framework (seperti Angular.js) mendukung CSP secara langsung, banyak framework populer yang belum diupdate ke mekanisme yang kompatibel dengan dunia tanpa eval ekstensi. Menghapus dukungan untuk fungsi tersebut terbukti lebih bermasalah dari yang diharapkan bagi developer.

Dokumen ini memperkenalkan sandboxing sebagai mekanisme yang aman untuk menyertakan library-library ini dalam project Anda tanpa mengorbankan keamanan.

Mengapa sandbox?

eval berbahaya di dalam ekstensi karena kode yang dieksekusi memiliki akses ke semua hal di lingkungan izin tinggi ekstensi. Ada banyak API chrome.* canggih yang tersedia dan dapat berdampak serius pada keamanan dan privasi pengguna; ekstraksi data sederhana adalah hal yang paling tidak perlu dikhawatirkan. Solusi yang ditawarkan adalah sandbox tempat eval dapat mengeksekusi kode tanpa akses ke data ekstensi atau API bernilai tinggi dari ekstensi. Tidak ada data, tidak ada API, tidak masalah.

Kita melakukannya dengan mencantumkan file HTML tertentu di dalam paket ekstensi sebagai sandbox. Setiap kali halaman dalam sandbox dimuat, halaman tersebut akan dipindahkan ke asal yang unik, dan akan ditolak akses ke chrome.* API. Jika memuat halaman dengan sandbox ini ke dalam ekstensi melalui iframe, kita dapat meneruskan pesan, membiarkannya bertindak berdasarkan pesan tersebut dengan cara tertentu, dan menunggunya meneruskan kembali hasil. Mekanisme pesan yang sederhana ini memberikan semua yang kami butuhkan untuk menyertakan aman berbasis eval dalam alur kerja ekstensi kita.

Membuat dan menggunakan sandbox

Jika Anda ingin langsung mempelajari kode, dapatkan ekstensi contoh sandboxing dan mulailah. Ini adalah contoh kerja API pesan kecil yang dibuat di atas library template Handlebars, dan akan memberi Anda semua yang diperlukan untuk memulai. Bagi Anda yang ingin mendapatkan penjelasan lebih lanjut, mari kita pelajari contoh tersebut bersama-sama di sini.

Membuat daftar file dalam manifes

Setiap file yang harus dijalankan di dalam sandbox harus tercantum dalam manifes ekstensi dengan menambahkan elemen sandbox. Ini adalah langkah penting, dan mudah terlupakan, jadi periksa kembali file dengan sandbox Anda tercantum dalam manifes. Dalam contoh ini, kami meng-{i>sandbox<i} file dengan cerdas bernama "sandbox.html". Entri manifes terlihat seperti ini:

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

Memuat file dalam sandbox

Untuk melakukan sesuatu yang menarik dengan file dengan sandbox, kita perlu memuat file tersebut dalam konteks yang dapat ditangani oleh kode ekstensi. Di sini, sandbox.html telah dimuat ke dalam halaman ekstensi melalui iframe. File javaScript halaman berisi kode yang mengirimkan pesan ke dalam sandbox setiap kali tindakan browser diklik dengan mencari iframe di halaman, dan memanggil postMessage() di contentWindow. Pesan adalah objek yang berisi tiga properti: context, templateName, dan command. Kita akan membahas context dan command sebentar lagi.

service-worker.js:

chrome.action.onClicked.addListener(() => {
  chrome.tabs.create({
    url: 'mainpage.html'
  });
  console.log('Opened a tab with a sandboxed page!');
});

extension-page.js:

let counter = 0;
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('reset').addEventListener('click', function () {
    counter = 0;
    document.querySelector('#result').innerHTML = '';
  });

  document.getElementById('sendMessage').addEventListener('click', function () {
    counter++;
    let message = {
      command: 'render',
      templateName: 'sample-template-' + counter,
      context: { counter: counter }
    };
    document.getElementById('theFrame').contentWindow.postMessage(message, '*');
  });

Lakukan hal berbahaya

Saat sandbox.html dimuat, library ini akan memuat library Handlebars, serta membuat dan mengompilasi sebuah inline seperti yang disarankan oleh Handlebars:

extension-page.html:

<!DOCTYPE html>
<html>
  <head>
    <script src="mainpage.js"></script>
    <link href="styles/main.css" rel="stylesheet" />
  </head>
  <body>
    <div id="buttons">
      <button id="sendMessage">Click me</button>
      <button id="reset">Reset counter</button>
    </div>

    <div id="result"></div>

    <iframe id="theFrame" src="sandbox.html" style="display: none"></iframe>
  </body>
</html>

sandbox.html:

   <script id="sample-template-1" type="text/x-handlebars-template">
      <div class='entry'>
        <h1>Hello</h1>
        <p>This is a Handlebar template compiled inside a hidden sandboxed
          iframe.</p>
        <p>The counter parameter from postMessage() (outer frame) is:
          </p>
      </div>
    </script>

    <script id="sample-template-2" type="text/x-handlebars-template">
      <div class='entry'>
        <h1>Welcome back</h1>
        <p>This is another Handlebar template compiled inside a hidden sandboxed
          iframe.</p>
        <p>The counter parameter from postMessage() (outer frame) is:
          </p>
      </div>
    </script>

Ini tidak gagal! Meskipun Handlebars.compile akhirnya menggunakan new Function, semuanya berfungsi seperti yang diharapkan, dan kita akan mendapatkan template yang dikompilasi di templates['hello'].

Meneruskan hasilnya kembali

Kita akan menyediakan template ini untuk digunakan dengan menyiapkan pemroses pesan yang menerima perintah dari halaman ekstensi. Kita akan menggunakan command yang diteruskan untuk menentukan apa yang harus dilakukan (Anda dapat bayangkan melakukan lebih dari sekadar {i>rendering<i}; mungkin membuat {i>template<i}? Mungkin mengelolanya dengan cara tertentu?), dan context akan diteruskan ke template secara langsung untuk rendering. HTML yang dirender akan diteruskan kembali ke halaman ekstensi sehingga ekstensi dapat melakukan sesuatu yang bermanfaat di lain waktu:

 <script>
      const templatesElements = document.querySelectorAll(
        "script[type='text/x-handlebars-template']"
      );
      let templates = {},
        source,
        name;

      // precompile all templates in this page
      for (let i = 0; i < templatesElements.length; i++) {
        source = templatesElements[i].innerHTML;
        name = templatesElements[i].id;
        templates[name] = Handlebars.compile(source);
      }

      // Set up message event handler:
      window.addEventListener('message', function (event) {
        const command = event.data.command;
        const template = templates[event.data.templateName];
        let result = 'invalid request';

       // if we don't know the templateName requested, return an error message
        if (template) {
          switch (command) {
            case 'render':
              result = template(event.data.context);
              break;
            // you could even do dynamic compilation, by accepting a command
            // to compile a new template instead of using static ones, for example:
            // case 'new':
            //   template = Handlebars.compile(event.data.templateSource);
            //   result = template(event.data.context);
            //   break;
              }
        } else {
            result = 'Unknown template: ' + event.data.templateName;
        }
        event.source.postMessage({ result: result }, event.origin);
      });
    </script>

Kembali ke halaman ekstensi, kita akan menerima pesan ini, dan melakukan sesuatu yang menarik dengan data html yang telah diteruskan kepada kita. Dalam hal ini, kita hanya akan menayangkannya melalui notifikasi, tetapi Anda dapat menggunakan HTML ini dengan aman sebagai bagian dari UI ekstensi. Menyisipkannya melalui innerHTML tidak menimbulkan risiko keamanan yang signifikan karena kami memercayai konten yang telah dirender dalam sandbox.

Mekanisme ini membuat pembuatan template menjadi mudah, tetapi tentu saja tidak terbatas pada pembuatan template. Apa saja kode yang tidak berfungsi secara langsung dalam Kebijakan Keamanan Konten yang ketat dapat di-sandbox; inci Namun, sering kali ada baiknya untuk melakukan sandbox komponen ekstensi Anda yang akan berjalan dengan benar dalam urutan untuk membatasi setiap bagian dari program Anda ke rangkaian hak istimewa terkecil yang diperlukan agar dapat berjalan dengan baik. Presentasi Menulis Aplikasi Web dan Ekstensi Chrome yang Aman dari Google I/O 2012 memberikan beberapa contoh bagus tentang penerapan teknik ini, dan Anda akan menghabiskan waktu 56 menit untuk menontonnya.