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ı, satır içi etkinlik işleyiciler addEventListener
kullanacak şekilde dönüştürülmelidir ve eval()
devre dışı bırakıldı.
Bununla birlikte, çeşitli kütüphanelerin eval()
ve eval
benzeri yapıları kullandığının farkındayız:
Performans optimizasyonu ve ifade kolaylığı için new Function()
. Şablon kitaplıkları özellikle bu tür bir uygulamaya eğilimlidir. Bazıları (ör. Angular.js) CSP'yi kutudan çıkar çıkmaz desteklese de popüler çerçevelerin çoğu henüz uzantıların eval
'siz dünyasıyla uyumlu bir mekanizmaya güncellenmemiştir. Bu nedenle, söz konusu işlev için desteğin
kaldırılması daha fazla
beklenenden fazla soruna neden olabilir.
Bu dokümanda, bu kitaplıkları projelerinize dahil etmek için güvenli bir mekanizma olarak korumalı alan kullanımı tanıtılmaktadır. hiçbir zaman ödün vermeden kullanabilirsiniz.
Neden korumalı alan kullanmalı?
eval
, yürüttüğü bir kod uzantıdaki her şeye erişebildiğinden bu uzantı içinde tehlikeli bir durum
eklentisinin yüksek izinli ortamını yönetebilirsiniz. Şunları yapabilecek bir grup güçlü chrome.*
API'si mevcuttur:
Kullanıcının güvenliğini ve gizliliğini ciddi şekilde etkileme; endişelenmemizin en az olduğu şey basit veri hırsızlığı.
Sunulan çözüm, eval
'nin uzantının verilerine veya yüksek değerli API'lerine erişmeden kod yürütebileceği bir korumalı alan sağlar. Veri yok, API yok, sorun değil.
Bu işlemi, uzantı paketinin içindeki belirli HTML dosyalarının korumalı alana alınmış olarak listeleyerek gerçekleştiririz.
Korumalı alana alınmış bir sayfa yüklendiğinde benzersiz bir kaynağa taşınır ve reddedilir
chrome.*
API'lerine erişim. Bu korumalı alan sayfasını bir iframe
aracılığıyla uzantımıza yüklersek ona mesaj iletebilir, bu mesajlar üzerinde bir şekilde işlem yapmasına izin verebilir ve bize bir sonuç döndürmesini bekleyebiliriz. Bu basit mesajlaşma mekanizması, uzantımızın iş akışına eval
tarafından yönlendirilen kodu güvenli bir şekilde dahil etmek için ihtiyacımız olan her şeyi bize sağlar.
Korumalı alan oluşturma ve kullanma
Doğrudan koda girmek istiyorsanız korumalı alan oluşturma örnek uzantısını alın ve kapalı'ya dokunun. Herkese açık kullanıcı adlarının üst kısmında oluşturulmuş küçük bir mesajlaşma API'sinin çalışan örneğidir. sadece şablon oluşturma kitaplığıdır ve başlamanız için gereken her şeyi sağlar. Konuyu biraz daha ayrıntılı bir şekilde öğrenmek isteyenler için bu örneği birlikte inceleyelim.
Manifest'teki dosyaları listele
Korumalı alan içinde çalışması gereken her dosya,
sandbox
mülk. Bu kritik bir adımdır ve kolayca unutulabilir. Bu nedenle, korumalı alanınızdaki dosyanızın manifest dosyasında listelenip listelenmediğini tekrar kontrol edin. Bu örnekte, "sandbox.html" olarak adlandırılan dosyayı korumalı alana alıyoruz. Manifest girişi şu şekilde görünür:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
Korumalı alandaki dosyayı yükleme
Korumalı alan dosyası ile ilginç bir şey yapmak için dosyayı, uzantının kodu tarafından erişilebileceği bir bağlamda yüklememiz gerekir. Burada, sandbox.html bir iframe
aracılığıyla uzantı sayfasına yüklendi. Sayfanın JavaScript dosyasında, tarayıcı işlemi tıklandığında sayfadaki iframe
öğesini bulup contentWindow
öğesinde postMessage()
çağrısı yaparak korumalı alana mesaj gönderen bir kod bulunur. Mesaj, context
, templateName
ve command
olmak üzere üç özellik içeren bir nesnedir. Birazdan context
ve command
konularını 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 yapma
sandbox.html
yüklendiğinde Gider çubuğu kitaplığını yükler ve bir satır içi oluşturup derler
Gidiş Çubuklarının önerdiği şekilde bir şablon oluşturun:
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
'u kullansa da işler tam olarak beklendiği gibi işler ve templates['hello']
biçiminde derlenmiş bir şablon elde ederiz.
Sonucu geri verme
Komutları kabul eden bir mesaj dinleyici oluşturarak bu şablonu kullanıma sunacağız
farklı olabilir. Yapılması gerekenleri belirlemek için iletilen command
belgesini kullanırız (
yalnızca görüntüleme değil, şablonlar hazırlıyor mu? Belki bunları belirli bir
şekilde?) ve context
, oluşturma için doğrudan şablona aktarılır. Oluşturulan HTML
, uzantının daha sonra yararlı bir şeyler yapabilmesi için uzantı sayfasına geri gönderilir:
<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 geri döndüğünüzde, bu mesajı alırız ve html
ile ilginç bir şey yaparız.
iletilmiş olabilir. Bu durumda, bu durumu yalnızca bir bildirim aracılığıyla yansıtırı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. Ekleme yöntemi:
innerHTML
, oluşturulan içeriğe güvendiğimiz için önemli bir güvenlik riski oluşturmaz.
sağlayabilirsiniz.
Bu mekanizma, şablon oluşturmayı kolaylaştırır ancak şablon oluşturmayla sınırlı değildir. Herhangi biri katı bir İçerik Güvenliği Politikası altında anında çalışmayan bir kod korumalı alana alınabilir; bir listesini oluştur: Aslında uzantılarınızın sırayla çalışacak bileşenlerini korumalı alana almak genellikle yararlıdır. ve programınızın her bir parçasını mümkün olan en küçük ayrıcalıklarla ve düzgün şekilde yürütülmesini sağlar. Google I/O 2012'deki Güvenli Web Uygulamaları ve Chrome Uzantıları Yazma sunumu, bu tekniklerin kullanıma yönelik bazı iyi örneklerini sunar ve 56 dakikanızı ayırmaya değerdir.