System rozszerzeń Chrome wymusza dość rygorystyczną domyślną politykę bezpieczeństwa treści (CSP).
Ograniczenia wynikające z zasad są proste: skrypt musi zostać przeniesiony poza wiersz do osobnych plików JavaScript, wbudowane moduły obsługi zdarzeń trzeba przekonwertować na korzystanie z elementu addEventListener
, a element eval()
musi być wyłączony. W aplikacjach Chrome są stosowane jeszcze bardziej rygorystyczne zasady i jesteśmy zadowoleni z właściwości zabezpieczeń, jakie zapewniają.
Zdajemy sobie jednak sprawę, że różne biblioteki korzystają z konstrukcji typu eval()
i eval
, takich jak new Function()
, które ułatwiają optymalizację wydajności i korzystanie z nich. Szczególnie narażone są biblioteki szablonów
na taki sposób implementacji. Niektóre z nich (np. Angular.js) od razu po uruchomieniu obsługują CSP, ale wiele popularnych platform nie zostało jeszcze zaktualizowanych do mechanizmu kompatybilnego ze światem dostępnym tylko w eval
rozszerzeniach. Usunięcie obsługi tej funkcji okazało się więc większym problemem dla deweloperów niż się spodziewaliśmy.
W tym dokumencie opisujemy piaskownicę jako bezpieczny mechanizm pozwalający uwzględnić te biblioteki w projektach bez naruszania bezpieczeństwa. W związku z tym będziemy używać terminu rozszerzenia, ale odnosi się on w równym stopniu do aplikacji.
Dlaczego piaskownica?
Element eval
jest niebezpieczny w rozszerzeniu, ponieważ uruchamiany przez niego kod ma dostęp do wszystkiego w środowisku o wysokich uprawnieniach rozszerzenia. Dostępnych jest wiele zaawansowanych interfejsów API chrome.*
, które mogą negatywnie wpłynąć na bezpieczeństwo i prywatność użytkowników. Nie musisz się martwić o proste wydobycie danych.
Oferowane rozwiązanie to piaskownica, w której eval
może uruchamiać kod bez dostępu do danych rozszerzenia ani do jego wysokiej wartości API. Nie ma danych, interfejsów API ani problemów.
W tym celu pokazujemy określone pliki HTML w pakiecie rozszerzeń jako pliki w piaskownicy.
Po każdym załadowaniu strony w piaskownicy jest ona przenoszona do unikalnego źródła i nie otrzymuje dostępu do interfejsów API chrome.*
. Jeśli wczytamy stronę znajdującą się w piaskownicy do naszego rozszerzenia za pomocą obiektu iframe
, możemy przekazać do niej komunikaty, pozwolić mu na jakąś czynność związaną z tymi wiadomościami i zaczekać, aż zwróci wynik. Ten prosty mechanizm komunikacji daje nam wszystko, czego potrzebujemy, aby bezpiecznie umieścić w procesie rozszerzenia kod oparty na eval
.
Tworzenie piaskownicy i korzystanie z niej.
Jeśli chcesz od razu przejść do kodu, pobierz przykładowe rozszerzenie do piaskownicy i zacznij działać. To działający przykład małego interfejsu API do przesyłania wiadomości, który jest oparty na bibliotece szablonów Handlebars i powinno zawierać wszystko, czego potrzebujesz. Jeśli szukasz dokładniejszych wyjaśnień, omówię ten fragment.
Wyświetlenie listy plików w pliku manifestu
Każdy plik, który powinien być uruchomiony wewnątrz piaskownicy, musi być wymieniony w pliku manifestu rozszerzeń przez dodanie właściwości sandbox
. Jest to kluczowy krok, który łatwo jest zapomnieć. Dlatego sprawdź dokładnie, czy plik piaskownicy znajduje się w pliku manifestu. W tym przykładzie dzielimy na piaskownice plik o nazwie „sandbox.html”. Wpis w pliku manifestu wygląda tak:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
Wczytaj plik piaskownicy
Aby można było zrobić coś ciekawego z plikiem w trybie piaskownicy, musimy go załadować w kontekście, w którym można mu to umożliwić za pomocą kodu rozszerzenia. W tym przypadku plik sandbox.html został wczytany na stronie zdarzeń rozszerzenia (eventpage.html) przez tag iframe
. Plik eventpage.js zawiera kod, który po każdym kliknięciu działania przeglądarki wysyła komunikat do piaskownicy. Aby to zrobić, znajduje obiekt iframe
na stronie i uruchamia metodę postMessage
w elemencie contentWindow
. Wiadomość jest obiektem zawierającym 2 właściwości: context
i command
. Za chwilę omówimy obydwa z nich.
chrome.browserAction.onClicked.addListener(function() {
var iframe = document.getElementById('theFrame');
var message = {
command: 'render',
context: {thing: 'world'}
};
iframe.contentWindow.postMessage(message, '*');
});
postMessage
API znajdziesz w dokumentacji postMessage
w MDN . To kompletna treść, którą warto przeczytać. Pamiętaj, że dane można przekazywać w tę i z powrotem tylko wtedy, gdy nadają się do serializacji. Funkcje nie są na przykład dostępne.Zrób coś niebezpiecznego
Po wczytaniu elementu sandbox.html
wczytuje się bibliotekę Handlebars, a następnie tworzy i kompiluje wbudowany szablon w sposób sugerujący, że:
<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 koniec! Chociaż Handlebars.compile
kończy się używaniem new Function
, wszystko działa dokładnie zgodnie z oczekiwaniami – w efekcie otrzymujemy skompilowany szablon w templates['hello']
.
Przekaż wynik z powrotem
Udostępnimy ten szablon za pomocą odbiornika, który akceptuje polecenia ze strony zdarzenia. Użyjemy przekazanego pliku command
, aby określić, co należy zrobić (wyobrażasz sobie coś więcej niż tylko renderowanie; np. tworzenie szablonów?). Być może jako zarządzanie nimi?), a element context
będzie przekazywany bezpośrednio do szablonu na potrzeby renderowania. Wyrenderowany kod HTML zostanie zwrócony na stronę zdarzenia, dzięki czemu rozszerzenie będzie mogło później robić z nim coś przydatnego:
<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>
Po powrocie na stronę wydarzenia otrzymamy tę wiadomość i zrobimy coś ciekawego z przekazanymi danymi html
. W tym przypadku odczytamy go w powiadomieniu na pulpicie, ale istnieje możliwość, że bezpiecznie użyjemy tego kodu HTML w interfejsie rozszerzenia. Wstawienie go za pomocą innerHTML
nie stwarza poważnego zagrożenia dla bezpieczeństwa, ponieważ nawet całkowite przejęcie jego kodu w piaskownicy poprzez sprytny atak nie pozwoliłoby wstrzyknąć niebezpiecznej zawartości skryptu lub wtyczki do kontekstu rozszerzenia o wysokich uprawnieniach.
Ten mechanizm ułatwia tworzenie szablonów, ale oczywiście nie ogranicza się do nich. Każdy kod, który nie działa od razu w ramach ścisłej polityki bezpieczeństwa treści, może znajdować się w piaskownicy. W rzeczywistości takie rozwiązanie często sprawdza się w piaskownicy komponentów rozszerzeń, które były prawidłowo uruchamiane. Dzięki temu każdy element programu ma jak najmniejszy zestaw uprawnień niezbędnych do jego prawidłowego wykonania. Prezentacja na temat pisania bezpiecznych aplikacji internetowych i rozszerzeń do Chrome z Google I/O 2012 zawiera kilka przykładów tych technik w praktyce i warto poświęcić na nie 56 minut.