Mainkan dengan aman di IFrame dengan sandbox

Untuk membuat pengalaman yang kaya di web saat ini, hampir tidak dapat dihindari, melibatkan komponen dan konten yang tidak dapat Anda kendalikan secara nyata. Widget pihak ketiga dapat mendorong interaksi dan berperan penting dalam pengalaman pengguna secara keseluruhan, dan konten buatan pengguna terkadang bahkan lebih penting dibandingkan konten native situs. Tidak memilih salah satunya bukanlah pilihan, tetapi keduanya akan meningkatkan risiko Hal BurukTM yang dapat terjadi di situs Anda. Setiap widget yang Anda sematkan -- setiap iklan dan setiap widget media sosial -- merupakan vektor serangan yang berpotensi bagi mereka yang memiliki niat jahat:

Kebijakan Keamanan Konten (CSP) dapat mengurangi risiko yang terkait dengan kedua jenis konten ini dengan memberi Anda kemampuan untuk mengizinkan secara khusus sumber skrip tepercaya dan konten lainnya. Ini adalah langkah besar ke arah yang tepat, tetapi perlu diperhatikan bahwa perlindungan yang ditawarkan oleh sebagian besar perintah CSP bersifat biner: resource diizinkan, atau tidak. Ada kalanya saya akan merasa perlu mengatakan "Saya tidak yakin bahwa saya benar-benar memercayai sumber konten ini, tapi sangat keren! Harap sematkan, Browser, tetapi jangan biarkan itu merusak situs saya."

Hak Istimewa Terendah

Intinya, kami mencari mekanisme yang memungkinkan kami memberikan konten, kami hanya menyematkan tingkat kemampuan minimum yang diperlukan untuk melakukan tugasnya. Jika widget tidak perlu menampilkan jendela baru, penghapusan akses ke window.open tidak dapat disalahgunakan. Jika tidak memerlukan Flash, menonaktifkan dukungan plugin seharusnya tidak menjadi masalah. Kita dapat aman jika mengikuti prinsip hak istimewa terendah, dan memblokir setiap fitur yang tidak secara langsung relevan dengan fungsi yang ingin kita gunakan. Hasilnya, kami tidak lagi harus begitu percaya bahwa beberapa bagian konten yang disematkan tidak akan memanfaatkan hak istimewa yang seharusnya tidak digunakannya. Aplikasi ini tidak memiliki akses ke fungsi tersebut sejak awal.

Elemen iframe adalah langkah pertama menuju framework yang baik untuk solusi tersebut. Memuat beberapa komponen tidak tepercaya dalam iframe memberikan ukuran pemisahan antara aplikasi Anda dan konten yang ingin Anda muat. Konten yang di-frame tidak akan memiliki akses ke DOM halaman Anda, atau data yang telah Anda simpan secara lokal, juga tidak akan dapat menggambar ke posisi arbitrer di halaman; konten tersebut terbatas cakupannya untuk garis batas frame. Namun, pemisahannya tidak sepenuhnya efektif. Halaman yang ada di dalamnya masih memiliki sejumlah opsi untuk perilaku yang mengganggu atau berbahaya: video yang diputar otomatis, plugin, dan pop-up adalah sebagian kecilnya.

Atribut sandbox dari elemen iframe memberi kita apa yang kita butuhkan untuk memperketat batasan pada konten yang dibingkai. Kita dapat menginstruksikan browser untuk memuat konten frame tertentu di lingkungan dengan hak istimewa rendah, sehingga hanya sebagian kemampuan yang diperlukan untuk melakukan pekerjaan apa pun yang perlu dilakukan.

Mengubah, tetapi memverifikasi

Tombol "Tweet" Twitter adalah contoh fungsi yang bagus yang dapat disematkan dengan lebih 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 yang dibutuhkan tombol tersebut. HTML yang dimuat ke dalam frame mengeksekusi sedikit JavaScript dari server Twitter, dan menghasilkan pop-up yang diisi dengan antarmuka tweeting saat diklik. Antarmuka tersebut memerlukan akses ke cookie Twitter untuk mengaitkan tweet ke akun yang benar, dan memerlukan kemampuan untuk mengirimkan formulir tweet. Cukup begitu; frame tidak perlu memuat plugin apa pun, tidak perlu menavigasi jendela tingkat atas, atau sejumlah bit fungsi lainnya. Karena {i>frame<i} tidak memerlukan hak istimewa tersebut, mari kita hapus dengan melakukan {i>sandbox<i} terhadap konten {i>frame<i}.

