Menggunakan eval() di iframe dalam sandbox

Sistem ekstensi Chrome menerapkan Kebijakan Keamanan Konten (CSP) default yang cukup ketat. Pembatasan kebijakan cukup mudah: skrip harus dipindahkan di 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 yang mirip eval() dan eval seperti new Function() untuk pengoptimalan performa dan kemudahan ekspresi. Library pemberian template sangat rentan terhadap gaya implementasi ini. Meskipun beberapa framework (seperti Angular.js) langsung mendukung CSP, banyak framework populer belum diupdate ke mekanisme yang kompatibel dengan dunia tanpa eval ekstensi. Oleh karena itu, menghapus dukungan untuk fungsi tersebut terbukti lebih bermasalah daripada yang diharapkan bagi developer.

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

Mengapa {i>sandbox<i}?

eval berbahaya di dalam ekstensi karena kode yang dijalankan memiliki akses ke semua yang ada di lingkungan izin tinggi ekstensi tersebut. Tersedia banyak API chrome.* canggih yang dapat berdampak buruk pada keamanan dan privasi pengguna; pemindahan data yang tidak sah adalah hal yang tidak perlu kami khawatirkan. Solusi yang ditawarkan adalah sandbox tempat eval dapat mengeksekusi kode tanpa akses ke data ekstensi atau API bernilai tinggi ekstensi. Tanpa data, API, tidak masalah.

Kami melakukannya dengan mencantumkan file HTML tertentu di dalam paket ekstensi sebagai di-sandbox. Setiap kali dimuat halaman dalam sandbox, halaman akan dipindahkan ke asal unik, dan akses ke chrome.* API akan ditolak. Jika memuat halaman dalam sandbox ini ke ekstensi melalui iframe, kita dapat meneruskannya pesan, membiarkannya bertindak atas pesan tersebut dengan cara tertentu, dan menunggu hingga meneruskan hasil kepada kita. Mekanisme pesan sederhana ini memberi kita semua yang diperlukan untuk menyertakan kode berbasis eval dengan aman dalam alur kerja ekstensi.

Membuat dan menggunakan sandbox

Jika Anda ingin langsung mempelajari kode, ambil ekstensi contoh sandboxing dan lepaskan. Ini adalah contoh aktif dari API pesan kecil yang dibuat di atas library template Handlebars, dan akan memberi Anda semua yang diperlukan untuk memulai. Bagi Anda yang ingin mendapat penjelasan lebih lanjut, mari kita bahas bersama di sini.

Mencantumkan file dalam manifes

Setiap file yang harus dijalankan di dalam sandbox harus tercantum dalam manifes ekstensi dengan menambahkan properti sandbox. Ini adalah langkah penting, dan mudah dilupakan, jadi periksa kembali apakah file yang di-sandbox Anda tercantum dalam manifes. Dalam contoh ini, kita akan melakukan sandbox file dengan nama "sandbox.html". Entri manifes terlihat seperti ini:

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

Memuat file yang di-sandbox

Untuk melakukan sesuatu yang menarik dengan file yang di-sandbox, kita perlu memuatnya dalam konteks tempat file tersebut dapat ditangani oleh kode ekstensi. Di sini, sandbox.html telah dimuat ke halaman ekstensi melalui iframe. File javaScript halaman berisi kode yang mengirim pesan ke sandbox setiap kali tindakan browser diklik dengan menemukan iframe di halaman, dan memanggil postMessage() di contentWindow-nya. Pesan ini adalah objek yang berisi tiga properti: context, templateName, dan command. Kita akan mendalami 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 yang berbahaya

Saat dimuat, sandbox.html akan memuat library Handlebars, serta membuat dan mengompilasi template inline seperti yang disarankan Handlebar:

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>

Tindakan ini tidak gagal. Meskipun Handlebars.compile akhirnya menggunakan new Function, semuanya berfungsi persis seperti yang diharapkan, dan kita akan menghasilkan template yang dikompilasi di templates['hello'].

Meneruskan kembali hasilnya

Kami 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 membayangkan tidak sekadar rendering; mungkin membuat template? Mungkin mengelolanya dalam beberapa cara?), dan context akan diteruskan ke template secara langsung untuk rendering. HTML yang dirender akan diteruskan kembali ke halaman ekstensi sehingga nanti ekstensi dapat melakukan sesuatu yang berguna:

 <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. Dalam hal ini, kita hanya akan menggemakannya melalui notifikasi, tetapi penggunaan HTML ini dapat dilakukan 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. Setiap kode yang tidak langsung berfungsi berdasarkan Kebijakan Keamanan Konten yang ketat dapat di-sandbox. Bahkan, sering kali sangat berguna untuk melakukan sandbox komponen ekstensi Anda yang akan berjalan dengan benar untuk membatasi setiap bagian program Anda ke rangkaian hak istimewa terkecil yang diperlukan agar dapat dijalankan dengan benar. Presentasi Menulis Aplikasi Web dan Ekstensi Chrome yang Aman dari Google I/O 2012 memberikan beberapa contoh bagus dari penerapan teknik ini, dan bernilai 56 menit dari waktu Anda.