Mainkan dengan aman di IFrame dengan sandbox

Membangun pengalaman yang kaya di web saat ini hampir tidak dapat dihindari melibatkan menyematkan komponen dan konten yang tidak Anda kendalikan sepenuhnya. Widget pihak ketiga dapat mendorong interaksi dan memainkan peran penting dalam keseluruhan pengalaman pengguna, dan konten buatan pengguna terkadang bahkan lebih penting daripada konten asli situs. Tidak melakukan keduanya bukanlah pilihan, tapi dapat meningkatkan risiko {i>Sesuatu yang BurukTM<i} terjadi di situs Anda. Masing-masing yang Anda sematkan -- setiap iklan, setiap widget media sosial -- merupakan potensi vektor serangan bagi mereka dengan niat jahat:

Kebijakan Keamanan Konten (CSP) dapat mengurangi risiko yang terkait dengan kedua jenis konten ini dengan Anda kemampuan untuk mengizinkan sumber skrip yang tepercaya secara khusus dan saat ini. Ini adalah langkah utama ke arah yang benar, tetapi perlu dicatat bahwa perlindungan yang ditawarkan oleh sebagian besar direktif CSP adalah biner: sumber dayanya diizinkan, atau tidak. Ada kalanya Anda perlu mengatakan "Saya tidak memastikan saya benar-benar memercayai sumber konten ini, tapi hasilnya sangat bagus! Sematkan tolong, Browser, tapi jangan biarkan itu merusak situs saya."

Hak Istimewa Terendah

Intinya, kami mencari mekanisme yang memungkinkan kami untuk memberikan konten yang hanya menyematkan tingkat kemampuan minimum yang diperlukan untuk melakukan tugasnya. Jika widget tidak perlu memunculkan jendela baru, sehingga menghapus akses ke window.open tidak dapat terluka. Jika layanan tersebut tidak memerlukan Flash, menonaktifkan dukungan plugin seharusnya masalah. Kita akan seaman mungkin jika kita mengikuti prinsip setidaknya hak istimewa, dan memblokir setiap fitur yang tidak secara langsung relevan dengan fungsi yang kita inginkan untuk digunakan. Hasilnya, kita tidak lagi harus mepercayai bahwa beberapa bagian konten tersemat tidak akan memanfaatkan hak istimewa yang tidak boleh digunakannya. Ini tidak akan memiliki akses ke fungsionalitas itu sejak awal.

Elemen iframe adalah langkah pertama menuju framework yang baik untuk solusi semacam itu. Memuat beberapa komponen yang tidak tepercaya di iframe memberikan ukuran pemisahan antara aplikasi Anda dan konten yang ingin dimuat. Konten berbingkai tidak akan memiliki akses ke DOM halaman Anda, atau data yang telah Anda simpan secara lokal, juga tidak akan dapat menggambar ke posisi apa pun di laman; cakupannya terbatas pada garis besar {i>frame<i} tersebut. Namun, pemisahan tersebut tidak benar-benar kuat. Halaman yang dimuat masih memiliki sejumlah opsi untuk perilaku yang mengganggu atau berbahaya: putar otomatis video, plugin, dan {i>pop-up<i} adalah awal dari banyak hal.

Atribut sandbox dari elemen iframe memberikan apa yang kita butuhkan untuk memperketat pembatasan konten berbingkai. Kita dapat menginstruksikan browser untuk memuat konten frame tertentu dengan hak istimewa rendah keamanan, yang hanya memungkinkan subset kemampuan yang diperlukan untuk melakukan apa pun perlu dilakukan pekerjaan.

Mengejutkan, tetapi memverifikasi

"Tweet" Twitter adalah contoh fungsi yang bagus yang dapat lebih disematkan dengan aman di situs Anda melalui sandbox. Twitter memungkinkan Anda menyematkan tombol melalui iframe dengan kode berikut:

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

