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

Günümüzde web'de zengin bir deneyim oluşturmak, neredeyse kaçınılmaz olarak üzerinde gerçek bir kontrol sahibi olmadığınız bileşenleri ve içerikleri yerleştirmeyi gerektirir. Üçü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 bir sitenin yerel içeriğinden daha da önemli olabilir. Bunlardan herhangi birini kullanmamak pek mümkün değildir ancak her ikisi de sitenizde Kötü Bir Şey™ yaşanma riskini artırır. Yerleştirdiğiniz her widget (tüm reklamlar, her sosyal medya widget'ı), kötü amaçlı kişiler için potansiyel bir saldırı vektörüdür:

İçerik Güvenliği Politikası (İGP), özellikle güvenilir komut dosyası ve diğer içerik kaynaklarını beyaz listeye ekleme olanağı sunarak bu içerik türlerinin her ikisiyle de ilgili riskleri azaltabilir. Bu, doğru yönde atılan önemli bir adımdır ancak çoğu CSP yönergesinin sunduğu korumanın ikili olduğunu belirtmek gerekir: Kaynağa izin verilir veya verilmez. "Bu içerik kaynağına güvendiğimden emin değilim ancak çok güzel. Lütfen yerleştirin, Tarayıcı." ama bunun sitemi bozmasına izin vermeyin."

En Az Ayrıcalık

Özetle, yerleştirdiğimiz içeriğe yalnızca görevini yerine getirmek için gereken minimum düzeyde özellik vermemizi sağlayacak bir mekanizma arıyoruz. Bir widget'ın yeni bir pencere açması gerekmiyorsa window.open erişimini kaldırmanızda bir sakınca yoktur. Flash gerektirmiyorsa eklenti desteğini devre dışı bırakmak sorun oluşturmaz. En az ayrıcalık ilkesini uygular ve kullanmak istediğimiz işlevle doğrudan alakalı olmayan her özelliği engellersek mümkün olduğunca güvenli oluruz. Sonuç olarak, yerleştirilmiş içeriğin bir kısmının, kullanmaması gereken ayrıcalıklardan yararlanamayacağına artık körü körüne güvenmek zorunda değiliz. Böylece, uygulamanın işlevlere daha önce erişimi olmaz.

iframe öğeleri, bu tür bir çözüm için iyi bir çerçeve oluşturmanın ilk adımıdır. Güvenilmeyen bir bileşeni iframe içine yüklemek, uygulamanız ile yüklemek istediğiniz içerik arasında bir ayrım sağlar. Çerçeveli içerik, sayfanızın DOM'sine veya yerel olarak depoladığınız verilere erişemez ve sayfadaki rastgele konumlara çizim yapamaz; yalnızca çerçevenin dış çizgisiyle sınırlıdır. Ancak bu ayrım gerçekten sağlam değildir. İçeriden sayfa, can sıkıcı veya kötü amaçlı davranışlar için hâlâ çeşitli seçeneklere sahiptir: Otomatik oynatılan videolar, eklentiler ve pop-up'lar, buzdağının görünen kısmıdır.

iframe öğesinin sandbox özelliği, çerçevelenmiş içerikle ilgili kısıtlamaları sıkılaştırmak için ihtiyacımız olan her şeyi bize sağlar. Tarayıcıya, belirli bir çerçevenin içeriğini düşük ayrıcalıklı bir ortamda yüklemesini talimat verebiliriz. Bu durumda, yalnızca yapılması gereken işi yapmak için gereken özellik alt kümesine izin verilir.

Güven, ancak doğrula

Twitter'ın "Tweet" düğmesi, 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 aracılığıyla 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 belirlemek için düğmenin hangi özellikleri gerektirdiğini dikkatlice inceleyelim. Çerçeveye yüklenen HTML, Twitter'ın sunucularından bir miktar JavaScript yürütür ve tıklandığında tweet arayüzüyle doldurulmuş bir pop-up oluşturur. Tweet'in doğru hesaba bağlanması için bu arayüzün Twitter'ın çerezlerine erişmesi ve tweet formunu gönderebilmesi gerekir. Hepsi bu kadar; çerçevenin herhangi bir eklenti yüklemesi ya da üst düzey pencerede veya başka herhangi bir işlevde gezinmesi gerekmez. Bu ayrıcalıklara ihtiyaç duymadığından bunları, çerçevenin içeriğini korumalı alana alarak kaldıralım.

