JSON Toolkit

Jak vzniká XSS přes JSON

Shrnutí: JSON se sám nikdy nevykonává. XSS vzniká ve chvíli, kdy ho ošetříte jako HTML – spojováním řetězců na serveru, frontendovým innerHTML nebo vložením JSON přímo do značky skriptu.

Středně pokročilý~4 min čteníPublikováno 2026-10-30Aktualizováno 2026-10-30
Tři způsoby, jak se JSON stane XSS, a jak je zabránit

Stručně: JSON se sám nikdy nevykonává – nejprve uzavřete tři výstupní cesty: escapujte na serveru podle kontextu, používejte textContent místo innerHTML a při vkládání JSON nahraďte < za \u003c.

{"name": "<img src=x onerror=alert(1)>"} je platná data. Zda jsou nebezpečná, záleží výhradně na tom, kam je umístíte.

Vstup první: sestavování řetězců na serveru

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

Prohlížeč parsuje značky uvnitř data.name. JSON je tady jen kontejner; chybou je escapování pro špatný kontext.

Oprava: použijte výchozí escapování šablonovacího enginu (<%= %> v EJS, {{ }} ve Vue a Django). Vyhněte se <%- %>, v-html a |safe pro cokoli, co ovládá uživatel.

Vstup druhý: innerHTML

el.innerHTML = user.bio;    // parsuje značky
el.textContent = user.bio;  // vkládá text

Reactí {user.bio} a Vue {{ user.bio }} vytvářejí textové uzly a jsou ve výchozím nastavení bezpečné. dangerouslySetInnerHTML a v-html jsou explicitní výjimky.

Pokud bohatý text skutečně potřebujete, sanitizujte pomocí allowlistu (DOMPurify a podobné), místo abyste důvěřovali backendu.

Vstup třetí: vložení do značky skriptu

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

</script> uvnitř dat uzavře blok předčasně a všechno za ním se spustí jako nový skript. To je nejvíce opomíjený případ.

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

Stále platný JSON, po parsování identický, už nemůže uzavřít značku. Next.js a Nuxt dělají totéž pro __NEXT_DATA__ a __NUXT__.

Dva sousedé, které stojí za to znát

JSON hijacking — staré prohlížeče vykonávaly JSON načtený přes <script src>; předefinování Array prozradilo obsah. Moderní prohlížeče to opravily a Content-Type: application/json plus X-Content-Type-Options: nosniff uzavírá zbytek.

Zbytky JSONP — uživatelsky ovládaný název callbacku je spuštění libovolného kódu z principu. V nových systémech ho nepoužívejte.

Kontrolní seznam

  • Každé místo, kde pole JSON dorazí do HTML, je escapované pro svůj kontext (HTML, atribut, JS, URL)
  • Žádné innerHTML, v-html ani dangerouslySetInnerHTML konzumující řetězce z API
  • Vložený JSON escapuje <, > a &
  • Bohatý text prochází sanitizací allowlistem, nikoli filtrováním blacklistem
  • Odpovědi nesou Content-Type: application/json a nosniff

Časté dotazy

Proč platný JSON může stále spustit XSS?

JSON je jen text a nikdy se nevykonává; to, že je platný, znamená jen, že se parsuje. Nebezpečí se objeví, když se zapíše do HTML nebo skriptového kontextu: prohlížeč parsuje značky, nebo </script> uzavře blok předčasně a zbytek se spustí jako kód. Chybou je chybějící escapování respektující kontext na výstupu, nikoli datový formát.

Mám pro vykreslení polí z API použít textContent, nebo innerHTML?

Preferujte textContent: vkládá hodnotu jako prostý text, takže značky se zobrazí doslovně. innerHTML parsuje značky, takže pole z API obsahující <img onerror=...> se vykoná. Reactí {user.bio} a Vue {{ user.bio }} ve výchozím nastavení vytvářejí textové uzly; dangerouslySetInnerHTML a v-html jsou explicitní výjimky.

Jak escapovat JSON vložený do značky <script>?

Ano. </script> uvnitř dat uzavře blok předčasně a všechno za ním se spustí jako nový skript. Po serializaci nahraďte <, > a & za \u003c, \u003e a \u0026: výsledek je stále platný JSON, parsuje se identicky a už nemůže uzavřít značku.

Další čtení