Korzystanie z eval w rozszerzeniach do Chrome

System rozszerzeń Chrome wymusza dość rygorystyczne domyślne zasady Content Security Policy (CSP). Ograniczenia wynikające z zasad są proste: skrypt musi zostać przeniesiony poza wiersz do osobnego JavaScript, wbudowane moduły obsługi zdarzeń muszą być przekonwertowane, aby można było używać addEventListener, a eval() to wyłączono. Aplikacje Chrome mają jeszcze bardziej rygorystyczne zasady i jesteśmy dość zadowoleni z zabezpieczeń właściwości udostępnianych przez te zasady.

Zdajemy sobie sprawę, że w różnych bibliotekach używane są konstrukcje podobne do eval() i eval, takie jak new Function() ze względu na optymalizację skuteczności i swobodę wypowiedzi. Biblioteki szablonów są szczególnie podatne na ten styl implementacji. Chociaż niektóre (np. Angular.js) obsługują CSP jednak wiele popularnych platform nie zostało jeszcze zaktualizowanych i nie zaoferowano w nich mechanizmu świat bez eval. Usunięcie obsługi tej funkcji okazało się bardziej problematyczne niż oczekiwano dla deweloperów.

W tym dokumencie przedstawiamy piaskownicę jako bezpieczny mechanizm umożliwiający uwzględnianie tych bibliotek w projektach bez obniżania poziomu bezpieczeństwa. Dla zwięzłości używamy terminu rozszerzenia, ale ma zastosowanie do aplikacji w równym stopniu.

Dlaczego piaskownica?

Element eval jest niebezpieczny w rozszerzeniu, ponieważ uruchamiany przez niego kod ma dostęp do wszystkich elementów z środowisko wysokiej dostępności rozszerzenia. Dostępnych jest wiele zaawansowanych interfejsów API chrome.*, które pozwalają poważnie wpływać na bezpieczeństwo i prywatność użytkowników; prosty wydobycie danych to dla nas najmniejszy problem. Oferowanym rozwiązaniem jest piaskownica, w której eval może uruchamiać kod bez dostępu do dane rozszerzenia lub wartościowe interfejsy API rozszerzenia. Brak danych, brak interfejsów API, brak problemów.

Osiągamy to, umieszczając w pakiecie rozszerzenia listę określonych plików HTML, które mają być umieszczone w piaskownicy. Przy każdym wczytywaniu strony w piaskownicy jest ona przenoszona do unikalnego źródła i odrzucana dostęp do interfejsów API usługi chrome.*. Jeśli wczytamy tę stronę piaskownicy w naszym rozszerzeniu za pomocą iframe, możemy przekazać mu wiadomości, pozwolić mu na działanie na podstawie tych wiadomości i poczekać, aż przekaże nam wynik. Ten prosty mechanizm przesyłania wiadomości zapewnia nam wszystko, czego potrzebujemy, aby bezpiecznie uwzględnić kod sterowany przez eval w przepływie pracy rozszerzenia.

Tworzenie piaskownicy i korzystanie z niej.

Jeśli chcesz od razu zagłębić się w kod, ściągnij przykładowe rozszerzenie do piaskownicy i wykonaj wyłączone. To działający przykład niewielkiego interfejsu API do przesyłania wiadomości utworzonego w górnej części kierunków kierownicy. z biblioteką szablonów. Powinien on dać Ci wszystko, czego potrzebujesz na początek. Jeśli chcesz dowiedzieć się więcej, przeanalizujmy ten przykład.

Wyświetlanie listy plików w pliku manifestu

Każdy plik, który powinien zostać uruchomiony w środowisku piaskownicy, musi być wymieniony w manifeście rozszerzenia przez dodanie parametru sandbox. To bardzo ważna czynność, którą łatwo zapomnieć, dlatego plik znajdujący się w trybie piaskownicy jest wymieniony w pliku manifestu. W tym przykładzie sprytnie zamknęliśmy plik w piaskownicy o nazwie „sandbox.html”. Wpis w pliku manifestu wygląda tak:

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

Załaduj plik z piaskownicy

Aby móc wykonać interesującą czynność z pliku w piaskownicy, musimy go załadować w kontekście, w którym kod rozszerzenia może się do niego odwoływać. Strona sandbox.html została wczytana do sekcji Strona zdarzenia (eventpage.html) rozszerzenia za pomocą iframe. Plik eventpage.js zawiera kod, który wysyła wiadomość do piaskownicy, gdy użytkownik kliknie działanie przeglądarki. Aby to zrobić, kod wyszukuje na stronie element iframe i wywołuje metodę postMessage w elementzie contentWindow. Wiadomość to obiekt zawierający 2 właściwości: contextcommand. Za chwilę omówimy oba te rozwiązania.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
Ogólne informacje o interfejsie postMessage API znajdziesz w dokumentacji postMessage w MDN . Jest kompletna i warto ją przeczytać. Zwróć uwagę, że dane można przesyłać tylko wtedy, gdy można je zakodować. Funkcje to nie są na przykład funkcje.

Zrób coś niebezpiecznego

Po załadowaniu sandbox.html wczytuje bibliotekę „Handlebars” i tworzy i kompiluje link w tekście w taki sposób, w jaki sugeruje to aplikacja Handlebars:

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

To nie zadziała. Mimo że Handlebars.compile korzysta z new Function, wszystko działa zgodnie z oczekiwaniami i ostatecznie otrzymujemy skompilowany szablon w templates['hello'].

Przekaż wynik z powrotem

Udostępnimy ten szablon do użytku po skonfigurowaniu odbiornika wiadomości, który akceptuje polecenia na stronie Wydarzenie. Użyjemy przekazanej wartości command, aby określić, co należy zrobić (możesz wyobrazić sobie coś więcej niż renderowanie, np. tworzenie szablonów). Być może w niektórych przypadkach ?), a elementy context będą przekazywane bezpośrednio do szablonu w celu renderowania. Wyrenderowany kod HTML zostanie przekazany z powrotem na stronę wydarzenia, aby rozszerzenie mogło później z niego skorzystać:

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

Na stronie wydarzenia otrzymamy tę wiadomość i zrobimy coś interesującego z funkcją html przekazywane przez nas dane. W tym przypadku powtórzymy to za pomocą powiadomienia na pulpicie, ale pozwala na bezpieczne użycie tego kodu HTML jako części interfejsu rozszerzenia. Wstawianie go przez Usługa innerHTML nie stanowi poważnego zagrożenia dla bezpieczeństwa, ponieważ jest nawet całkowita i nie stanowi zagrożenia dla piaskownicy za pomocą sprytnego ataku nie mogliby wstrzyknąć niebezpiecznego skryptu lub wtyczki kontekst rozszerzenia o dużych uprawnieniach.

Ten mechanizm upraszcza tworzenie szablonów, ale oczywiście nie ogranicza się tylko do nich. Dowolne kod, który nie działa od razu przy rygorystycznych zasadach Content Security Policy, może być umieszczony w piaskownicy; cale warto umieścić w piaskownicy komponenty rozszerzeń, które mogłyby działać prawidłowo, w kolejności Ograniczyć każdy element programu do jak najmniejszego zestawu uprawnień niezbędnych i wykonaniu działania. Prezentacja Writing Secure Web Apps and Chrome Extensions z Google I/O 2012 zawiera dobre przykłady zastosowania tych technik i warto poświęcić na nią 56 minut.