Sanitizer API baru bertujuan membuat prosesor yang andal untuk string arbitrer agar dapat disisipkan dengan aman ke dalam halaman.
Aplikasi menangani string yang tidak tepercaya sepanjang waktu, namun merender konten tersebut dengan aman sebagai bagian dari dokumen HTML bisa menjadi hal yang rumit. Tanpa perhatian yang memadai, Anda dapat dengan mudah menciptakan peluang untuk pembuatan skrip lintas situs (XSS) yang dapat dieksploitasi oleh penyerang berbahaya secara tidak sengaja.
Untuk mengurangi risiko tersebut, proposal Sanitizer API yang baru bertujuan untuk membuat pemroses yang andal agar string arbitrer disisipkan dengan aman ke dalam halaman. Artikel ini memperkenalkan API ini dan menjelaskan penggunaannya.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
Meng-escape input pengguna
Saat menyisipkan input pengguna, string kueri, konten cookie, dan sebagainya, ke dalam DOM, string harus di-escape dengan benar. Perhatian khusus harus diberikan pada manipulasi DOM melalui .innerHTML
, dengan string yang tidak di-escape adalah sumber XSS standar.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input
Jika Anda meng-escape karakter khusus HTML dalam string input di atas atau meluaskannya menggunakan .textContent
, alert(0)
tidak akan dieksekusi. Namun, karena <em>
yang ditambahkan oleh pengguna juga diperluas sebagai string, metode ini tidak dapat digunakan untuk mempertahankan dekorasi teks di HTML.
Hal terbaik yang harus dilakukan di sini adalah bukan melakukan escape, melainkan membersihkan.
Membersihkan input pengguna
Perbedaan antara proses escaping dan sanitasi
Escaping mengacu pada penggantian karakter HTML khusus dengan Entitas HTML.
Sanitasi mengacu pada penghapusan bagian yang berbahaya secara semantik (seperti eksekusi skrip) dari string HTML.
Contoh
Pada contoh sebelumnya, <img onerror>
menyebabkan pengendali error dieksekusi, tetapi jika pengendali onerror
dihapus, Anda dapat memperluasnya dengan aman di DOM tanpa mengubah <em>
.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`
Untuk membersihkan dengan benar, Anda perlu mengurai string input sebagai HTML, menghilangkan tag dan atribut yang dianggap berbahaya, dan mempertahankan tag dan atribut yang tidak berbahaya.
Spesifikasi Sanitizer API yang diusulkan bertujuan untuk menyediakan pemrosesan semacam itu seperti API standar untuk browser.
API Sanitizer
Sanitizer API digunakan dengan cara berikut:
const $div = document.querySelector('div')
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.setHTML(user_input, { sanitizer: new Sanitizer() }) // <div><em>hello world</em><img src=""></div>
Namun, { sanitizer: new Sanitizer() }
adalah argumen default. Jadi bisa seperti di bawah ini.
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
Perlu diperhatikan bahwa setHTML()
ditentukan di Element
. Sebagai metode Element
, konteks yang harus diurai sudah jelas (dalam hal ini <div>
), penguraian dilakukan sekali secara internal, dan hasilnya langsung diperluas ke DOM.
Untuk mendapatkan hasil sanitasi sebagai string, Anda dapat menggunakan .innerHTML
dari hasil setHTML()
.
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">
Menyesuaikan melalui konfigurasi
Sanitizer API dikonfigurasi secara default untuk menghapus string yang akan memicu eksekusi skrip. Namun, Anda juga dapat menambahkan penyesuaian Anda sendiri ke proses sanitasi melalui objek konfigurasi.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
Opsi berikut menentukan cara hasil sanitasi memperlakukan elemen yang ditentukan.
allowElements
: Nama elemen yang harus dipertahankan oleh pembersih.
blockElements
: Nama elemen yang harus dihapus pembersih udara, sambil mempertahankan anak-anak mereka.
dropElements
: Nama elemen yang harus dihapus pembersih udara, bersama anak-anaknya.
const str = `hello <b><i>world</i></b>`
$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <div>hello <b>world</b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <div>hello <i>world</i></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: []}) })
// <div>hello world</div>
Anda juga dapat mengontrol apakah sanitizer akan mengizinkan atau menolak atribut yang ditentukan dengan opsi berikut:
allowAttributes
dropAttributes
Properti allowAttributes
dan dropAttributes
memerlukan daftar pencocokan atribut, yaitu objek yang kuncinya adalah nama atribut, dan nilainya adalah daftar elemen target atau karakter pengganti *
.
const str = `<span id=foo class=bar style="color: red">hello</span>`
$div.setHTML(str)
// <div><span id="foo" class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <div><span>hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <div><span class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <div>hello</div>
allowCustomElements
adalah opsi untuk mengizinkan atau menolak elemen kustom. Jika diizinkan, konfigurasi lain untuk elemen dan atribut tetap berlaku.
const str = `<custom-elem>hello</custom-elem>`
$div.setHTML(str)
// <div></div>
const sanitizer = new Sanitizer({
allowCustomElements: true,
allowElements: ["div", "custom-elem"]
})
$div.setHTML(str, { sanitizer })
// <div><custom-elem>hello</custom-elem></div>
Platform API
Perbandingan dengan DomPurify
DOMPurify adalah library terkenal yang menawarkan fungsi sanitasi. Perbedaan utama antara Sanitizer API dan DOMPurify adalah DOMPurify menampilkan hasil sanitasi sebagai string, yang harus Anda tulis ke dalam elemen DOM melalui .innerHTML
.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sanitized
// `<em>hello world</em><img src="">`
DOMPurify dapat berfungsi sebagai fallback saat Sanitizer API tidak diimplementasikan di browser.
Implementasi DOMPurify memiliki
beberapa kekurangan. Jika string ditampilkan, maka string input akan diuraikan dua kali, oleh DOMPurify dan .innerHTML
. Penguraian ganda ini menyia-nyiakan waktu pemrosesan, tetapi juga dapat menyebabkan kerentanan menarik yang disebabkan oleh kasus saat hasil penguraian kedua berbeda dengan yang pertama.
HTML juga memerlukan konteks untuk diurai. Misalnya, <td>
dapat digunakan di <table>
, tetapi tidak dalam <div>
. Karena DOMPurify.sanitize()
hanya mengambil string sebagai argumen, konteks penguraian harus dapat ditebak.
Sanitizer API meningkatkan pendekatan DOMPurify dan dirancang untuk menghilangkan kebutuhan akan penguraian ganda dan untuk memperjelas konteks penguraian.
Status API dan dukungan browser
Sanitizer API sedang dibahas dalam proses standardisasi dan Chrome sedang dalam proses menerapkannya.
Langkah | Status |
---|---|
1. Buat penjelasan | Selesai |
2. Buat draf spesifikasi | Selesai |
3. Mengumpulkan masukan dan melakukan iterasi desain | Selesai |
4. Uji coba origin Chrome | Selesai |
5. Luncurkan | Rencana Akan Dikirimkan pada M105 |
Mozilla: Mempertimbangkan proposal ini layak untuk dibuat prototipe, dan secara aktif menerapkannya.
WebKit: Lihat respons di milis WebKit.
Cara mengaktifkan Sanitizer API
Mengaktifkan melalui opsi CLI atau about://flags
Chrome
Chrome sedang dalam proses menerapkan Sanitizer API. Di Chrome 93 atau yang lebih baru, Anda dapat mencoba perilaku tersebut dengan mengaktifkan tanda about://flags/#enable-experimental-web-platform-features
. Di saluran Chrome Canary dan Dev versi sebelumnya, Anda dapat mengaktifkannya melalui --enable-blink-features=SanitizerAPI
dan mencobanya sekarang. Lihat petunjuk cara menjalankan Chrome dengan tanda.
Firefox
Firefox juga mengimplementasikan Sanitizer API sebagai fitur eksperimental. Untuk mengaktifkannya, setel tanda dom.security.sanitizer.enabled
ke true
di about:config
.
Deteksi fitur
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Masukan
Jika Anda mencoba API ini dan memiliki masukan, kami akan senang mendengarnya. Sampaikan pendapat Anda tentang masalah GitHub Sanitizer API dan diskusikan dengan penulis spesifikasi dan orang-orang yang tertarik dengan API ini.
Jika Anda menemukan bug atau perilaku yang tidak terduga dalam implementasi Chrome, laporkan bug untuk melaporkannya. Pilih komponen Blink>SecurityFeature>SanitizerAPI
dan bagikan detail untuk membantu pengimplementasi melacak masalah.
Demo
Untuk melihat cara kerja Sanitizer API, lihat Sanitizer API Playground oleh Mike West:
Referensi
- Spesifikasi HTML Sanitizer API
- Repo WICG/sanitizer-api
- FAQ Sanitizer API
- Dokumentasi referensi HTML Sanitizer API di MDN
Foto oleh Towfiqu barbhuiya di Unsplash.