JSON Toolkit

Bagaimana XSS berlaku melalui JSON

Inti pati: JSON tidak pernah melaksanakan dengan sendirinya. XSS berlaku pada saat anda menganggapnya sebagai HTML — penggabungan rentetan di pelayan, innerHTML di frontend, atau membenamkan JSON terus ke dalam tag skrip.

Pertengahan~4 min bacaanDiterbitkan 2026-10-30Dikemas kini 2026-10-30
Tiga cara JSON menjadi XSS dan cara mencegahnya

Pokoknya: JSON tidak pernah melaksanakan dengan sendirinya — tutup tiga laluan output dahulu: escape di pelayan mengikut konteks, guna textContent dan bukan innerHTML, dan ganti < dengan \u003c apabila membenamkan JSON.

{"name": "<img src=x onerror=alert(1)>"} ialah data yang sah. Sama ada ia berbahaya bergantung sepenuhnya pada tempat anda meletakkannya.

Titik masuk satu: pembinaan rentetan di pelayan

res.send(`<div>Hello, ${data.name}</div>`);

Pelayar menghuraikan tag di dalam data.name. JSON di sini hanyalah bekas; kesalahannya ialah escape untuk konteks yang salah.

Pembetulan: guna escape lalai enjin templat (<%= %> dalam EJS, {{ }} dalam Vue dan Django). Elakkan <%- %>, v-html dan |safe untuk apa-apa yang dikawal pengguna.

Titik masuk dua: innerHTML

el.innerHTML = user.bio;    // menghuraikan markup
el.textContent = user.bio;  // memasukkan teks

{user.bio} dalam React dan {{ user.bio }} dalam Vue mencipta nod teks dan selamat secara lalai. dangerouslySetInnerHTML dan v-html ialah penolakan eksplisit.

Jika anda benar-benar memerlukan teks kaya, sanitasi dengan senarai putih (DOMPurify dan seumpamanya) dan bukannya mempercayai backend.

Titik masuk tiga: membenamkan ke dalam tag skrip

<script>
  window.__DATA__ = {"name": "x</script><script>alert(1)</script>"};
</script>

</script> di dalam data menutup blok lebih awal dan segala-galanya selepasnya berjalan sebagai skrip baharu. Ini ialah kes yang paling selalu terlepas.

JSON.stringify(data)
  .replace(/</g, '\\u003c')
  .replace(/>/g, '\\u003e')
  .replace(/&/g, '\\u0026');

Masih JSON yang sah, serupa selepas penghuraian, tidak lagi dapat menutup tag. Next.js dan Nuxt melakukan perkara yang sama untuk __NEXT_DATA__ dan __NUXT__.

Dua jiran yang patut diketahui

Pembajakan JSON — pelayar lama melaksanakan JSON yang dimuatkan melalui <script src>; mentakrif semula Array membocorkan kandungannya. Pelayar moden telah membetulkannya, dan Content-Type: application/json serta X-Content-Type-Options: nosniff menutup selebihnya.

Sisa JSONP — nama panggil balik yang dikawal pengguna ialah pelaksanaan kod sewenang-wenangnya secara reka bentuk. Jangan gunakannya dalam sistem baharu.

Senarai semak

  • Setiap tempat medan JSON sampai ke HTML di-escape mengikut konteksnya (HTML, atribut, JS, URL)
  • Tiada innerHTML, v-html atau dangerouslySetInnerHTML yang menggunakan rentetan API
  • JSON yang dibenamkan me-escape <, > dan &
  • Teks kaya melalui sanitasi senarai putih, bukan penapisan senarai hitam
  • Respons membawa Content-Type: application/json dan nosniff

Soalan Lazim

Mengapa JSON yang sah masih boleh mencetuskan XSS?

JSON hanyalah teks dan tidak pernah melaksanakan; menjadi sah hanya bermaksud ia dihuraikan. Bahaya muncul apabila ia ditulis ke dalam konteks HTML atau skrip: pelayar menghuraikan tag, atau </script> menutup blok lebih awal dan selebihnya berjalan sebagai kod. Kesalahannya ialah escape sedar konteks yang hilang semasa output, bukan format data.

Patutkah saya guna textContent atau innerHTML untuk memaparkan medan API?

Utamakan textContent: ia memasukkan nilai sebagai teks biasa, jadi tag dipaparkan secara literal. innerHTML menghuraikan markup, jadi medan API yang mengandungi <img onerror=...> melaksanakan. {user.bio} dalam React dan {{ user.bio }} dalam Vue mencipta nod teks secara lalai; dangerouslySetInnerHTML dan v-html ialah penolakan eksplisit.

Bagaimana saya harus me-escape JSON yang dibenamkan ke dalam tag <script>?

Ya. </script> di dalam data menutup blok lebih awal dan segala-galanya selepasnya berjalan sebagai skrip baharu. Selepas mensirikan, ganti <, > dan & dengan \u003c, \u003e dan \u0026: hasilnya masih JSON yang sah, dihuraikan secara serupa, dan tidak lagi dapat menutup tag.

Bacaan lanjut