Korumalı alan, beyaz liste temel alınarak çalışır. Olası tüm izinleri kaldırarak başlarız ve ardından korumalı alanın yapılandırmasına belirli işaretler ekleyerek ayrı ayrı özellikleri tekrar etkinleştiririz. Twitter widget'ı için JavaScript, pop-up'lar, form gönderimi ve twitter.com'un çerezlerini etkinleştirmeye karar verdik. Bunu, iframe öğesine aşağıdaki değerle 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 ihtiyaç duyduğu tüm özellikleri verdik. Tarayıcı, sandbox özelliğinin değeri aracılığıyla açıkça izin vermediğimiz ayrıcalıklara erişmesine izin vermez.

Özellikler üzerinde ayrıntılı kontrol

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

Boş bir korumalı alan özelliğine sahip bir iframe verildiğinde, çerçevelenen doküman tamamen korumalı alana alınır ve aşağıdaki kısıtlamalara tabi olur:

  • JavaScript, çerçevelenmiş dokümanda yürütülmez. Bu kapsamda yalnızca komut dosyası etiketleri aracılığıyla açıkça yüklenen JavaScript'ler değil, satır içi etkinlik işleyiciler ve javascript: URL'leri de yer alır. Bu, noscript etiketlerinde bulunan içeriğin, kullanıcı komut dosyasını devre dışı bırakmış gibi gösterileceği anlamına da gelir.
  • Çerçeveli belge benzersiz bir kaynağa yüklenir. Bu, aynı kaynak kontrollerinin tümü başarısız olacağı anlamına gelir. Benzersiz kaynaklar, kendileri dahil hiçbir zaman başka hiçbir kaynakla eşleşmez. Bu durum, diğer etkilerin yanı sıra dokümanın herhangi bir kaynağın çerezlerinde veya diğer depolama mekanizmalarında (DOM depolama alanı, dizine eklenen veritabanı vb.) depolanan verilere erişemediği anlamına gelir.
  • Çerçevelenmiş doküman yeni pencereler veya iletişim kutuları oluşturamaz (örneğin, window.open veya target="_blank" aracılığıyla).
  • Formlar gönderilemez.
  • Eklentiler yüklenmez.
  • Çerçevelenmiş doküman yalnızca kendisine gidebilir, üst düzey üst öğesine gidemez. window.top.location ayarlaması bir istisnaya neden olur ve target="_top" ile bağlantı tıklandığında herhangi bir etkisi olmaz.
  • Otomatik olarak tetiklenen özellikler (otomatik odaklanan form öğeleri, otomatik oynatılan videolar vb.) engellenir.
  • İşaretçi kilidi alınamıyor.
  • Çerçevelenmiş belgenin içerdiği iframes üzerinde seamless özelliği yoksayılır.

Bu son derece acımasız bir davranıştır ve tamamen korumalı alana alınmış iframe içine yüklenen bir doküman aslında çok az risk taşır. Elbette, büyük bir değer de sunmaz: Birtakım statik içerikler için tam korumalı alan kullanabilirsiniz, ancak çoğu zaman işleri biraz gevşetmek istersiniz.

Eklentiler hariç olmak üzere bu kısıtlamaların her biri, korumalı alan özelliğinin değerine bir işaret ekleyerek kaldırılabilir. Eklentiler korumalı alandan kaldırılmış yerel kod olduğundan, korumalı alandaki dokümanlar hiçbir zaman eklenti çalıştıramaz. Ancak diğer her şey serbesttir:

  • allow-forms, form gönderimine izin verir.
  • allow-popups, pop-up'lara (şok!) izin verir.
  • allow-pointer-lock, işaretçi kilidine (şaşırtıcı bir şekilde) izin verir.
  • allow-same-origin, dokümanın kaynağını korumasına olanak tanır. https://example.com/ kaynağından yüklenen sayfalar, bu kaynağın verilerine erişmeye devam eder.
  • allow-scripts, JavaScript'in yürütülmesine olanak tanır ve ayrıca özelliklerin otomatik olarak tetiklenmesine olanak tanır (JavaScript aracılığıyla uygulanması önemsiz olacağından).
  • allow-top-navigation, dokümanın üst düzey pencerede gezinerek çerçeveden ayrılmasına olanak tanır.

