Korumalı alana alınmış IFrame'lerde güvenli şekilde oyun oynayın

Günümüz web'inde zengin bir deneyim oluşturmak, neredeyse kaçınılmaz olarak üzerinde gerçek denetiminizin olmadığı bileşenleri ve içerikleri yerleştirmekten ibarettir. Üçüncü taraf widget'lar, etkileşimi artırabilir ve genel kullanıcı deneyiminde kritik bir rol oynayabilir. Kullanıcı tarafından oluşturulan içerik, bazen sitenin yerel içeriğinden daha da önemlidir. İkisinden de uzak durmak bir seçenek değildir, ancak her ikisi de Bir Şey BadTM hizmetinin sitenizde gerçekleşme riskini artırır. Yerleştirdiğiniz her widget (tüm reklamlar, sosyal medya widget'ları), kötü niyetli kişiler için potansiyel bir saldırı vektörüdür:

İçerik Güvenliği Politikası (İGP), özellikle güvenilen komut dosyası kaynaklarını ve diğer içerikleri beyaz listeye ekleme olanağı sunarak bu iki içerik türüyle ilişkili riskleri azaltabilir. Bu, doğru yönde atılmış önemli bir adımdır ancak çoğu CSP yönergesinin sunduğu korumanın ikili program olduğunu, yani kaynağa izin verildiğini ya da olmadığını belirtmekte fayda vardır. Bazen "Bu içerik kaynağına gerçekten güvendiğimden emin değilim ama çok güzel! Götür lütfen, Tarayıcı, ama sitemi bozmasına izin verme."

En Az Ayrıcalık

Aslında, içerik sağlamamıza olanak tanıyacak bir mekanizma arıyoruz. Yalnızca işini yapmak için gereken minimum düzeyde kapasiteye yer veririz. Bir widget'ın yeni pencere açması gerekmiyorsa window.open erişimini kaldırmak zarara yol açmaz. Flash gerektirmiyorsa, eklenti desteğinin kapatılması sorun yaratmayacaktır. En az ayrıcalık ilkesini uyguluyor ve kullanmak istediğimiz işlevle doğrudan alakalı olmayan her bir özelliği engellediğimizde kendimiz mümkün olduğunca güvendeyiz. Sonuç olarak, bazı yerleşik içeriklerin kullanmaması gereken ayrıcalıklardan yararlanmayacağına artık körü körüne güvenmek zorunda değiliz. Başlangıçta işlevlere erişemez.

iframe öğeleri, bu tür bir çözüm için iyi bir çerçeve oluşturmak üzere atılacak ilk adımdır. iframe içinde güvenilmeyen bazı bileşenlerin yüklenmesi, uygulamanız ile yüklemek istediğiniz içerik arasındaki ayrımı ölçer. Çerçeveli içerik, sayfanızın DOM'sine veya yerel olarak depoladığınız verilere erişemez. Ayrıca, sayfada rastgele belirlenen konumlara da çizemez; kapsam, çerçevenin ana hatlarıyla sınırlıdır. Ancak bu ayrım aslında yeterince güçlü değildir. Kapsanan sayfada hâlâ rahatsız edici veya kötü amaçlı davranış için birçok seçenek vardır: Otomatik oynatılan videolar, eklentiler ve pop-up'lar buz dağının görünen ucudur.

iframe öğesinin sandbox özelliği, çerçeveli içerikteki kısıtlamaları daha sıkı hale getirmek için ihtiyacımız olan şeyi bize sağlar. Tarayıcının, belirli bir çerçevenin içeriğini düşük ayrıcalıklı bir ortamda yüklemesini sağlayabilir, böylece o işlemi gerçekleştirmek için gereken yeteneklerin yalnızca alt kümesine izin verebiliriz.

Döndürün ancak doğrulayın

Twitter'ın "Tweet" düğmesi, bir korumalı alan aracılığıyla sitenize daha güvenli bir şekilde yerleştirilebilecek işlevlere mükemmel bir örnektir. Twitter, aşağıdaki kodu kullanarak düğmeyi bir iframe yoluyla yerleştirmenize olanak tanır:

<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
        style="border: 0; width:130px; height:20px;"></iframe>

Neleri kilitleyebileceğimizi anlamak için düğmenin gerektirdiği özellikleri dikkatlice inceleyelim. Çerçeveye yüklenen HTML, Twitter sunucularından bir miktar JavaScript çalıştırır ve tıklandığında bir tweet arayüzüyle doldurulan pop-up oluşturur. Bu arayüzün, tweet'i doğru hesaba bağlamak için Twitter'ın çerezlerine erişmesi ve tweet formunu gönderebilmesi gerekir. Aşağı yukarı bu kadar; çerçevenin herhangi bir eklenti yüklemesi, üst düzey pencerede veya başka birkaç işlev parçasında gezinmesi gerekmez. Bu ayrıcalıklara ihtiyaç duymadığından, karenin içeriğini korumalı alana alarak bunları kaldıralım.

Korumalı alan oluşturma işlemi bir beyaz liste temelinde çalışır. Mümkün olan tüm izinleri kaldırarak başlıyor ve ardından korumalı alanın yapılandırmasına belirli işaretler ekleyerek her bir özelliği tekrar etkinleştiriyoruz. Twitter widget'ı için JavaScript'i, pop-up'ları, form gönderme özelliğini ve twitter.com çerezlerini etkinleştirmeye karar verdik. Bunu, iframe öğesine aşağıdaki değere sahip bir sandbox özelliği ekleyerek yapabiliriz:

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
    src="https://platform.twitter.com/widgets/tweet_button.html"
    style="border: 0; width:130px; height:20px;"></iframe>

Bu kadar basit. Çerçeveye gerekli tüm özellikleri sağladık ve tarayıcı, sandbox özelliğinin değeri üzerinden açıkça vermediğimiz ayrıcalıklara erişimi reddetmesi için faydalı olacak.

Özellikler Üzerinde Ayrıntılı Kontrol

Yukarıdaki örnekte olası korumalı alan işaretlerinden birkaçını gördük. Şimdi de özelliğin iç işleyişini biraz daha ayrıntılı olarak inceleyelim.

Korumalı alan özelliği boş olan bir iframe söz konusu olduğunda, çerçevelenen doküman aşağıdaki kısıtlamalara tabi olacak şekilde tamamen korumalı alana alınır:

  • JavaScript, çerçeveli dokümanda yürütülmez. Bu, yalnızca komut dosyası etiketleri aracılığıyla açıkça yüklenen JavaScript'i değil, aynı zamanda satır içi etkinlik işleyicileri ve javascript: URL'leri de içerir. Bu aynı zamanda, noscript etiketlerinde bulunan içeriğin, kullanıcının komut dosyasını kendisini devre dışı bırakmış gibi görüntüleneceği anlamına gelir.
  • Çerçeveli belge benzersiz bir kaynağa yüklenir. Bu, tüm aynı kaynak kontrollerinin başarısız olacağı anlamına gelir. Benzersiz kaynaklar hiçbir zaman başka kaynaklarla eşleşmez, hatta kendileriyle bile eşleşmez. Diğer etkilerinin yanı sıra bu durum, dokümanın herhangi bir kaynağın çerezlerinde veya diğer depolama mekanizmalarında (DOM depolama, dizine eklenmiş DB vb.) depolanan verilere erişemeyeceği anlamına gelir.
  • Çerçeveli doküman yeni pencereler veya iletişim kutuları oluşturamaz (örneğin, window.open veya target="_blank" aracılığıyla).
  • Formlar gönderilemiyor.
  • Eklentiler yüklenmeyecek.
  • Çerçeveli doküman yalnızca kendi içinde gezinebilir, en üst düzey dokümanında gezinemez. window.top.location ayarı bir istisnaya yol açar ve target="_top" içeren bağlantının tıklanmasının herhangi bir etkisi olmaz.
  • Otomatik olarak tetiklenen özellikler (otomatik odaklı form öğeleri, otomatik oynatılan videolar vb.) engellenir.
  • İşaretçi kilidi alınamıyor.
  • Çerçeveli dokümanda seamless özelliği, iframes üzerinde yoksayılır.

Bu oldukça acımasız bir ifadedir ve tamamen korumalı alana alınmış bir iframe içine yüklenen bir belgenin riski de pek azdır. Elbette, pek de fazla değeri yoktur: Bazı statik içerikler için tam bir korumalı alana sahip olabilirsiniz, ancak çoğu zaman işleri biraz gevşetmek istersiniz.

Eklentiler hariç bu kısıtlamaların her biri, korumalı alan özelliğinin değerine bir işaret eklenerek kaldırılabilir. Eklentiler korumalı alanda olmayan yerel kod olduğundan, korumalı alana alınan dokümanlar hiçbir zaman eklenti çalıştıramaz. Geri kalan her şey uygundur:

  • allow-forms form gönderimine olanak tanır.
  • allow-popups pop-up'lara izin verir (şok!).
  • allow-pointer-lock işaretçi kilidine (sürpriz!) izin verir.
  • allow-same-origin, dokümanın kaynağını korumasına olanak tanır. https://example.com/ sayfasından yüklenen sayfalar söz konusu kaynağın verilerine erişmeye devam eder.
  • allow-scripts, hem JavaScript'in çalıştırılmasına hem de özelliklerin otomatik olarak tetiklenmesine olanak tanır (JavaScript aracılığıyla uygulanması çok kolay bir işlemdir).
  • allow-top-navigation, üst düzey pencerede gezinerek dokümanın çerçeveden çıkmasına olanak tanır.

Bunları göz önünde bulundurarak, yukarıdaki Twitter örneğinde neden belirli bir korumalı alan işareti kümesini bulduğumuzu tam olarak değerlendirebiliriz:

  • Çerçeveye yüklenen sayfa, kullanıcı etkileşimini yönetmek için bazı JavaScript'ler çalıştırdığından allow-scripts gereklidir.
  • Düğme, yeni bir pencerede bir tweet formu açtığından allow-popups alanının doldurulması zorunludur.
  • allow-forms gerekiyor çünkü tweet formu gönderilebilir olmalıdır.
  • Aksi takdirde twitter.com çerezlerine erişemeyeceğinden ve kullanıcı, formu yayınlamak için giriş yapamadığından allow-same-origin gereklidir.

Unutulmaması gereken önemli bir nokta da bir çerçeveye uygulanan korumalı alan işaretlemelerinin, korumalı alanda oluşturulan tüm pencereler veya çerçeveler için de geçerli olduğudur. Bu, form yalnızca çerçevenin açıldığı pencerede bulunsa da çerçevenin korumalı alanına allow-forms eklememiz gerektiği anlamına gelir.

sandbox özelliği kullanıldığında widget yalnızca gerektirdiği izinleri alır ve eklentiler, üst gezinme ve işaretçi kilidi gibi özellikler engellenmeye devam eder. Widget'ı gömme riskini, kötü etkileri olmadan azalttık. Bu, ilgili herkes için bir kazançtır.

Ayrıcalık Ayırma

Güvenilmeyen kodlarını düşük ayrıcalıklı bir ortamda çalıştırmak amacıyla üçüncü taraf içeriklerini korumalı alana almak, bariz bir şekilde avantajlıdır. Peki, kendi kodunuz? Kendinize güveniyorsunuz, değil mi? Peki, korumalı alana alma neden gerekli?

Şu soruyu tersine çeviriyorum: Kodunuz eklenti gerektirmiyorsa, kodun eklentilere erişmesine neden izin veriyorsunuz? En iyi ihtimalle bu asla kullanmayacağınız bir ayrıcalıktır. En kötüsü, saldırganların kapıdan içeri adım atması olası bir faktördür. Herkesin kodunda hatalar vardır ve neredeyse her uygulama bir şekilde, kötüye kullanıma açık durumdadır. Kendi kodunuzu korumalı alana almak, bir saldırganın uygulamanızı başarılı bir şekilde ele geçirse bile kendisine uygulama kaynağına tam erişim veremeyeceği, yalnızca uygulamanın yapabileceği şeyleri yapabileceği anlamına gelir. Hâlâ kötü olabilir, ancak olabileceği kadar kötü değildir.

Uygulamanızı mantıksal parçalara bölerek ve her bir parçayı mümkün olan minimum ayrıcalıkla korumalı alana alarak riski daha da azaltabilirsiniz. Bu teknik yerel kodda çok yaygındır: Örneğin Chrome, kendini yerel sabit diske erişimi olan ve ağ bağlantıları kurabilen yüksek ayrıcalıklı bir tarayıcı işleminin yanı sıra güvenilir olmayan içeriği ayrıştırma işini yapan pek çok düşük ayrıcalıklı oluşturucu işlemine girer. Oluşturucuların diske dokunmalarına gerek yoktur. Tarayıcı, bir sayfayı oluşturmak için ihtiyaç duydukları tüm bilgilerin sağlanmasıyla ilgilenir. Akıllı bir bilgisayar korsanı bir oluşturucuyu bozmanın bir yolunu bulsa bile, oluşturucu pek fazla işlem yapamadığı için fazla ilerlememiştir: Yüksek ayrıcalıklı tüm erişimler, tarayıcı işlemi üzerinden yönlendirilmelidir. Saldırganların herhangi bir hasar verebilmek için sistemin farklı parçalarında birkaç delik bulması gerekecektir. Bu da tahrip etme riskini önemli ölçüde azaltır.

eval() güvenli bir şekilde korumalı alana alınıyor

Korumalı alan oluşturma ve postMessage API ile bu modelin başarısını web'e uygulamak oldukça kolaydır. Uygulamanızın parçaları korumalı alan içine alınmış iframe'lerde yaşayabilir ve üst doküman, iletiler yayınlayıp yanıtları dinleyerek bunlar arasındaki iletişimi sağlayabilir. Bu tür bir yapı, uygulamanın herhangi bir parçasındaki istismarların mümkün olan en az zararı vermesini sağlar. Ayrıca sizi net entegrasyon noktaları oluşturmaya zorlayarak giriş ve çıkışı doğrulamak için tam olarak nerede dikkatli olmanız gerektiğini bilirsiniz. Bir oyuncak örneğine bakalım, işe yarayacağını görmek için.

Evalbox, bir dizeyi JavaScript olarak değerlendiren heyecan verici bir uygulamadır. Vay canına, değil mi? Bunca yıldır beklediğiniz gibi. Bu oldukça tehlikeli bir uygulamadır. Rastgele JavaScript'in yürütülmesine izin vermek, bir kaynağın sunması gereken tüm verileri yakalamaya hazır olduğu anlamına gelir. Kodun bir korumalı alan içinde yürütülmesini sağlayarak Bad ThingsTM'in ortaya çıkma riskini azaltacağız. Böylece, işlem biraz daha güvenli olacak. Çerçevenin içeriğinden başlayarak kodu içeriden başlayarak ele alacağız:

<!-- frame.html -->
<!DOCTYPE html>
<html>
    <head>
    <title>Evalbox's Frame</title>
    <script>
        window.addEventListener('message', function (e) {
        var mainWindow = e.source;
        var result = '';
        try {
            result = eval(e.data);
        } catch (e) {
            result = 'eval() threw an exception.';
        }
        mainWindow.postMessage(result, event.origin);
        });
    </script>
    </head>
</html>

Çerçevenin içinde, window nesnesinin message etkinliğine bağlanarak üst öğeden gelen mesajları dinleyen minimal bir belgemiz vardır. Üst öğe, iframe'in içeriğinde postMessage çalıştırdığında, bu etkinlik tetiklenerek üst öğemizin yürütmemizi istediği dizeye erişim elde etmemizi sağlar.

İşleyicide etkinliğin üst pencere olan source özelliğini yakalıyoruz. Bu e-posta, yoğun çalışmamızın sonucunu bir kez tamamlandığında geri göndermek için kullanacağız. Ardından, bize verilen verileri eval() platformuna ileterek işin zor kısmını yapacağız. Korumalı alana alınmış bir iframe içinde yasaklanan işlemler genellikle DOM istisnaları oluşturacağından bu çağrı bir deneme blokunda sonlandırılmıştır. Bunları yakalayıp kolay bir hata mesajı bildireceğiz. Son olarak, sonucu tekrar üst pencerede yayınlarız. Bu oldukça basit bir işlemdir.

Ebeveyn de benzer şekilde karmaşık değildir. Kod için textarea, yürütme için button ile küçük bir kullanıcı arayüzü oluşturacağız. frame.html alan adını, korumalı alana alınmış bir iframe aracılığıyla alacak ve yalnızca komut dosyasının yürütülmesine izin vereceğiz:

<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
        id='sandboxed'
        src='frame.html'></iframe>

Şimdi işleri yürütmeye hazırlayacağız. İlk olarak, iframe uygulamasından gelen yanıtları dinleyip kullanıcılarımıza alert() vereceğiz. Gerçek bir uygulamanın daha az rahatsız edici bir şeyler yapacağını düşünebilirsiniz:

window.addEventListener('message',
    function (e) {
        // Sandboxed iframes which lack the 'allow-same-origin'
        // header have "null" rather than a valid origin. This means you still
        // have to be careful about accepting data via the messaging API you
        // create. Check that source, and validate those inputs!
        var frame = document.getElementById('sandboxed');
        if (e.origin === "null" &amp;&amp; e.source === frame.contentWindow)
        alert('Result: ' + e.data);
    });

Ardından, button tıklaması için bir etkinlik işleyici bağlayacağız. Kullanıcı tıkladığında, mevcut textarea içeriği alınır ve yürütme için çerçeveye aktarılır:

function evaluate() {
    var frame = document.getElementById('sandboxed');
    var code = document.getElementById('code').value;
    // Note that we're sending the message to "*", rather than some specific
    // origin. Sandboxed iframes which lack the 'allow-same-origin' header
    // don't have an origin which you can target: you'll have to send to any
    // origin, which might alow some esoteric attacks. Validate your output!
    frame.contentWindow.postMessage(code, '*');
}

document.getElementById('safe').addEventListener('click', evaluate);

Kolay, değil mi? Çok basit bir değerlendirme API'si geliştirdik. Böylece değerlendirilen kodun çerezler veya DOM depolama gibi hassas bilgilere erişimi olmadığından emin olabiliriz. Benzer şekilde, değerlendirilen kod eklentileri yükleyemez, yeni pencereler açamaz veya başka rahatsız edici ya da kötü amaçlı etkinliklerden herhangi birini yükleyemez.

Monolit uygulamaları tek amaçlı bileşenlere bölerek kendi kodunuz için de aynısını yapabilirsiniz. Bunların her biri, yukarıda yazdıklarımız gibi basit bir mesajlaşma API'si içine dahil edilebilir. Yüksek ayrıcalıklı üst pencere, her biri işlerini yapmak için mümkün olan en az ayrıcalığa sahip belirli modüllere mesaj göndererek, sonuçları dinleyerek ve her modülün yalnızca ihtiyaç duyduğu bilgilerin iyi beslenmesini sağlayarak bir denetleyici ve görev dağıtıcı görevi görebilir.

Bununla birlikte, ana içerikle aynı kaynaktan gelen çerçeveli içerikle ilgili işlem yaparken çok dikkatli olmanız gerektiğini unutmayın. https://example.com/ üzerindeki bir sayfa, aynı kaynaktaki bir sayfayı hem allow-same-origin hem de allow-scripts işaretlerini içeren bir korumalı alanla çerçevelerse çerçevelenen sayfa üst öğeye erişebilir ve korumalı alan özelliğini tamamen kaldırabilir.

Korumalı alanda oynayın

Korumalı alan oluşturma özelliğini şu anda çeşitli tarayıcılarda kullanabilirsiniz: Firefox 17 ve sonraki sürümleri, IE10 ve sonraki sürümleri ile Chrome'un yazıldığı sırada Chrome (tabii ki güncel bir destek tablosu vardır). Eklediğiniz iframes için sandbox özelliğini uygulamak, bu ayrıcalıkların görüntülediği içerikler için yalnızca içeriğin doğru şekilde çalışması için gereken ayrıcalıklara belirli ayrıcalıklar vermenize olanak tanır. Bu size, üçüncü taraf içeriklerinin dahil edilmesiyle ilişkili riski İçerik Güvenliği Politikası ile mümkün olanın da ötesinde azaltma fırsatı sunar.

Üstelik korumalı alan, zeki bir saldırganın kendi kodunuzdaki deliklerden yararlanma riskini azaltmak için güçlü bir tekniktir. Saldırganlar, monolitik uygulamayı korumalı alana alınmış bir grup hizmete ayırarak (her biri bağımsız işlevlerden oluşan küçük bir parçadan sorumlu olur), saldırganlar belirli çerçevelerin içeriğinin yanı sıra denetleyicilerinin de güvenliğini ihlal etmeye zorlanır. Özellikle denetleyicinin kapsamı önemli ölçüde küçültülebileceği için bu çok daha zor bir görevdir. Tarayıcıdan geri kalan konusunda yardım isterseniz güvenlikle ilgili çalışmanızı bu kodu denetlemek için harcayabilirsiniz.

Bu, korumalı alanın internetteki güvenlik sorununa tam bir çözüm olduğu anlamına gelmez. Derinlemesine savunma sağlar ve kullanıcılarınızın istemcileri üzerinde kontrolünüz olmadığı sürece henüz tüm kullanıcılarınız için tarayıcı desteğine güvenemezsiniz (kullanıcılarınızın istemcilerini siz denetliyorsanız (örneğin, kurumsal bir ortam), yaşasın!). Bir gün... ancak şimdilik korumalı alan oluşturma, savunmanızı güçlendirmek için kullanabileceğiniz bir başka koruma katmanıdır. Tamamen güvenebileceğiniz tam bir savunma değildir. Yine de katmanlar mükemmel. bundan yararlanmanızı öneriyorum.

Ek Okumalar

  • "HTML5 Uygulamalarında Ayrıcalık Ayırma", küçük bir çerçevenin tasarımı ve mevcut üç HTML5 uygulamasına uygulanması üzerinde çalışan ilginç bir makaledir.

  • Korumalı alan oluşturma, diğer iki yeni iframe özelliğiyle birleştirildiğinde daha da esnek olabilir: srcdoc ve seamless. Birinci yöntem, HTTP isteğinin ek yükü olmadan çerçeveyi içerikle doldurmanıza olanak tanır. İkincisi ise stilin çerçeveli içeriğe akmasına olanak tanır. Her ikisinin de tarayıcı desteği şu anda oldukça kötü (Chrome ve WebKit gecelik) ancak bu özellikler gelecekte ilginç bir kombinasyon olacak. Örneğin, aşağıdaki kodu kullanarak bir makaleyle ilgili yorumları korumalı alana alabilirsiniz:

        <iframe sandbox seamless
                srcdoc="<p>This is a user's comment!
                           It can't execute script!
                           Hooray for safety!</p>"></iframe>