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.