Bunları göz önünde bulundurarak yukarıdaki Twitter örneğinde özel korumalı alan işaretleme işaretleriyle neden sonuçladığımızı tam olarak değerlendirebiliriz:

  • Çerçeveye yüklenen sayfa, kullanıcı etkileşimini işlemek için bazı JavaScript'ler çalıştırdığından allow-scripts gereklidir.
  • Düğme yeni bir pencerede tweet gönderme formu açtığından allow-popups gereklidir.
  • Tweet gönderme formu gönderilebilir olması gerektiğinden allow-forms gereklidir.
  • Aksi takdirde twitter.com'un çerezlerine erişilemeyeceği ve kullanıcının formu göndermek için oturum açamayacağı için allow-same-origin gereklidir.

Bir çerçeveye uygulanan korumalı alan işaretlerinin, korumalı alanda oluşturulan tüm pencereler veya çerçeveler için de geçerli olduğunu unutmayın. Bu nedenle, form yalnızca çerçevenin açıldığı pencerede mevcut olsa bile allow-forms öğesini çerçevenin korumalı alanına eklememiz gerekir.

sandbox özelliği etkinken, widget yalnızca gereken izinleri alır. Eklentiler, üst gezinme ve işaretçi kilidi gibi özellikler ise engellenmiş olarak kalır. Widget'ı yerleştirme riskini, olumsuz bir etki yaratmadan azalttık. Bu, ilgili herkes için kazançlı bir durumdur.

Ayrıcalık Ayırma

Güvenilmeyen kodlarını düşük ayrıcalıklı bir ortamda çalıştırmak için üçüncü taraf içeriklerini korumalı alanda tutmak oldukça faydalıdır. Peki kendi kodunuz ne olacak? Kendinize güveniyorsunuz, değil mi? Peki neden korumalı alan kullanımıyla ilgili endişelenmelisiniz?

Bu soruyu tersine çevirerek soruyorum: Kodunuzun eklentiye ihtiyacı yoksa neden eklentilere erişim izni veriyorsunuz? En iyi ihtimalle hiç kullanmadığınız bir ayrıcalık, en kötü ihtimalle saldırganların sisteme girmesi için olası bir vektördür. Herkesin kodunda hatalar vardır ve neredeyse her uygulama bir şekilde istismara açıktır. Kendi kodunuzu korumalı alana yerleştirmek, bir saldırgan uygulamanızı başarıyla altüst etse bile uygulamanın kaynağına tam erişim elde edemeyeceği anlamına gelir. Saldırgan yalnızca uygulamanın yapabileceği işlemleri yapabilir. Bu durum kötü olsa da olabileceği kadar kötü değildir.

Uygulamanızı mantıksal parçalara ayırarak ve her parçayı mümkün olan en az ayrıcalıkla korumalı alana alarak riski daha da azaltabilirsiniz. Bu teknik, yerel kodda çok yaygındır: Örneğin Chrome, kendisini yerel sabit sürücüye erişebilen ve ağ bağlantıları oluşturabilen yüksek ayrıcalıklı bir tarayıcı işlemine ve güvenilmeyen içeriği ayrıştırma konusunda ağır işler yapan birçok düşük ayrıcalıklı oluşturma işlemine ayırır. Oluşturucuların diske dokunması gerekmez. Tarayıcı, sayfayı oluşturmak için ihtiyaç duydukları tüm bilgileri onlara sağlar. Zeki bir bilgisayar korsanı bir oluşturma aracını bozmanın bir yolunu bulsa bile, oluşturma aracı tek başına pek fazla şey yapamayacağından çok fazla ilerleme kaydedemez. Tüm yüksek ayrıcalıklı erişim, tarayıcının işlemi üzerinden yönlendirilmelidir. Saldırganların herhangi bir zarar vermek için sistemin farklı parçalarında birkaç açık bulmaları gerekir. Bu da başarılı bir saldırı riskini büyük ölçüde azaltır.

