Membuat pengalaman yang kaya di web saat ini hampir tidak dapat dihindari melibatkan penyematan komponen dan konten yang tidak dapat Anda kontrol sepenuhnya. Widget pihak ketiga dapat mendorong interaksi dan berperan penting dalam pengalaman pengguna secara keseluruhan, dan konten buatan pengguna terkadang bahkan lebih penting daripada konten native situs. Tidak menggunakan keduanya bukanlah suatu opsi, tetapi keduanya meningkatkan risiko terjadinya Hal Buruk™ di situs Anda. Setiap widget yang Anda sematkan -- setiap iklan, setiap widget media sosial -- adalah potensi vektor serangan 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 sumber skrip dan konten lain yang tepercaya secara khusus. Ini adalah langkah besar ke arah yang benar, tetapi perlu diperhatikan bahwa perlindungan yang ditawarkan sebagian besar perintah CSP bersifat biner: resource diizinkan, atau tidak. Terkadang, akan lebih baik jika Anda mengatakan "Saya tidak yakin saya benar-benar mempercayai sumber konten ini, tetapi kontennya sangat bagus. Sebaiknya browser, sematkan, tetapi jangan sampai merusak situs saya."
Hak Istimewa Terendah
Pada dasarnya, kita mencari mekanisme yang akan memungkinkan kita memberikan konten yang disematkan hanya tingkat kemampuan minimum yang diperlukan untuk melakukan tugasnya. Jika widget tidak perlu memunculkan jendela baru, menghapus akses ke window.open tidak akan bermasalah. Jika tidak memerlukan Flash, menonaktifkan dukungan plugin seharusnya tidak menjadi masalah. Kami akan semaksimal mungkin aman jika mengikuti prinsip hak istimewa terendah, dan memblokir setiap fitur yang tidak relevan secara langsung dengan fungsi yang ingin kami gunakan. Hasilnya, kami tidak lagi harus percaya secara membabi buta bahwa beberapa bagian konten tersemat tidak akan memanfaatkan hak istimewa yang tidak boleh digunakan. Aplikasi tersebut tidak akan memiliki akses ke fungsi tersebut sejak awal.
Elemen iframe
adalah langkah pertama menuju framework yang baik untuk solusi tersebut.
Memuat beberapa komponen yang tidak tepercaya di iframe
memberikan ukuran pemisahan
antara aplikasi dan konten yang ingin Anda muat. Konten dalam bingkai
tidak akan memiliki akses ke DOM halaman, atau data yang Anda simpan secara lokal, dan juga tidak akan
dapat menggambar ke posisi arbitrer di halaman; cakupannya terbatas pada
garis batas bingkai. Namun, pemisahan ini tidak benar-benar andal. Halaman yang dimuat
masih memiliki sejumlah opsi untuk perilaku yang mengganggu atau berbahaya: video, plugin, dan pop-up yang diputar otomatis adalah puncak gunung es.
Atribut sandbox
dari elemen iframe
memberi kita hal yang diperlukan untuk memperketat batasan pada konten berbingkai. Kita dapat
menginstruksikan browser untuk memuat konten frame tertentu di lingkungan
hak istimewa rendah, yang hanya mengizinkan subset kemampuan yang diperlukan untuk melakukan
pekerjaan apa pun yang perlu dilakukan.
Percaya, tetapi verifikasi
Tombol "Tweet" Twitter adalah contoh bagus fungsi yang dapat disematkan dengan lebih aman di situs Anda melalui sandbox. Twitter memungkinkan Anda menyisipkan 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 diperlukan tombol. HTML yang dimuat ke dalam frame menjalankan sedikit JavaScript dari server Twitter, dan menghasilkan pop-up yang diisi dengan antarmuka tweet saat diklik. Antarmuka tersebut memerlukan akses ke cookie Twitter untuk mengaitkan tweet ke akun yang benar, dan memerlukan kemampuan untuk mengirimkan formulir tweet. Hanya itu saja; frame tidak perlu memuat plugin apa pun, tidak perlu menavigasi jendela tingkat atas, atau sejumlah fungsi lainnya. Karena tidak memerlukan hak istimewa tersebut, mari kita hapus dengan membuat sandbox konten frame.
Sandboxing berfungsi berdasarkan daftar yang diizinkan. Kita mulai dengan menghapus semua izin yang memungkinkan, lalu mengaktifkan kembali setiap kemampuan dengan menambahkan flag 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. Kami telah memberikan semua kemampuan yang diperlukan ke frame, dan browser akan menolak aksesnya ke hak istimewa yang tidak kami berikan secara eksplisit melalui nilai atribut sandbox
.
Kontrol Terperinci atas Kemampuan
Kita telah melihat beberapa kemungkinan flag sandboxing dalam contoh di atas, sekarang mari kita pelajari cara kerja atribut secara lebih mendetail.
Dengan iframe yang memiliki atribut sandbox kosong, dokumen berbingkai akan sepenuhnya di-sandbox, sehingga dokumen tersebut akan dikenai batasan 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, persis seperti pengguna telah menonaktifkan skrip sendiri.
- Dokumen berbingkai dimuat ke dalam origin unik, yang berarti semua pemeriksaan origin yang sama akan gagal; origin unik tidak cocok dengan origin lain, bahkan dengan dirinya sendiri. Di antara dampak lainnya, hal ini berarti bahwa dokumen tidak memiliki akses ke data yang disimpan dalam cookie asal atau mekanisme penyimpanan lainnya (penyimpanan DOM, DB Terindek, dll.).
- Dokumen berbingkai tidak dapat membuat jendela atau dialog baru (misalnya, melalui
window.open
atautarget="_blank"
). - Formulir tidak dapat dikirim.
- Plugin tidak akan dimuat.
- Dokumen berbingkai hanya dapat menavigasi dirinya sendiri, bukan induk tingkat atasnya.
Menetapkan
window.top.location
akan menampilkan pengecualian, dan mengklik link dengantarget="_top"
tidak akan berpengaruh. - Fitur yang dipicu secara otomatis (elemen formulir yang difokuskan secara otomatis, video yang diputar secara otomatis, dsb.) akan diblokir.
- Kunci kursor tidak dapat diperoleh.
- Atribut
seamless
diabaikan padaiframes
yang berisi dokumen berbingkai.
Ini sangat ketat, dan dokumen yang dimuat ke dalam iframe
dengan sandbox penuh
memang sangat sedikit berisiko. Tentu saja, sandbox juga tidak dapat melakukan banyak hal yang bernilai: Anda
mungkin dapat menggunakan sandbox penuh untuk beberapa konten statis, tetapi sebagian besar
waktu Anda ingin sedikit melonggarkan hal-hal.
Dengan pengecualian plugin, setiap batasan ini dapat dicabut dengan menambahkan tanda ke nilai atribut sandbox. Dokumen dengan sandbox tidak akan pernah dapat menjalankan plugin, karena plugin adalah kode native tanpa sandbox, tetapi yang lainnya adalah game yang adil:
allow-forms
memungkinkan pengiriman formulir.allow-popups
mengizinkan pop-up.allow-pointer-lock
memungkinkan (kejutan!) penguncian kursor.allow-same-origin
memungkinkan dokumen mempertahankan asalnya; halaman yang dimuat darihttps://example.com/
akan mempertahankan akses ke data asal tersebut.allow-scripts
memungkinkan eksekusi JavaScript, dan juga memungkinkan fitur dipicu secara otomatis (karena mudah diterapkan melalui JavaScript).allow-top-navigation
memungkinkan dokumen keluar dari bingkai dengan membuka jendela tingkat atas.
Dengan mempertimbangkan hal ini, kita dapat mengevaluasi dengan tepat mengapa kita mendapatkan kumpulan flag sandboxing tertentu dalam 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 ini 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 sandboxing yang diterapkan ke frame juga
berlaku untuk jendela atau frame apa pun yang dibuat di sandbox. Artinya, kita harus
menambahkan allow-forms
ke sandbox bingkai, meskipun formulir hanya ada
di jendela tempat bingkai muncul.
Dengan atribut sandbox
yang diterapkan, widget hanya mendapatkan izin yang
diperlukan, dan kemampuan seperti plugin, navigasi atas, dan kunci pointer tetap
diblokir. Kami telah mengurangi risiko penyematan widget, tanpa efek buruk.
Semua pihak akan diuntungkan.
Pemisahan Hak Istimewa
Men-sandbox konten pihak ketiga untuk menjalankan kode yang tidak tepercaya di lingkungan dengan hak istimewa rendah jelas bermanfaat. Namun, bagaimana dengan kode Anda sendiri? Anda percaya diri, bukan? Jadi, mengapa harus khawatir dengan sandboxing?
Saya akan membalikkan pertanyaan tersebut: jika kode Anda tidak memerlukan plugin, mengapa memberinya akses ke plugin? Pada kondisi terbaik, ini adalah hak istimewa yang tidak pernah Anda gunakan, dan pada kondisi terburuk, ini adalah vektor potensial bagi penyerang untuk masuk. Kode semua orang memiliki bug, dan hampir setiap aplikasi rentan terhadap eksploitasi dengan cara tertentu. Dengan menerapkan sandbox pada kode Anda sendiri, meskipun penyerang berhasil menyusup ke 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 mungkin terjadi.
Anda dapat mengurangi risiko lebih jauh dengan membagi aplikasi menjadi bagian-bagian logis dan membuat sandbox untuk setiap bagian dengan hak istimewa minimal. Teknik ini sangat umum dalam kode native: Chrome, misalnya, membagi 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 melakukan tugas berat dalam mengurai konten yang tidak tepercaya. Perender tidak perlu menyentuh disk, browser akan memberikan semua informasi yang diperlukan untuk merender halaman. Meskipun peretas cerdas menemukan cara untuk merusak perender, dia belum berhasil, karena perender tidak dapat melakukan banyak hal yang menarik dengan sendirinya: semua akses dengan hak istimewa tinggi harus dirutekan melalui proses browser. Penyerang harus menemukan beberapa celah di berbagai bagian sistem agar dapat melakukan kerusakan, yang sangat mengurangi risiko keberhasilan pwnage.
Men-sandbox eval()
dengan aman
Dengan sandboxing dan
postMessage
API, keberhasilan model ini cukup mudah diterapkan ke web. Bagian
aplikasi Anda dapat berada di iframe
dengan sandbox, dan dokumen induk dapat
melakukan mediasi komunikasi di antara keduanya dengan memposting pesan dan memproses
respons. Struktur semacam ini memastikan bahwa eksploitasi di salah satu bagian
aplikasi melakukan kerusakan minimum. Hal ini juga memiliki keuntungan untuk memaksa Anda
membuat titik integrasi yang jelas, sehingga Anda tahu persis di mana Anda harus
berhati-hati dalam memvalidasi input dan output. Mari kita pelajari contoh mainan,
hanya untuk melihat cara kerjanya.
Evalbox adalah aplikasi menarik yang menggunakan string, dan mengevaluasinya sebagai JavaScript. Wow, bukan? Tepat seperti yang Anda tunggu selama bertahun-tahun. Tentu saja, ini adalah aplikasi yang cukup berbahaya, karena mengizinkan JavaScript arbitrer untuk dieksekusi berarti bahwa semua data yang ditawarkan origin dapat diambil. Kita akan memitigasi risiko Bad Things™ yang terjadi dengan memastikan bahwa kode dieksekusi di dalam sandbox, sehingga membuatnya jauh lebih aman. Kita akan mempelajari kode dari dalam ke luar, dimulai dengan konten frame:
<!-- 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 menghubungkan ke peristiwa message
objek window
.
Setiap kali induk mengeksekusi postMessage pada konten iframe, peristiwa ini
akan terpicu, sehingga memberi kita akses ke string yang ingin dijalankan oleh induk.
Di pengendali, kita mengambil atribut source
peristiwa, yang merupakan jendela induk. Kita akan menggunakannya untuk mengirim hasil kerja keras kita setelah selesai. Kemudian, kita akan melakukan pekerjaan berat, dengan meneruskan data yang telah diberikan ke
eval()
. Panggilan ini telah digabungkan dalam blok try, karena operasi yang dilarang
di dalam iframe
dengan sandbox akan sering menghasilkan pengecualian DOM; kita akan menangkapnya
dan melaporkan pesan error yang mudah dipahami. Terakhir, kita memposting hasil
kembali ke jendela induk. Hal ini cukup mudah.
Induk juga tidak rumit. Kita akan membuat UI kecil dengan textarea
untuk kode, dan button
untuk eksekusi, dan kita akan mengambil frame.html
melalui
iframe
dengan sandbox, yang hanya mengizinkan 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 menghubungkan semuanya untuk dieksekusi. Pertama, kita akan memproses respons dari
iframe
dan alert()
ke pengguna. Mungkin aplikasi sebenarnya
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" && e.source === frame.contentWindow)
alert('Result: ' + e.data);
});
Selanjutnya, kita akan menghubungkan pengendali peristiwa ke klik pada button
. Saat 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, memunculkan jendela baru, atau sejumlah aktivitas menjengkelkan 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 kita 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 diberi informasi yang diperlukan dengan baik.
Namun, perhatikan bahwa Anda harus sangat berhati-hati saat menangani konten berbingkai
yang berasal dari asal yang sama dengan induk. Jika halaman di
https://example.com/
membingkai halaman lain di origin yang sama dengan sandbox
yang menyertakan flag allow-same-origin dan allow-scripts, maka
halaman yang dibingkai dapat menjangkau induk, dan menghapus atribut sandbox
sepenuhnya.
Bermain di sandbox
Sandboxing kini tersedia untuk Anda di berbagai browser: Firefox 17+,
IE10+, dan Chrome pada saat penulisan (caniuse, tentu saja, memiliki tabel dukungan
terbaru). Dengan menerapkan atribut sandbox
ke iframes
yang Anda sertakan, Anda dapat memberikan hak istimewa tertentu ke
konten yang ditampilkannya, hanya hak istimewa yang diperlukan agar
konten berfungsi dengan benar. Hal ini memberi Anda peluang untuk mengurangi risiko
yang terkait dengan penyertaan konten pihak ketiga, di atas dan di luar yang
sudah dapat dilakukan dengan Kebijakan
Keamanan Konten.
Selain itu, sandboxing adalah teknik yang efektif untuk mengurangi risiko penyerang pintar yang dapat mengeksploitasi celah dalam kode Anda sendiri. Dengan memisahkan aplikasi monolitik menjadi serangkaian layanan dengan sandbox, yang masing-masing bertanggung jawab atas sebagian kecil fungsi mandiri, penyerang akan dipaksa untuk tidak hanya membahayakan konten frame tertentu, tetapi juga pengontrolnya. Itu adalah tugas yang jauh lebih sulit, terutama karena cakupan pengontrol dapat dikurangi secara signifikan. Anda dapat menghabiskan upaya terkait keamanan untuk mengaudit kode tersebut jika meminta bantuan browser untuk hal lainnya.
Namun, bukan berarti sandboxing adalah solusi lengkap untuk masalah keamanan di internet. Fitur ini menawarkan pertahanan menyeluruh, dan kecuali jika Anda memiliki kontrol atas klien pengguna, Anda belum dapat mengandalkan dukungan browser untuk semua pengguna (jika Anda mengontrol klien pengguna -- misalnya, lingkungan perusahaan -- selamat!). Suatu hari nanti… tetapi untuk saat ini, sandboxing adalah lapisan perlindungan lain untuk memperkuat pertahanan Anda, bukan pertahanan lengkap yang dapat Anda andalkan sepenuhnya. Namun, lapisan sangat bagus. Sebaiknya gunakan kode ini.
Bacaan Lebih Lanjut
"Pemisahan Hak Istimewa dalam Aplikasi HTML5" adalah makalah menarik yang membahas desain framework kecil, dan penerapannya ke tiga aplikasi HTML5 yang ada.
Sandboxing dapat menjadi lebih fleksibel jika digabungkan dengan dua atribut iframe baru lainnya:
srcdoc
, danseamless
. Yang pertama memungkinkan Anda mengisi frame dengan konten tanpa overhead permintaan HTTP, dan yang kedua memungkinkan gaya mengalir ke konten berbingkai. Keduanya memiliki dukungan browser yang cukup buruk saat ini (Chrome dan WebKit nightly). Namun, keduanya akan menjadi kombinasi yang menarik di masa mendatang. Misalnya, Anda dapat menempatkan komentar dalam 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>