Untuk mengetahui apa yang dapat kita kunci, mari kita periksa dengan cermat kemampuan apa yang dibutuhkan tombol tersebut. HTML yang dimuat ke dalam {i>frame<i} mengeksekusi sedikit JavaScript dari server Twitter, dan menghasilkan pop-up yang diisi dengan antarmuka {i>tweet<i} saat diklik. Antarmuka itu membutuhkan akses ke cookie untuk mengaitkan tweet ke akun yang benar, dan memerlukan kemampuan untuk mengirimkan formulir tweeting. Cukup begitu saja; {i>frame<i} tidak perlu memuat plugin apa pun, tidak perlu menavigasi jendela tingkat atas, atau salah satu jumlah bit fungsionalitas lainnya. Karena ia tidak membutuhkan hak istimewa tersebut, mari kita hapus dengan melakukan {i> sandbox<i} konten {i>frame<i}.

Sandbox bekerja berdasarkan daftar yang diizinkan. Kita mulai dengan menghapus semua izin akses yang diberikan, dan kemudian mengaktifkan kembali kemampuan individu dengan menambahkan penanda tertentu ke konfigurasi sandbox. Untuk widget Twitter, kita telah memutuskan untuk mengaktifkan JavaScript, popup, pengiriman formulir, dan cookie. Kita dapat melakukannya dengan menambahkan atribut sandbox ke iframe dengan nilai berikut:

<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>

Selesai. Kita telah memberikan {i>frame<i} semua kemampuan yang dibutuhkan, dan browser akan membantu menolak akses ke hak istimewa yang tidak kita secara eksplisit memberikannya melalui nilai atribut sandbox.

Kontrol Terperinci atas Kemampuan

Kita melihat beberapa kemungkinan tanda {i>sandbox<i} pada contoh di atas. Sekarang mari kita mendalami cara kerja bagian dalam atribut dengan sedikit lebih detail.

Mengingat iframe dengan atribut sandbox kosong, dokumen berbingkai akan sepenuhnya di-sandbox, sehingga membuatnya menjadi subjek pembatasan berikut:

  • JavaScript tidak akan dijalankan dalam dokumen berbingkai. Hal ini tidak hanya mencakup JavaScript yang dimuat secara eksplisit melalui tag skrip, tetapi juga pengendali peristiwa inline dan javascript: URL. Ini juga berarti bahwa konten yang terkandung dalam tag noscript akan ditampilkan, seolah-olah pengguna telah menonaktifkan skrip sendiri.
  • Dokumen berbingkai dimuat ke asal yang unik, yang berarti bahwa semua pemeriksaan dari origin yang sama akan gagal; asal yang unik tidak sama dengan asal yang lain, bahkan diri mereka sendiri. Di antara dampak lainnya, ini berarti bahwa dokumen tidak memiliki akses ke data yang disimpan di cookie origin mana pun atau mekanisme penyimpanan lainnya (Penyimpanan DOM, Indexed DB, dll.).
  • Dokumen berbingkai tidak dapat membuat jendela atau dialog baru (melalui window.open atau target="_blank").
  • Formulir tidak dapat dikirim.
  • Plugin tidak akan dimuat.
  • Dokumen berbingkai hanya dapat menavigasi sendiri, bukan induk tingkat teratasnya. Menyetel window.top.location akan menampilkan pengecualian, dan mengklik link dengan target="_top" tidak akan berpengaruh.
  • Fitur yang dipicu secara otomatis (elemen formulir yang difokuskan otomatis, pemutaran otomatis video, dll.) akan diblokir.
  • Kunci kursor tidak dapat diperoleh.
  • Atribut seamless diabaikan di iframes dalam dokumen berbingkai.

Ini sangat kejam, dan dokumen dimuat ke iframe dengan sandbox sepenuhnya memang memiliki risiko yang sangat kecil. Tentu saja, hal itu juga tidak bisa memberikan banyak manfaat: Anda mungkin bisa menggunakan sandbox penuh untuk beberapa konten statis, tetapi sebagian besar waktu Anda perlu mengendurkan segalanya.

