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 taşınarak ayrı JavaScript dosyalarına yerleştirilmeli, satır içi etkinlik işleyiciler addEventListener kullanacak şekilde dönüştürülmeli ve eval() devre dışı bırakılmalıdır.
Ancak, ç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ı, bu uygulama stiline özellikle yatkındır. Bazıları (ör. Angular.js) İGP'yi kutudan çıktığı haliyle desteklese de birçok popüler çerçeve henüz uzantıların eval-sız dünyasıyla uyumlu bir mekanizmaya güncellenmedi. Bu nedenle, söz konusu işlev için desteğin kaldırılması geliştiriciler açısından beklenenden daha sorunlu oldu.
Bu belge, güvenliği tehlikeye atmadan bu kitaplıkları projelerinize dahil etmek için güvenli bir mekanizma olarak korumalı alanı tanıtır.
Neden korumalı alan?
eval, uzantının yüksek izinli ortamındaki her şeye erişebilen kodu yürüttüğü için uzantı içinde tehlikelidir. Kullanıcının güvenliğini ve gizliliğini ciddi şekilde etkileyebilecek bir dizi güçlü chrome.* API mevcuttur. Basit veri hırsızlığı, endişeleneceğimiz en küçük konudur.
Sunulan çözüm, eval'nın uzantının verilerine veya uzantının yüksek değerli API'lerine erişmeden kodu çalıştırabileceği bir sanal alandır. Veri, API veya sorun yok.
Bunu, uzantı paketindeki belirli HTML dosyalarını korumalı alan olarak listeleyerek yaparız.
Koruma alanına alınmış bir sayfa her yüklendiğinde benzersiz bir kaynağa taşınır ve chrome.* API'lerine erişimi engellenir. Bu korumalı alanı iframe aracılığıyla uzantımıza yüklersek ona mesajlar iletebilir, bu mesajlar üzerinde bir şekilde işlem yapmasını sağlayabilir ve bize bir sonuç iletmesini bekleyebiliriz. Bu basit mesajlaşma mekanizması, eval destekli kodu uzantımızın iş akışına güvenli bir şekilde dahil etmek için ihtiyacımız olan her şeyi bize sunar.
Korumalı alan oluşturma ve kullanma
Doğrudan kod yazmaya başlamak istiyorsanız sandbox oluşturma örnek uzantısını alıp kullanmaya başlayın. Bu uzantı, Handlebars şablon kitaplığı üzerine oluşturulmuş küçük bir mesajlaşma API'sinin çalışan bir örneğidir ve başlamak için ihtiyacınız olan her şeyi size sunar. Daha fazla açıklama isteyenler için bu örneği birlikte inceleyelim.
Manifestteki dosyaları listeleme
Bir korumalı alan içinde çalıştırılması gereken her dosya, uzantı manifestosunda sandbox özelliği eklenerek listelenmelidir. Bu önemli adımı unutmamak için korumalı alan dosyanızın manifestte listelendiğinden emin olun. Bu örnekte, "sandbox.html" adlı dosyayı akıllıca sanal ortamda test ediyoruz. Manifest girişi aşağıdaki gibi görünür:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
Korumalı alana alınan dosyayı yükleme
Koruma alanına alınmış dosyayla ilginç bir şeyler yapmak için dosyayı, uzantının kodu tarafından ele alınabileceği bir bağlamda yüklememiz gerekir. Burada, sandbox.html, iframe kullanılarak bir uzantı sayfasına yüklenmiştir. Sayfanın JavaScript dosyası, tarayıcı işlemi tıklandığında sayfadaki iframe bulunarak ve contentWindow üzerinde postMessage() çağrılarak sanal ortama mesaj gönderen kodu içerir. Mesaj, context, templateName ve command olmak üzere üç özellik içeren bir nesnedir. Birazdan context ve command konularına değineceğ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 yapmak
sandbox.html yüklendiğinde Handlebars kitaplığını yükler ve Handlebars'ın önerdiği şekilde satır içi bir ş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>
Bu işlem başarısız olmaz. Handlebars.compile, new Function kullanmasına rağmen her şey tam olarak beklendiği gibi çalışır ve templates['hello'] içinde derlenmiş bir şablon elde ederiz.
Sonucu geri iletme
Uzantı sayfasından gelen komutları kabul eden bir mesaj dinleyici ayarlayarak bu şablonu kullanıma sunacağız. Ne yapılması gerektiğini belirlemek için command parametresini kullanırız (yalnızca oluşturmaktan daha fazlasını yapabileceğinizi düşünün; örneğin, şablon oluşturma). Belki de bunları bir şekilde yönetmek istiyorsunuzdur. Bu durumda context, oluşturma için doğrudan şablona aktarılır. Oluşturulan HTML, uzantının daha sonra yararlı bir işlem yapabilmesi için uzantı sayfasına geri iletilir:
<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üğümüzde bu mesajı alırız ve html
bize iletilen verilerle ilginç bir işlem yaparız. Bu durumda, yalnızca bildirim aracılığıyla yansıtacağı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 üzerinden eklemek, sanal alan içinde 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 elbette şablon oluşturmayla sınırlı değildir. Sıkı bir İçerik Güvenliği Politikası kapsamında kutudan çıktığı gibi çalışmayan tüm kodlar korumalı alana alınabilir. Aslında, programınızın her bir parçasını düzgün şekilde yürütmek için gereken en küçük ayrıcalıklar kümesiyle sınırlamak amacıyla, uzantılarınızın doğru çalışacak bileşenlerini korumalı alana almak genellikle yararlıdır. Google I/O 2012'deki Writing Secure Web Apps and Chrome Extensions (Güvenli Web Uygulamaları ve Chrome Uzantıları Yazma) sunumunda bu tekniklerin uygulamadaki bazı iyi örnekleri verilmektedir ve 56 dakikanızı ayırmaya değer.