eval()'ü güvenli şekilde korumalı alana alma

Korumalı alan ve postMessage API ile bu modelin başarısını web'e uygulamak oldukça kolaydır. Uygulamanızın parçaları korumalı alanlardaki iframe'lerde bulunabilir ve üst doküman, mesaj yayınlayarak ve yanıtları dinleyerek aralarında iletişim kurabilir. Bu tür bir yapı, uygulamanın herhangi bir bölümündeki istismarların mümkün olan en az zararı almasını sağlar. Ayrıca, net entegrasyon noktaları oluşturmanıza zorlar. Böylece, giriş ve çıkışı doğrulama konusunda tam olarak nerede dikkatli olmanız gerektiğini bilirsiniz. Bunun nasıl çalışabileceğini görmek için basit bir örnek üzerinden gidelim.

Evalbox, bir dizeyi JavaScript olarak değerlendiren heyecan verici bir uygulamadır. Vay canına, değil mi? Bu uzun yıllardır beklediğiniz ilk şey. Elbette bu oldukça tehlikeli bir uygulamadır. İsteğe bağlı JavaScript'in yürütülmesine izin vermek, bir kaynağın sunduğu tüm verilerin ele geçirilebileceği anlamına gelir. Kodun bir korumalı alanda yürütülmesini sağlayarak "Kötü Şeyler"™ yaşanma riskini azaltırız. Bu sayede kod çok daha güvenli hale gelir. Çerçevenin içeriğinden başlayarak kodu baştan sona inceleyeceğiz:

<!-- 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 yalnızca üst öğesinden gelen mesajları dinleyen minimum düzeyde bir doküman bulunur. Üst öğe, iframe içeriğinde postMessage yürüttüğünde, bu etkinlik tetiklenir ve üst öğemizin yürütmemizi istediği dizeye erişim izni verir.

İşleyicide, etkinliğin üst pencere olan source özelliğini alırız. Bunu, yoğun çalışmamızın sonucunu bitirdikten sonra tekrar göndermek için kullanacağız. Ardından, bize iletilen verileri eval()'e aktararak tüm işi biz yaparız. Korumalı alana alınmış bir iframe içindeki yasaklanan işlemler sık sık DOM istisnaları oluşturduğundan, bu çağrı bir deneme bloğunda sona erdi. Bu istisnaları yakalayıp bunun yerine uyumlu bir hata mesajı bildireceğiz. Son olarak, sonucu üst pencereye göndeririz. Bu oldukça basit bir işlemdir.

Üst öğe de benzer şekilde karmaşık değildir. Kod için bir textarea ve yürütme için bir button içeren küçük bir kullanıcı arayüzü oluştururuz. Ardından, yalnızca komut dosyası yürütmeye izin veren bir korumalı alan iframe aracılığıyla frame.html'yi alırız:

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

Şimdi yürütme planı hazırlayacağız. Öncelikle iframe'den gelen yanıtları dinleyecek ve kullanıcılarımıza alert() edeceğiz. Gerçek bir uygulama muhtemelen daha az can sıkıcı bir şey yapar:

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 öğesinin tıklamalarına bir etkinlik işleyici bağlarız. Kullanıcı tıkladığında textarea öğesinin mevcut içeriğini alır ve yürütme için çerçeveye iletiriz:

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 oluşturduk. Değerlendirilen kodun çerez veya DOM depolama alanı gibi hassas bilgilere erişemediğinden emin olabiliriz. Benzer şekilde, değerlendirilen kod eklentileri yükleyemez, yeni pencereler açamaz veya can sıkıcı ya da kötü amaçlı diğer etkinliklerden herhangi birini yapamaz.