Dengan pengecualian plugin, masing-masing pembatasan ini dapat dicabut dengan menambahkan tanda ke nilai atribut sandbox. Dokumen dengan sandbox tidak akan pernah menjalankan plugin, karena plugin adalah kode native yang tidak di-sandbox, tetapi yang lainnya adil permainan:

  • allow-forms mengizinkan pengiriman formulir.
  • allow-popups mengizinkan pop-up (shock).
  • allow-pointer-lock memungkinkan penguncian pointer (secara mengejutkan).
  • allow-same-origin memungkinkan dokumen mempertahankan asalnya; halaman dimuat dari https://example.com/ akan mempertahankan akses ke data origin tersebut.
  • allow-scripts memungkinkan eksekusi JavaScript, dan juga memungkinkan fitur untuk dipicu secara otomatis (karena mudah diterapkan melalui JavaScript).
  • allow-top-navigation memungkinkan dokumen keluar dari frame dengan menavigasi jendela {i>top-level<i}.

Dengan mengingat hal ini, kita dapat mengevaluasi dengan tepat mengapa kami berakhir dengan satu set tanda sandboxing pada contoh Twitter di atas:

  • allow-scripts diperlukan, karena halaman yang dimuat ke dalam frame menjalankan beberapa JavaScript untuk menangani interaksi pengguna.
  • allow-popups diperlukan, karena tombol akan memunculkan formulir tweet di jendela.
  • allow-forms diperlukan, karena formulir tweet harus dapat dikirimkan.
  • allow-same-origin diperlukan, karena cookie twitter.com akan menggunakan tidak dapat diakses, dan pengguna tidak dapat {i>login<i} untuk memposting formulir.

Satu hal penting yang perlu diperhatikan adalah bahwa penanda {i>sandbox<i} yang diterapkan pada sebuah {i>frame<i} juga berlaku untuk jendela atau bingkai apa pun yang dibuat di {i>sandbox<i}. Ini berarti kita memiliki untuk menambahkan allow-forms ke sandbox frame, meskipun formulir tersebut hanya ada di jendela tempat {i>frame<i} itu muncul.

Dengan menerapkan atribut sandbox, widget hanya mendapatkan izin yang diberikan diperlukan, dan kemampuan seperti plugin, navigasi atas, dan penguncian pointer tetap diblokir. Kami telah mengurangi risiko penyematan widget tersebut, tanpa efek buruk. Hal ini menguntungkan bagi semua orang yang peduli.

Pemisahan Hak Istimewa

Melakukan sandbox konten pihak ketiga untuk menjalankan kode yang tidak tepercaya dalam lingkungan dengan hak istimewa rendah sudah jelas menguntungkan. Tapi bagaimana dengan kode Anda sendiri? Kamu percaya pada diri sendiri, kan? Jadi mengapa perlu mengkhawatirkan sandbox?

Saya akan membalikkan pertanyaan itu: jika kode Anda tidak memerlukan plugin, mengapa diberikan akses ke plugin? Paling-paling, ini adalah hak istimewa yang tidak pernah Anda gunakan, paling buruknya itu vektor yang potensial bagi penyerang untuk menginjakkan kakinya di pintu. Kode setiap orang memiliki {i>bugs<i}, dan hampir setiap aplikasi rentan terhadap eksploitasi dengan satu cara atau lainnya. Sandbox kode Anda sendiri berarti bahwa meskipun penyerang berhasil merusak aplikasi Anda, pengguna tersebut tidak akan diberi akses penuh ke asal aplikasi; mereka hanya akan bisa melakukan hal-hal yang bisa dilakukan aplikasi fungsi tersebut. Masih buruk, tapi tidak seburuk biasanya.

Anda dapat mengurangi risiko lebih jauh dengan memecah aplikasi menjadi bagian logis dan {i>sandbox <i}setiap bagian dengan hak istimewa seminimal mungkin. Teknik ini sangat umum dalam kode native: Chrome, misalnya, merusak dirinya sendiri proses browser dengan hak istimewa tinggi yang memiliki akses ke {i>hard drive<i} lokal dan dapat membuat koneksi jaringan, serta banyak proses perender dengan hak istimewa rendah yang melakukan tugas berat dalam mengurai konten yang tidak tepercaya. Perender tidak perlu menyentuh {i>disk<i}, {i>browser<i} akan memberikan semua informasi yang dibutuhkan untuk merender halaman. Bahkan jika peretas yang pintar menemukan cara untuk merusak perender, dia belum terlalu jauh, karena perender tidak dapat melakukan banyak hal menarik sendiri: semua akses hak istimewa tinggi harus diarahkan melalui proses {i>browser<i}. Penyerang perlu menemukan beberapa lubang di bagian sistem yang berbeda menimbulkan kerusakan, yang sangat mengurangi risiko serangan pwnage.

