JSON Toolkit

Bagaimana XSS terjadi melalui JSON

Intinya: JSON tidak pernah dieksekusi dengan sendirinya. XSS terjadi saat Anda memperlakukannya sebagai HTML — konkatenasi string di server, innerHTML di frontend, atau menyematkan JSON langsung ke tag script.

Menengah±4 menit bacaDiterbitkan 2026-10-30Diperbarui 2026-10-30
Tiga cara JSON menjadi XSS dan cara mencegahnya

Intinya: JSON tidak pernah dieksekusi sendiri — tutup dulu tiga jalur output: escape di server sesuai konteks, pakai textContent alih-alih innerHTML, dan ganti < dengan \u003c saat menyematkan JSON.

{"name": "<img src=x onerror=alert(1)>"} adalah data yang valid. Apakah berbahaya sepenuhnya bergantung pada di mana Anda menaruhnya.

Titik masuk satu: membangun string di server

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

Browser mem-parsing tag di dalam data.name. JSON di sini hanyalah wadah; kesalahannya adalah escaping untuk konteks yang salah.

Perbaikan: pakai escaping default templating engine (<%= %> di EJS, {{ }} di Vue dan Django). Hindari <%- %>, v-html dan |safe untuk apa pun yang dikendalikan pengguna.

Titik masuk dua: innerHTML

el.innerHTML = user.bio;    // parses markup
el.textContent = user.bio;  // inserts text

{user.bio} di React dan {{ user.bio }} di Vue membuat text node dan aman secara default. dangerouslySetInnerHTML dan v-html adalah opt-out eksplisit.

Jika Anda benar-benar butuh rich text, sanitasi dengan allowlist (DOMPurify dan sejenisnya) alih-alih memercayai backend.

Titik masuk tiga: menyematkan ke tag script

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

</script> di dalam data menutup blok lebih awal dan semua yang setelahnya berjalan sebagai skrip baru. Ini kasus yang paling sering terlewat.

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

Masih JSON valid, identik setelah parsing, tidak lagi bisa menutup tag. Next.js dan Nuxt melakukan hal yang sama untuk __NEXT_DATA__ dan __NUXT__.

Dua tetangga yang perlu diketahui

JSON hijacking — browser lama mengeksekusi JSON yang dimuat melalui <script src>; mendefinisikan ulang Array membocorkan isinya. Browser modern telah memperbaikinya, dan Content-Type: application/json plus X-Content-Type-Options: nosniff menutup sisanya.

Sisa-sisa JSONP — nama callback yang dikendalikan pengguna berarti eksekusi kode arbitrer secara desain. Jangan memakainya di sistem baru.

Checklist

  • Setiap tempat field JSON mencapai HTML di-escape sesuai konteksnya (HTML, atribut, JS, URL)
  • Tidak ada innerHTML, v-html atau dangerouslySetInnerHTML yang mengonsumsi string API
  • JSON yang disematkan meng-escape <, > dan &
  • Rich text melewati sanitasi allowlist, bukan penyaringan blacklist
  • Response membawa Content-Type: application/json dan nosniff

FAQ

Mengapa JSON yang valid masih bisa memicu XSS?

JSON hanyalah teks dan tidak pernah dieksekusi; valid hanya berarti ia bisa di-parse. Bahaya muncul ketika ia ditulis ke konteks HTML atau script: browser mem-parsing tag, atau </script> menutup blok lebih awal dan sisanya berjalan sebagai kode. Kesalahannya adalah tidak adanya escaping yang sesuai konteks saat output, bukan format datanya.

Haruskah saya memakai textContent atau innerHTML untuk merender field API?

Utamakan textContent: ia menyisipkan nilai sebagai teks biasa, sehingga tag ditampilkan apa adanya. innerHTML mem-parsing markup, sehingga field API yang berisi <img onerror=...> akan dieksekusi. {user.bio} di React dan {{ user.bio }} di Vue membuat text node secara default; dangerouslySetInnerHTML dan v-html adalah opt-out eksplisit.

Bagaimana cara meng-escape JSON yang disematkan ke tag <script>?

Ya. </script> di dalam data menutup blok lebih awal dan semua yang setelahnya berjalan sebagai skrip baru. Setelah serialisasi, ganti <, > dan & dengan \u003c, \u003e dan \u0026: hasilnya tetap JSON valid, ter-parse identik, dan tidak lagi bisa menutup tag.

Bacaan lanjutan