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ı, satır içi etkinlik işleyiciler 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 new Function() gibi eval() ve eval benzeri yapılar kullandığını biliyoruz. Şablon oluşturma kitaplıkları özellikle bu uygulama tarzına eğilimlidir. Bazıları (Angular.js gibi) CSP'yi kullanıma hazır olarak desteklese de birçok popüler çerçeve, uzantıların eval içermeyen dünyasıyla uyumlu bir mekanizmaya henüz güncellenmemiştir. Bu nedenle, bu işleve yönelik desteğin kaldırılmasının geliştiriciler için beklenenden daha sorun olduğu kanıtlandı.

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

Neden korumalı alan?

eval, çalıştırdığı kod, uzantının yüksek izinli ortamındaki her şeye erişimi olduğundan uzantının içinde tehlikelidir. Kullanıcıların güvenliğini ve gizliliğini önemli ölçüde etkileyebilecek çok sayıda güçlü chrome.* API kullanıma sunulmuştur. Basit veri hırsızlığı, en az endişelendiğimizdir. Sunulan çözüm, eval ürününün, uzantı verilerine veya uzantının yüksek değerli API'lerine erişmeden kod çalıştırabileceği bir korumalı alan olmasıdır. Ne veri, ne API, ne de sorun.

Bunu, belirli HTML dosyalarını uzantı paketi içinde korumalı alan olarak listeleyerek yaparız. Korumalı alana alınmış bir sayfa her yüklendiğinde benzersiz bir kaynağa taşınır ve chrome.* API'lerine erişimi reddedilir. Korumalı alana alınmış bu sayfayı bir iframe aracılığıyla uzantımıza yüklersek söz konusu sayfayı mesaj iletebilir, bu mesajlar üzerinde bir şekilde hareket etmesine izin verebilir ve bize bir sonucu geri vermesini bekleyebiliriz. Bu basit mesajlaşma mekanizması, eval yönlendirmeli kodu uzantımızın iş akışına güvenli bir şekilde eklemek için ihtiyacımız olan her şeyi sağlıyor.

Korumalı alan oluşturma ve kullanma

Doğrudan koda girmek isterseniz korumalı alan örnek uzantısını alın ve kaldırın. Bu, Handlebars şablon kitaplığının üzerine inşa edilmiş küçük bir mesajlaşma API'sinin çalışan bir örneğidir ve başlamak için ihtiyacınız olan her şeyi size sağlayacaktır. Biraz daha açıklama isteyenler için buradaki örneği birlikte inceleyelim.

Manifest'teki dosyaları listeleyin

Korumalı alan içinde çalıştırılması gereken her dosya, bir sandbox özelliği eklenerek uzantı manifest'inde listelenmelidir. Bu önemli bir adımdır ve kolayca unutulabilir. Bu nedenle, korumalı alana alınmış dosyanızın manifest'te listelendiğini tekrar kontrol edin. Bu örnekte, akıllı bir şekilde "sandbox.html" adını oluşturan dosyayı korumalı alana alıyoruz. Manifest girişi şöyle görünür:

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

Korumalı alana alınmış dosyayı yükle

Korumalı alana alınan dosyayla ilginç bir şey yapmak için dosyayı uzantı kodunun ele alınabileceği bir bağlamda yüklememiz gerekir. Burada, sandbox.html bir iframe aracılığıyla bir uzantı sayfasına yüklenmiştir. Sayfanın JavaScript dosyası, tarayıcı işlemi her tıklandığında, sayfada iframe öğesini bulup contentWindow öğesinde postMessage() çağrısı yaparak korumalı alana bir ileti gönderen bir kod içerir. Mesaj üç özellik içeren bir nesnedir: context, templateName ve command. Birazdan context ve command öğelerini inceleyeceğiz.

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 yap

sandbox.html yüklendiğinde, Gidon kitaplığını yükler ve Gidbar'ın önerdiği şekilde bir satır içi şablon oluşturup derler:

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>

Hata yok. Handlebars.compile, new Function kodunu kullansa bile işler tam olarak beklendiği gibi çalışır ve templates['hello'] içinde derlenmiş bir şablon elde ederiz.

Sonucu geri verme

Uzantı sayfasından komutları kabul eden bir mesaj işleyici oluşturarak bu şablonu kullanıma sunacağız. Ne yapılması gerektiğini belirlemek için iletilen command bilgisini kullanacağız (yalnızca oluşturma işleminden daha fazlasını yapmayı, belki de şablon oluşturmayı düşünebilirsiniz. Belki bunları bir şekilde yönetir misiniz?) ve context, oluşturma için doğrudan şablona iletilir. Oluşturulan HTML, uzantı sayfasına geri gönderilir ve böylece uzantı daha sonra bununla ilgili faydalı bir şey yapabilir:

 <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 döndüğünüzde, bu mesajı alıp ilettiğimiz html verileriyle ilginç bir şey yaparız. Bu örnekte, bunu bir bildirim ile tekrarlamak isteriz, 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. Korumalı alan içinde oluşturulan içeriğe güvendiğimiz için innerHTML aracılığıyla eklemek önemli bir güvenlik riski oluşturmaz.

Bu mekanizma, şablon oluşturmayı basitleştirir ancak elbette şablon oluşturmakla sınırlı değildir. Katı İçerik Güvenliği Politikası kapsamında çalışmayan tüm kodlar korumalı alana alınabilir. Aslında, programınızın her bir parçasını doğru yürütülmesi için gereken en küçük ayrıcalık grubuyla kısıtlamak üzere, doğru şekilde çalışacak uzantılarınızın korumalı alana alınması genellikle yararlıdır. Google I/O 2012'deki Write Secure Web Apps and Chrome Extensions (Güvenli Web Uygulamaları ve Chrome Uzantıları Yazma) sunumunda, bu tekniği uygulamalı olarak ne kadar başarılı örneklerle görebilirsiniz? Bu sunum için 56 dakikanızı ayırmanız yeterli.