Sandbox eval() dengan aman

Dengan sandboxing dan postMessage API, kesuksesan model ini cukup mudah untuk diterapkan di web. Bagian dari aplikasi Anda dapat berada di iframe dengan sandbox, dan dokumen induk dapat perantara komunikasi di antara mereka dengan memposting pesan dan mendengarkan yang dihasilkan. Struktur semacam ini memastikan bahwa eksploitasi di salah satu bagian aplikasi Anda dapat melakukan kerusakan seminimal mungkin. Hal ini juga memiliki keuntungan untuk memaksa Anda untuk membuat titik integrasi yang jelas, sehingga Anda tahu persis di mana Anda harus berhati-hati dalam memvalidasi input dan output. Mari kita lihat contoh mainan, untuk melihat cara kerjanya.

Evalbox adalah aplikasi yang menarik yang mengambil {i>string<i}, dan mengevaluasinya sebagai JavaScript. Wah, benar kan? Apa saja yang telah Anda tunggu-tunggu selama bertahun-tahun. Ini adalah serangan yang cukup berbahaya aplikasi, tentu saja, karena mengizinkan JavaScript arbitrer untuk dieksekusi berarti bahwa setiap dan semua data yang ditawarkan origin siap diperebutkan. Kita akan memitigasi risiko {i>Bad ThingsTM<i} terjadi dengan memastikan bahwa kode dieksekusi di dalam {i>sandbox<i}, yang membuatnya sedikit lebih aman. Kita akan mempelajari kode dari ke dalam, dimulai dengan isi {i>frame<i}:

<!-- 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>

Di dalam frame, kita memiliki dokumen minimal yang hanya memproses pesan dari induknya dengan mengaitkan ke peristiwa message dari objek window. Setiap kali induk mengeksekusi postMessage pada konten iframe, peristiwa ini akan akan dipicu, sehingga memberi kita akses ke string yang diinginkan oleh induk kita mengeksekusi.

Di pengendali, kita mengambil atribut source peristiwa, yang merupakan induk jendela. Kita akan menggunakannya untuk mengirimkan hasil kerja keras kita kembali setelah kita selesai. Kemudian kita akan melakukan bagian pekerjaan yang sulit, dengan meneruskan data yang telah diberikan ke eval(). Panggilan ini telah ditutup dalam blok coba, sebagai operasi yang diblokir di dalam iframe dengan sandbox akan sering menghasilkan pengecualian DOM; kita akan menangkap dan melaporkan pesan {i>error<i}. Terakhir, kita posting hasil kembali ke jendela induk. Ini adalah hal yang cukup sederhana.

Induknya juga tidak rumit. Kita akan membuat UI kecil dengan textarea untuk kode, dan button untuk eksekusi, lalu kita akan menarik frame.html melalui iframe dalam sandbox, hanya memungkinkan eksekusi skrip:

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

Sekarang kita akan menyiapkan semuanya untuk dieksekusi. Pertama, kita akan mendengarkan respons dari iframe dan alert() kepada pengguna. Diduga aplikasi nyata akan melakukan sesuatu yang tidak terlalu mengganggu:

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);
    });

Berikutnya, kita akan menghubungkan pengendali peristiwa untuk mengklik button. Saat pengguna klik, kita akan mengambil konten textarea saat ini, dan meneruskannya ke {i>frame<i} untuk dieksekusi:

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);

Mudah, kan? Kita telah membuat API evaluasi yang sangat sederhana, dan dapat yakin bahwa kode yang dievaluasi tidak memiliki akses ke informasi sensitif seperti cookie atau penyimpanan DOM. Demikian juga, kode yang dievaluasi tidak dapat memuat plugin, memunculkan jendela baru, atau sejumlah aktivitas mengganggu atau jahat lainnya.

