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çerikler bazen sitenin yerel içeriklerinden bile daha önemlidir. 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 (her reklam, 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 ama çok güzel. Lütfen Tarayıcı, bunu yerleştir, ancak sitemin çalışmasını engellemesine izin verme."

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, artık bazı yerleşik içeriklerin kullanmaması gereken ayrıcalıklardan yararlanmayacağına körü körüne güvenmek zorunda değiliz. Bu durumda, işleve erişimi zaten yoktur.

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çevelenmiş içerik, sayfanızı DOM'una veya yerel olarak depoladığınız verilere erişemez ve sayfadaki rastgele konumlara çizim yapamaz. Kapsamı, çerçevenin dış çizgisiyle sınırlıdır. Ancak bu ayrım gerçekten sağlam değildir. İçeriden sayfaya erişen kullanıcılar, can sıkıcı veya kötü amaçlı davranışlarda bulunabilir. Otomatik oynatılan videolar, eklentiler ve pop-up'lar bunlardan sadece birkaçı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. Bu arayüzün, tweet'i doğru hesaba bağlamak için Twitter'ın çerezlerine erişmesi ve tweet gönderme formunu gönderebilmesi gerekir. Hemen hemen bu kadar. Çerçevenin herhangi bir eklenti yüklemesi, üst düzey pencerede gezinmesi veya diğer işlevlerden herhangi birini yapması gerekmez. Bu ayrıcalıklara ihtiyaç duymadığından çerçevenin içeriğini korumalı alana alarak bunları 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 yapmak için iframe öğesine aşağıdaki değeri içeren 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, yalnızca komut dosyası etiketleri aracılığıyla açıkça yüklenen JavaScript'i değil, satır içi etkinlik işleyicileri ve javascript: URL'lerini de kapsar. Ayrıca, noscript etiketlerinde bulunan içeriğin, kullanıcı komut dosyasını devre dışı bırakmış gibi gösterileceği anlamına da gelir.
  • Çerçevelenmiş doküman benzersiz bir kaynağa yüklenir. Bu, aynı kaynakla ilgili tüm kontrollerin başarısız olacağı anlamına gelir. Benzersiz kaynaklar, kendileriyle bile hiçbir zaman 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çeveli 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 kendi içinde gezinebilir, üst düzey üst öğesine gidemez. window.top.location ayarını yaptığınızda bir istisna oluşur ve target="_top" içeren bağlantıyı tıklamanın hiç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 oldukça katı bir uygulamadır ve tamamen korumalı bir iframe içine yüklenen bir doküman gerçekten çok az risk taşır. Elbette, çok fazla değer de sunamaz: Bazı statik içerikler için tam korumalı alanla idare edebilirsiniz ancak çoğu zaman biraz daha esnek olmak 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, söz konusu kaynağın verilerine erişmeye devam eder.
  • allow-scripts, JavaScript'in yürütülmesine ve özelliklerin otomatik olarak tetiklenmesine (JavaScript aracılığıyla uygulanması kolay olduğu için) olanak tanır.
  • allow-top-navigation, üst düzey pencerede gezinerek dokümanın çerçevenin dışına çıkmasını sağlar.

Bu bilgiler ışığında, yukarıdaki Twitter örneğinde neden belirli bir korumalı alan işareti grubuna ulaştığı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 uygulandığında widget yalnızca ihtiyaç duyduğu izinleri alır ve eklentiler, üst gezinme ve işaretçi kilidi gibi özellikler engellenmeye devam eder. 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 pwnage 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 parçasındaki kötüye kullanımların mümkün olan en az zararı vermesini 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 dize alıp JavaScript olarak değerlendiren heyecan verici bir uygulamadır. Vay canına, değil mi? Tam da uzun yıllardır beklediğiniz ş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ğini bağlayarak yalnızca üst öğesinden gelen mesajları dinleyen minimum düzeyde bir doküman bulunur. Üst öğe, iframe'ın içeriklerinde postMessage'i her çalıştırdığında bu etkinlik tetiklenir ve üst öğenin yürütmemizi istediği dizeye erişim elde ederiz.

İşleyicide, etkinliğin source özelliğini (üst pencere) alırız. Bu e-posta adresini, yoğun çalışmamızın sonucunu tamamladığımızda size göndermek için kullanacağız. Ardından, bize iletilen verileri eval()'e aktararak tüm işi biz yaparız. Bu çağrı, korumalı alandaki iframe içinde yasaklanmış işlemler sık sık DOM istisnaları oluşturacağından bir try bloğuna yerleştirilmiştir. Bu istisnaları yakalayıp bunun yerine kullanıcı dostu bir hata mesajı bildiririz. 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 için gerekli bağlantıları yapacağı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 öğesine yapılan tıklamalara 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 kendi kodunuz için de aynısını 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'da güncel bir destek tablosu mevcuttur). 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, İçerik Güvenliği Politikası ile mümkün olanın ötesinde, üçüncü taraf içeriği eklemeyle ilişkili riskleri azaltabilirsiniz.

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. Şu anda her ikisi de oldukça kötü tarayıcı desteğine sahip (Chrome ve WebKit gece sürümleri). Ancak gelecekte ilginç bir kombinasyon olacak. Örneğin, aşağıdaki kodla 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>