Tentang mekanisme Bereaksi untuk mencegah kemungkinan injeksi JSON untuk XSS, dan tentang menghindari kerentanan umum.
Anda mungkin berpikir Anda sedang menulis JSX:
<marquee bgcolor="#ffa7c4">hi</marquee>
Tetapi sebenarnya Anda memanggil fungsi:
React.createElement( 'marquee', { bgcolor: '#ffa7c4' }, 'hi' )
Dan fungsi ini mengembalikan Anda objek biasa yang disebut elemen Bereaksi. Dengan demikian, setelah melintasi semua komponen, pohon benda serupa diperoleh:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
Jika Anda menggunakan Bereaksi sebelumnya, Anda mungkin terbiasa dengan jenis, properti, kunci, dan bidang ref. Tapi apa jenis properti properti $$typeof
? Dan mengapa ia memiliki simbol Symbol()
sebagai nilainya?
Sebelum pustaka UI menjadi populer, untuk menampilkan input klien dalam kode aplikasi, baris yang berisi markup HTML dibuat dan disisipkan langsung ke DOM, melalui innerHTML:
const messageEl = document.getElementById('message'); messageEl.innerHTML = '<p>' + message.text + '</p>';
Mekanisme ini berfungsi dengan baik kecuali message.text
diatur ke <img src onerror="stealYourPassword()">
. Karenanya, kami menyimpulkan bahwa Anda tidak perlu menginterpretasikan semua input klien sebagai markup HTML.
Untuk melindungi dari serangan semacam itu, Anda dapat menggunakan API aman, seperti document.createTextNode()
atau textContent
, yang tidak menafsirkan teks. Dan sebagai tindakan tambahan, lepas string dengan mengganti karakter yang berpotensi berbahaya seperti <
, >
dengan yang aman.
Namun demikian, probabilitas kesalahannya tinggi, karena sulit untuk melacak semua tempat di mana Anda menggunakan informasi yang direkam oleh pengguna di halaman Anda. Inilah sebabnya mengapa perpustakaan modern seperti Bereaksi bekerja dengan aman dengan teks default apa pun:
<p> {message.text} </p>
Jika message.text
adalah string berbahaya dengan <img>
, itu tidak akan berubah menjadi <img>
nyata. Bereaksi keluar dari konten teks dan kemudian menambahkannya ke DOM. Karena itu, alih-alih melihat <img>
, Anda cukup melihat markupnya sebagai string.
Untuk menampilkan HTML sewenang-wenang di dalam elemen Bereaksi, Anda harus menggunakan konstruksi berikut: dangerouslySetInnerHTML={{ __html: message.text }}
. Desainnya sengaja tidak nyaman. Karena absurditasnya, itu menjadi lebih terlihat, dan menarik perhatian ketika melihat kode.
Apakah ini berarti Bereaksi sepenuhnya aman? Tidak. Ada banyak metode serangan yang dikenal berdasarkan HTML dan DOM. Atribut tag patut mendapat perhatian khusus. Misalnya, jika Anda menulis <a href={user.website}>
, maka Anda dapat mengganti kode berbahaya: 'javascript: stealYourPassword()'
sebagai tautan teks.
Dalam kebanyakan kasus, keberadaan kerentanan di sisi klien adalah hasil dari masalah di sisi server, dan harus diperbaiki terlebih dahulu.
Namun, tampilan aman dari konten teks khusus adalah garis pertahanan pertama yang masuk akal yang mencerminkan banyak serangan potensial.
Berdasarkan pertimbangan sebelumnya, kita dapat menyimpulkan bahwa kode berikut harus benar-benar aman:
Tapi bukan itu masalahnya. Dan di sini kita lebih dekat untuk menjelaskan keberadaan $$typeof
di elemen Bereaksi.
Seperti yang kami jelaskan sebelumnya, Elemen Bereaksi adalah objek sederhana:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
Biasanya, elemen Bereaksi dibuat dengan memanggil fungsi React.createElement()
, tetapi Anda dapat membuatnya segera dengan literal, seperti yang baru saja saya lakukan di atas.
Misalkan kita menyimpan string di server yang sebelumnya dikirimkan pengguna kepada kami, dan setiap kali kami menampilkannya di sisi klien. Tetapi seseorang, bukannya string, mengirimi kami JSON:
let expectedTextButGotJSON = { type: 'div', props: { dangerouslySetInnerHTML: { __html: '/* */' }, },
Yaitu, tiba-tiba, alih-alih string yang diharapkan, nilai variabel expectedTextButGotJSON
berubah menjadi JSON. Yang akan diproses oleh Bereaksi sebagai literal, dan dengan demikian mengeksekusi kode berbahaya.
Bereaksi 0.13 rentan terhadap serangan seperti XSS, tetapi dimulai dengan versi 0.14, setiap elemen ditandai dengan simbol:
{ type: 'marquee', props: { bgcolor: '#ffa7c4', children: 'hi', }, key: null, ref: null, $$typeof: Symbol.for('react.element'), }
Perlindungan seperti itu berfungsi karena karakter bukan nilai JSON yang valid. Oleh karena itu, bahkan jika server memiliki potensi kerentanan dan mengembalikan JSON sebagai ganti teks, JSON tidak dapat memuat Symbol.for('response.element')
. Bereaksi memeriksa elemen untuk element.$$typeof
dan menolak untuk memproses elemen jika elemen tersebut hilang atau tidak valid.
Keuntungan utama Symbol.for()
adalah bahwa simbol bersifat global di antara konteks karena mereka menggunakan registri global. Ini memastikan nilai pengembalian yang sama bahkan dalam iframe. Dan bahkan jika ada beberapa salinan Bereaksi pada halaman, mereka masih akan dapat "mencocokkan" melalui nilai tunggal $$typeof
.
Bagaimana dengan browser yang tidak mendukung karakter?
Sayangnya, mereka tidak akan dapat menerapkan perlindungan tambahan yang dibahas di atas, tetapi elemen Bereaksi masih akan berisi properti $$typeof
untuk konsistensi, tetapi itu hanya berupa angka - 0xeac7
.
Kenapa tepatnya 0xeac7
? Karena sepertinya Bereaksi.