Korumalı alana alınmış iframe'lerde eval() işlevini kullanma

Chrome'un uzantı sistemi oldukça katı bir varsayılan İçerik Güvenliği Politikası (İGP) uygular. Politika kısıtlamaları basittir: Komut dosyası satır dışına, ayrı JavaScript dosyalarına taşınmalıdır, satır içi etkinlik işleyicileri addEventListener kullanacak şekilde dönüştürülmelidir ve eval() devre dışı bırakılmalıdır.

Bununla birlikte, çeşitli kitaplıkların performans optimizasyonu ve ifade kolaylığı için eval() ve eval benzeri yapıları (ör. new Function()) kullandığını biliyoruz. Şablon kitaplıkları özellikle bu tür bir uygulamaya eğilimlidir. Bazıları (ör. Angular.js) CSP'yi kutudan çıkar çıkmaz desteklese de popüler çerçevelerin çoğu henüz uzantıların eval'siz dünyasıyla uyumlu bir mekanizmaya güncellenmedi. Bu nedenle, bu işlev için desteğin kaldırılması geliştiriciler için beklenenden daha sorunlu oldu.

Bu dokümanda, bu kitaplıkları güvenlikten ödün vermeden projelerinize dahil etmek için güvenli bir mekanizma olarak korumalı alan kullanımı tanıtılmaktadır.

Korumalı alan neden gerekli?

eval, yürüttüğü kod uzantının yüksek izinli ortamındaki her şeye erişebildiğinden uzantı içinde tehlikelidir. Kullanıcıların güvenliğini ve gizliliğini ciddi şekilde etkileyebilecek çok sayıda güçlü chrome.* API mevcuttur. Basit veri sızıntıları, endişelerimiz arasında en az önemli olanıdır. Sunulan çözüm, eval'nin uzantının verilerine veya yüksek değerli API'lerine erişmeden kod yürütebileceği bir korumalı alan sağlar. Veri yok, API yok, sorun yok.

Bunu, uzantı paketindeki belirli HTML dosyalarını korumalı alan olarak listelememiz sayesinde yapıyoruz. Korumalı alan sayfası her yüklendiğinde benzersiz bir kaynağa taşınır ve chrome.* API'lerine erişimi reddedilir. Bu korumalı alan sayfasını bir iframe aracılığıyla uzantımıza yüklersek ona mesaj iletebilir, bu mesajlar üzerinde bir şekilde işlem yapmasına izin verebilir ve bize bir sonuç döndürmesini bekleyebiliriz. Bu basit mesajlaşma mekanizması, uzantımızın iş akışına eval tarafından yönlendirilen kodu güvenli bir şekilde dahil etmek için ihtiyacımız olan her şeyi bize sağlar.

Korumalı alan oluşturma ve kullanma

Hemen koda dalmak istiyorsanız korumalı alan örnek uzantısını indirip işe başlayın. Handlebars şablon kitaplığının üzerine inşa edilmiş küçük bir mesajlaşma API'sinin çalışan bir örneği olan bu uzantı, işe başlamak için ihtiyacınız olan her şeyi size sunar. Konuyu biraz daha ayrıntılı bir şekilde öğrenmek isteyenler için bu örneği birlikte inceleyelim.

Manifest dosyasında dosyaları listeleme

Bir korumalı alanda çalıştırılması gereken her dosya, sandbox özelliği eklenerek uzantı manifestinde listelenmelidir. Bu önemli bir adımdır ve kolayca unutulabilir. Bu nedenle, korumalı alan dosyanızı manifest dosyasında listelediğinizden emin olun. Bu örnekte, "sandbox.html" olarak adlandırılan dosyayı korumalı alana alıyoruz. Manifest girişi şu şekilde görünür:

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

Korumalı alan dosyası yükleme

Korumalı alan dosyası ile ilginç bir şey yapmak için dosyayı, uzantının kodu tarafından erişilebileceği bir bağlamda yüklememiz gerekir. Burada, sandbox.html bir iframe aracılığıyla uzantı sayfasına yüklendi. Sayfanın JavaScript dosyası, tarayıcı işlemi tıklandığında sayfada iframe bulunarak ve contentWindow üzerinde postMessage() çağrılarak korumalı alana mesaj gönderen kod içerir. Mesaj, context, templateName ve command olmak üzere üç özellik içeren bir nesnedir. context ve command konularını birazdan ele alacağız.

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, '*');
  });

Tehlikeli bir şey yapma

sandbox.html yüklendiğinde Handlebars kitaplığı yüklenir ve Handlebars'ın önerdiği şekilde satır içi bir şablon oluşturulup derlenir:

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>

Bu işlem başarısız olmaz. Handlebars.compile, new Function'u kullansa da işler tam olarak beklendiği gibi işler ve templates['hello'] biçiminde derlenmiş bir şablon elde ederiz.

Sonucu geri gönderme

Uzantı sayfasından komutları kabul eden bir mesaj dinleyicisi ayarlayarak bu şablonu kullanıma sunacağız. Ne yapılması gerektiğini belirlemek için iletilen command değerini kullanırız (yalnızca oluşturmanın ötesinde, şablon oluşturmayı da düşünebilirsiniz). Belki bir şekilde yönetiyorsunuzdur?), context doğrudan şablona aktarılır ve oluşturulur. Oluşturulan HTML, uzantının daha sonra bu HTML ile yararlı bir işlem yapabilmesi için uzantı sayfasına geri gönderilir:

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

Uzantı sayfasına geri döndüğümüzde bu mesajı alır ve bize iletilen html verileri kullanarak ilginç bir şey yaparız. Bu durumda, bu durumu yalnızca bir bildirim aracılığıyla yansıtırız. Ancak bu HTML'yi uzantının kullanıcı arayüzünün bir parçası olarak güvenli bir şekilde kullanmak tamamen mümkündür. innerHTML aracılığıyla eklemek, korumalı alanda oluşturulan içeriğe güvendiğimiz için önemli bir güvenlik riski oluşturmaz.

Bu mekanizma, şablon oluşturmayı kolaylaştırır ancak şablon oluşturmayla sınırlı değildir. Katı bir İçerik Güvenliği Politikası altında kutudan çıkar çıkmaz çalışmayan tüm kodlar korumalı alana yerleştirilebilir. Aslında, programınızın her bir parçasını düzgün şekilde yürütülmesi için gereken en küçük ayrıcalıklar grubuyla kısıtlamak amacıyla, uzantılarınızın doğru şekilde çalışacak bileşenlerini korumalı alana yerleştirmek genellikle yararlıdır. Google I/O 2012'deki Güvenli Web Uygulamaları ve Chrome Uzantıları Yazma sunumu, bu tekniklerin kullanıma yönelik bazı iyi örneklerini sunar ve 56 dakikanızı ayırmaya değerdir.