Sandbox berfungsi berdasarkan daftar yang diizinkan. Kami mulai dengan menghapus semua izin yang memungkinkan, lalu mengaktifkan kembali setiap kemampuan dengan menambahkan tanda tertentu ke konfigurasi sandbox. Untuk widget Twitter, kami telah memutuskan untuk mengaktifkan JavaScript, pop-up, pengiriman formulir, dan cookie twitter.com. 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 semua kemampuan yang diperlukan kepada frame, dan browser akan membantu menolak aksesnya ke hak istimewa apa pun yang tidak secara eksplisit kita berikan melalui nilai atribut sandbox.

Kontrol Terperinci atas Kemampuan

Kita melihat beberapa kemungkinan flag sandbox dalam contoh di atas. Sekarang mari kita pelajari cara kerja bagian dalam atribut secara lebih mendetail.

Jika terdapat iframe dengan atribut sandbox kosong, dokumen berbingkai akan di-sandbox sepenuhnya, sehingga tunduk pada pembatasan berikut:

  • JavaScript tidak akan dieksekusi dalam dokumen berbingkai. Hal ini tidak hanya mencakup JavaScript yang dimuat secara eksplisit melalui tag skrip, tetapi juga pengendali peristiwa inline dan URL javascript:. Ini juga berarti bahwa konten yang terdapat dalam tag noscript akan ditampilkan, sama seperti meskipun pengguna telah menonaktifkan skrip sendiri.
  • Dokumen yang di-frame dimuat ke origin unik, yang berarti semua pemeriksaan origin yang sama akan gagal; origin unik tidak akan cocok dengan origin lain, bahkan bahkan asalnya sendiri. Di antara dampak lainnya, hal ini berarti bahwa dokumen tersebut tidak memiliki akses ke data yang disimpan dalam cookie asal atau mekanisme penyimpanan lainnya (penyimpanan DOM, DB Terindeks, dll.).
  • Dokumen berbingkai tidak dapat membuat jendela atau dialog baru (misalnya melalui window.open atau target="_blank").
  • Formulir tidak dapat dikirim.
  • Plugin tidak akan dimuat.
  • Dokumen berbingkai hanya dapat menavigasi dirinya 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, video yang diputar otomatis, dll.) akan diblokir.
  • Kunci pointer tidak dapat diperoleh.
  • Atribut seamless diabaikan pada iframes yang dimuat dokumen berbingkai.

Ini terlihat sangat rumit, dan dokumen yang dimuat ke iframe yang di-sandbox sepenuhnya memiliki risiko yang sangat kecil. Tentu saja, hal ini juga tidak banyak bermanfaat: Anda mungkin dapat menggunakan sandbox penuh untuk beberapa konten statis, tetapi sering kali Anda perlu sedikit melonggarkan.

Dengan pengecualian plugin, setiap batasan ini dapat dicabut dengan menambahkan flag ke nilai atribut sandbox. Dokumen dengan sandbox tidak pernah dapat menjalankan plugin, karena plugin adalah kode native yang tidak di-sandbox, tetapi hal lainnya bersifat wajar:

  • allow-forms mengizinkan pengiriman formulir.
  • allow-popups mengizinkan pop-up (kejutan!).
  • allow-pointer-lock memungkinkan (kejutan) kunci pointer.
  • allow-same-origin memungkinkan dokumen mempertahankan asalnya; halaman yang dimuat dari https://example.com/ akan mempertahankan akses ke data asal tersebut.
  • allow-scripts memungkinkan eksekusi JavaScript, dan juga memungkinkan fitur untuk dipicu secara otomatis (karena mudah diterapkan melalui JavaScript).
  • allow-top-navigation memungkinkan dokumen untuk keluar dari frame dengan membuka jendela tingkat atas.

Dengan mempertimbangkan hal ini, kita dapat mengevaluasi dengan tepat mengapa kita berakhir dengan kumpulan tanda sandbox spesifik pada contoh Twitter di atas:

  • allow-scripts diperlukan, karena halaman yang dimuat ke dalam frame menjalankan beberapa JavaScript untuk menangani interaksi pengguna.
  • allow-popups wajib ada, karena tombol akan memunculkan formulir tweet di jendela baru.
  • allow-forms diperlukan, karena formulir tweet harus dapat dikirim.
  • allow-same-origin diperlukan, karena cookie twitter.com tidak akan dapat diakses, dan pengguna tidak dapat login untuk memposting formulir.

Satu hal penting yang perlu diperhatikan adalah tanda sandbox yang diterapkan ke frame juga berlaku untuk setiap jendela atau frame yang dibuat di sandbox. Artinya, kita harus menambahkan allow-forms ke sandbox frame, meskipun formulir tersebut hanya ada di jendela tempat frame muncul.

Dengan adanya atribut sandbox, widget hanya akan mendapatkan izin yang diperlukan, dan kemampuan seperti plugin, navigasi atas, dan kunci pointer akan tetap diblokir. Kami telah mengurangi risiko penyematan widget, tanpa efek buruk. Ini adalah kemenangan bagi semua pihak yang berkepentingan.