Monolitik uygulamaları tek amaçlı bileşenlere ayırarak aynı işlemi kendi kodunuzda da yapabilirsiniz. Her biri, yukarıda yazdığımız gibi basit bir mesajlaşma API'sine sarmalanabilir. Yüksek ayrıcalıklı üst pencere, denetleyici ve dağıtıcı görevi görebilir. Her biri işlerini yapmak için mümkün olan en az ayrıcalığa sahip belirli modüllere mesaj gönderir, sonuçları dinler ve her modülün yalnızca ihtiyaç duyduğu bilgilerle beslendiğinden emin olur.

Ancak, üst öğeyle aynı kaynaktan gelen çerçevelenmiş içeriklerle çalışırken çok dikkatli olmanız gerektiğini unutmayın. https://example.com/ adresindeki bir sayfa, hem allow-same-origin hem de allow-scripts işaretlerini içeren bir korumalı alanla aynı kaynaktaki başka bir sayfayı çerçeve içine alırsa çerçeve içine alınan sayfa üst öğeye ulaşabilir ve korumalı alan özelliğini tamamen kaldırabilir.

Korumalı alanınızda oynama

Korumalı alan özelliği şu anda çeşitli tarayıcılarda kullanılabilir: Firefox 17 ve üzeri, IE10 ve üzeri ve Chrome (caniuse'ta güncel bir destek tablosu vardır). Eklediğiniz iframes öğesine sandbox özelliğini uygulamak, görüntüledikleri içeriğe belirli ayrıcalıklar vermenize olanak tanır. Bu ayrıcalıklar, yalnızca içeriğin düzgün çalışması için gerekli olan ayrıcalıklardır. Bu sayede, üçüncü taraf içeriklerinin dahil edilmesiyle ilişkili riskleri, İçerik Güvenliği Politikası ile mümkün olandan daha fazla ve daha az ortadan kaldırabilirsiniz.

Ayrıca korumalı alan, akıllı bir saldırganın kendi kodunuzdaki boşluklardan yararlanma riskini azaltmak için güçlü bir tekniktir. Monolitik bir uygulamayı, her biri bağımsız işlevselliğin küçük bir kısmından sorumlu olan bir dizi korumalı alan hizmetine ayırarak saldırganlar yalnızca belirli çerçevelerin içeriğini değil, aynı zamanda denetleyicilerini de tehlikeye atmaya zorlanır. Bu, özellikle de denetleyicinin kapsamı büyük ölçüde azaltılabileceğinden çok daha zor bir görevdir. Güvenlikle ilgili çabanızı bu kodu denetlemeye harcayabilirsiniz. Gerisi için tarayıcıdan yardım isteyebilirsiniz.

Bu, korumalı alan oluşturmanın internetteki güvenlik sorununa tam bir çözüm olduğu anlamına gelmez. Bu yöntem, kapsamlı bir savunma sunar. Kullanıcılarınızın istemcileri üzerinde kontrol sahibi değilseniz henüz tüm kullanıcılarınız için tarayıcı desteğinden yararlanamazsınız (Kullanıcılarınızın istemcileri üzerinde kontrol sahibiyseniz (ör. kurumsal bir ortamda) bu harika bir haberdir). Bir gün… Ancak şu anda korumanızı güçlendirmek için kullanabileceğiniz başka bir koruma katmanı olan korumalı alan, tek başına güvenebileceğiniz tam bir savunma değildir. Yine de katmanlar mükemmeldir. Bu aracı kullanmanızı öneririm.

Daha fazla bilgi

  • "HTML5 Uygulamalarında Ayrıcalık Ayrımı", küçük bir çerçevenin tasarımını ve bu çerçevenin mevcut üç HTML5 uygulamasına uygulanmasını ele alan ilginç bir makaledir.

  • Korumalı alan, diğer iki yeni iframe özelliğiyle (srcdoc ve seamless) birlikte kullanıldığında daha da esnek olabilir. İlki, bir çerçeveyi HTTP isteği yükü olmadan içerikle doldurmanıza olanak tanır. İkincisi ise stilin çerçevelenmiş içeriğe aktarılmasına olanak tanır. Her ikisi de şu anda oldukça kötü bir tarayıcı desteğine sahip (Chrome ve WebKit geceleri). Ancak gelecekte ilginç bir kombinasyon olacak. Örneğin, aşağıdaki kodu kullanarak bir makaledeki 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>