Anda dapat melakukan hal yang sama untuk kode Anda sendiri dengan memecah aplikasi monolitik menjadi komponen dengan tujuan tunggal. Masing-masing dapat digabungkan dalam API pesan sederhana, cukup seperti yang telah kita tulis di atas. Jendela induk dengan hak istimewa tinggi dapat bertindak sebagai {i>controller<i} dan operator, mengirim pesan ke modul khusus yang masing-masing memiliki hak istimewa yang paling sedikit untuk melakukan pekerjaan, mendengarkan hasil, dan memastikan bahwa setiap modul hanya diberi informasi yang dibutuhkan.

Namun, perlu diperhatikan bahwa Anda harus sangat berhati-hati saat menangani konten berbingkai yang berasal dari asal yang sama dengan induknya. Jika halaman di https://example.com/ membingkai halaman lain di asal yang sama dengan sandbox yang menyertakan flag allow-same-origin dan allow-scripts, lalu halaman yang berbingkai bisa menjangkau induk, dan menghapus atribut {i>sandbox<i} sepenuhnya.

Bermain di sandbox

Sandboxing kini tersedia di berbagai browser: Firefox 17+, IE10+, dan Chrome pada saat penulisan (kaniuse, tentu saja, memiliki tabel dukungan). Menerapkan sandbox ke iframes yang Anda sertakan memungkinkan Anda memberikan hak istimewa tertentu kepada konten yang ditampilkan, hanya hak istimewa yang diperlukan untuk konten berfungsi dengan baik. Hal ini memberi Anda kesempatan untuk mengurangi risiko yang terkait dengan penyertaan konten pihak ketiga, melebihi apa yang dapat dilakukan dengan Content Security Kebijakan kami.

Selain itu, {i>sandbox<i} adalah teknik ampuh untuk mengurangi risiko bahwa penyerang akan dapat mengeksploitasi lubang di kode Anda sendiri. Dengan memisahkan aplikasi monolitik ke dalam satu set layanan dalam sandbox, masing-masing bertanggung jawab untuk sepotong kecil fungsionalitas mandiri, penyerang akan dipaksa untuk tidak hanya mengganggu {i>frame<i} tertentu konten, tetapi juga pengontrolnya. Itu adalah tugas yang jauh lebih sulit, terutama karena {i> controller<i} dapat sangat berkurang ke dalam cakupan. Anda dapat menghabiskan upaya terkait keamanan untuk mengaudit kode itu jika Anda minta bantuan ke browser untuk sisanya.

Ini tidak berarti bahwa {i>sandbox<i} adalah solusi lengkap untuk masalah keamanan di internet. Cara ini menawarkan pertahanan yang mendalam, kecuali jika Anda memiliki pengguna Anda Anda belum dapat mengandalkan dukungan browser untuk semua pengguna Anda (jika Anda mengontrol, klien pengguna Anda -- lingkungan perusahaan, misalnya -- hore!). Suatu hari... tetapi untuk saat ini, {i>sandbox<i} adalah lapisan lain dari perlindungan untuk memperkuat pertahanan Anda, bukanlah pertahanan lengkap yang dapat Anda andalkan. Namun, lapisan sangat baik. Saya sarankan untuk memanfaatkan satu.

Bacaan Lebih Lanjut

  • "Pemisahan Hak Istimewa dalam Aplikasi HTML5" adalah makalah menarik yang bekerja melalui desain kerangka kerja kecil, dan penerapannya ke tiga aplikasi HTML5 yang ada.

  • Sandboxing dapat menjadi lebih fleksibel jika dikombinasikan dengan dua iframe baru lainnya atribut: srcdoc, dan seamless. Metode pertama memungkinkan Anda mengisi {i>frame<i} dengan konten tanpa overhead permintaan HTTP, dan yang kedua memungkinkan gaya untuk mengalir ke konten yang dibingkai. Keduanya memiliki dukungan browser yang cukup buruk saat ini (Chrome dan WebKit malam). tetapi akan menjadi kombinasi yang menarik di masa mendatang. Anda bisa, misalnya, komentar sandbox di artikel melalui kode berikut:

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