Pemisahan Hak Istimewa

Dengan menggunakan sandbox konten pihak ketiga untuk menjalankan kode tidak tepercaya di lingkungan dengan hak istimewa rendah cukup bermanfaat. Tapi bagaimana dengan kode Anda sendiri? Kamu percaya pada dirimu sendiri, kan? Jadi mengapa harus khawatir tentang sandbox?

Saya akan membalikkan pertanyaan itu: jika kode Anda tidak memerlukan plugin, mengapa memberikan akses ke plugin? Yang paling buruknya, ini adalah keistimewaan yang tidak pernah Anda gunakan, paling buruknya ini adalah sebuah vektor yang potensial bagi penyerang untuk menginjakkan kaki di pintu. Kode setiap orang memiliki bug, dan hampir setiap aplikasi rentan terhadap eksploitasi dalam berbagai cara. Melakukan sandbox kode Anda sendiri berarti meskipun penyerang berhasil mengalihkan aplikasi Anda, mereka tidak akan diberi akses penuh ke asal aplikasi; mereka hanya dapat melakukan hal-hal yang dapat dilakukan aplikasi. Masih buruk, tetapi tidak seburuk yang seharusnya.

Anda dapat mengurangi risiko ini lebih jauh dengan memecah aplikasi menjadi bagian-bagian logis dan melakukan sandbox setiap bagian dengan hak istimewa minimal yang memungkinkan. Teknik ini sangat umum dalam kode native: Chrome, misalnya, memecah dirinya menjadi proses browser dengan hak istimewa tinggi yang memiliki akses ke hard drive lokal dan dapat membuat koneksi jaringan, serta banyak proses perender dengan hak istimewa rendah yang kesulitan mengurai konten yang tidak tepercaya. Perender tidak perlu menyentuh disk, browser akan memberinya semua informasi yang diperlukan untuk merender halaman. Meskipun peretas yang cerdas menemukan cara untuk merusak perender, dia belum sampai terlalu jauh, karena perender tidak dapat menanganinya sendiri: semua akses hak istimewa tinggi harus diarahkan melalui proses browser. Penyerang perlu menemukan beberapa lubang di berbagai bagian sistem untuk melakukan kerusakan, yang sangat mengurangi risiko keberhasilan pwnage.

Melakukan sandbox eval() dengan aman

Dengan sandbox dan postMessage API, keberhasilan model ini cukup mudah diterapkan pada web. Bagian aplikasi Anda dapat berada di iframe dengan sandbox, dan dokumen induk dapat memperantara komunikasi di antara keduanya dengan memposting pesan dan memproses respons. Struktur seperti ini memastikan bahwa eksploit di bagian mana pun pada aplikasi melakukan kerusakan seminimal mungkin. Hal ini juga memiliki keuntungan karena memaksa Anda untuk membuat titik integrasi yang jelas, sehingga Anda tahu persis di mana Anda perlu berhati-hati dalam memvalidasi input dan output. Mari kita lihat contoh mainan, untuk melihat cara kerjanya.

Evalbox adalah aplikasi menarik yang mengambil string dan mengevaluasinya sebagai JavaScript. Wah, bukan? Inilah yang sudah Anda tunggu selama bertahun-tahun. Tentu saja ini merupakan aplikasi yang cukup berbahaya karena mengizinkan JavaScript arbitrer dieksekusi artinya setiap dan semua data yang ditawarkan suatu origin akan diambil alih. Kami akan mengurangi risiko Bad ThingsTM akan terjadi dengan memastikan kode dieksekusi di dalam sandbox sehingga sedikit lebih aman. Kita akan mengerjakan kode tersebut dari dalam ke luar, 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 terpicu, sehingga memberi kita akses ke string yang akan dijalankan oleh induk.

Di pengendali, kita mengambil atribut source peristiwa, yang merupakan jendela induk. Kita akan menggunakannya untuk mengirimkan kembali hasil kerja keras kita setelah selesai. Kemudian, kita akan melakukan bagian pekerjaan yang sulit, dengan meneruskan data yang telah diberikan ke eval(). Panggilan ini telah diakhiri dalam blok coba, karena operasi yang diblokir di dalam iframe yang di-sandbox akan sering menghasilkan pengecualian DOM; kita akan menangkapnya dan melaporkan pesan error yang ramah. Terakhir, kita memposting hasilnya kembali ke jendela induk. Ini adalah hal yang cukup mudah.

Induknya juga tidak rumit. Kita akan membuat UI kecil dengan textarea untuk kode, dan button untuk eksekusi, lalu menarik frame.html melalui iframe yang di-sandbox, sehingga 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 menyambungkan semuanya untuk dieksekusi. Pertama, kita akan memproses respons dari iframe dan alert() kepada pengguna. Mungkin aplikasi sungguhan akan melakukan hal yang tidak terlalu menyebalkan:

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

Selanjutnya, kita akan menghubungkan pengendali peristiwa untuk mengklik pada button. Ketika pengguna mengklik, kita akan mengambil konten textarea saat ini, dan meneruskannya ke frame 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? Kami telah membuat API evaluasi yang sangat sederhana, dan kami dapat memastikan bahwa kode yang dievaluasi tidak memiliki akses ke informasi sensitif seperti cookie atau penyimpanan DOM. Demikian pula, kode yang dievaluasi tidak dapat memuat plugin, jendela pop-up baru, atau sejumlah aktivitas mengganggu atau berbahaya lainnya.

Anda dapat melakukan hal yang sama untuk kode Anda sendiri dengan memecah aplikasi monolitik menjadi komponen tujuan tunggal. Masing-masing dapat digabungkan dalam API pesan sederhana, seperti yang telah kami tulis di atas. Jendela induk dengan hak istimewa tinggi dapat bertindak sebagai pengontrol dan dispatcher, mengirim pesan ke modul tertentu yang masing-masing memiliki hak istimewa sesedikit mungkin untuk melakukan tugasnya, memproses hasil, dan memastikan bahwa setiap modul cukup diberi informasi yang diperlukan saja.

Namun, perhatikan bahwa Anda harus sangat berhati-hati saat menangani konten dengan frame yang berasal dari asal yang sama dengan induknya. Jika halaman di https://example.com/ membingkai halaman lain pada origin yang sama dengan sandbox yang menyertakan tanda allow-same-origin dan allow-scripts, halaman dengan frame dapat menjangkau induk, dan menghapus atribut sandbox sepenuhnya.

Mainkan di sandbox Anda

Sandboxing kini tersedia untuk Anda di berbagai browser: Firefox 17+, IE10+, dan Chrome pada saat penulisan (caniuse, tentu saja, memiliki tabel dukungan terbaru). Menerapkan atribut sandbox ke iframes yang Anda sertakan memungkinkan Anda memberikan hak istimewa tertentu untuk konten yang ditampilkan, hanya hak istimewa yang diperlukan agar konten berfungsi dengan benar. Hal ini memberi Anda peluang untuk mengurangi risiko terkait penyertaan konten pihak ketiga, melebihi apa yang sudah mungkin dilakukan dengan Kebijakan Keamanan Konten.

Selain itu, sandboxing adalah teknik ampuh untuk mengurangi risiko penyerang cerdas dapat mengeksploitasi lubang dalam kode Anda sendiri. Dengan memisahkan aplikasi monolitik ke dalam sekumpulan layanan dalam sandbox, yang masing-masing bertanggung jawab atas sebagian kecil fungsi mandiri, penyerang akan dipaksa untuk tidak hanya menyusupi konten frame tertentu, tetapi juga pengontrolnya. Ini tugas yang jauh lebih sulit, terutama karena cakupan pengontrol dapat dikurangi secara signifikan. Anda dapat menghabiskan upaya terkait keamanan untuk mengaudit kode tersebut jika Anda meminta bantuan browser untuk melakukan lainnya.

Hal ini bukan berarti bahwa sandbox adalah solusi lengkap untuk masalah keamanan di internet. Layanan ini menawarkan pertahanan mendalam, dan kecuali Anda memiliki kontrol atas klien pengguna Anda, Anda belum dapat mengandalkan dukungan browser untuk semua pengguna Anda (jika Anda mengontrol klien pengguna Anda -- lingkungan perusahaan, misalnya -- hore!). Suatu hari nanti... tetapi untuk saat ini, sandbox adalah lapisan perlindungan lain untuk memperkuat pertahanan Anda. Namun, sandbox bukanlah pertahanan menyeluruh yang dapat Anda andalkan. Namun, {i>layer<i} itu sangat baik. Saya sarankan untuk menggunakan fitur ini.

Bacaan Lebih Lanjut

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

  • Sandboxing dapat menjadi lebih fleksibel jika digabungkan dengan dua atribut iframe baru lainnya: srcdoc, dan seamless. Format pertama memungkinkan Anda mengisi frame dengan konten tanpa overhead permintaan HTTP, dan yang kedua memungkinkan gaya mengalir ke konten yang di-frame. Keduanya memiliki dukungan browser yang cukup buruk saat ini (malam Chrome dan WebKit). Namun, kombinasi keduanya akan menarik di masa mendatang. Misalnya, Anda dapat melakukan sandbox komentar pada 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>