T09 · Insecure Skill Coding Practices
Error
- Location
- references/schema-examples.md:385
- Finding
- Unsafe JSON-LD Serialization Can Enable Stored Cross-Site Scripting<![CDATA[ ## Vulnerability Details **File Location**: `references/schema-examples.md:385-393` **Vulnerability Type**: Unsafe serialization of dynamic data into an inline script **Risk Level**: High ### Vulnerable Code ```jsx export default function ProductPage({ product }) { const schema = { "@context": "https://schema.org", "@type": "Product", name: product.name, // ... other properties }; return ( <> <Head> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} /> </Head> {/* Page content */} </> ); } ``` ### Technical Analysis The example inserts dynamically populated product data into an inline JSON-LD `<script>` element using `dangerouslySetInnerHTML`. Although `JSON.stringify()` produces valid JSON, it is not a safe HTML-script-context encoder. In particular, it does not escape the `<` character or prevent an attacker-controlled value from containing a literal `</script>` sequence. HTML parsing rules recognize `</script>` even when it appears inside a JSON string. Consequently, a malicious product name or another dynamic schema property could terminate the JSON-LD element and introduce attacker-controlled HTML or JavaScript. For example, an attacker-controlled field containing a payload conceptually equivalent to: ```text </script><script>ATTACKER_CODE</script> ``` could break out of the non-executable `application/ld+json` block. The newly introduced regular script element would then execute in the origin of the affected website. This is especially relevant when product information comes from a CMS, marketplace seller, catalog import, API integration, or another source that is not fully trusted. ### Attack Path 1. An attacker obtains the ability to create or modify a product field consumed by the schema generator, such as `product.name`. 2. The attacker places a script-closing sequence and malicious markup in that fi ...[truncated 1324 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Use a serializer designed for embedding JSON in HTML script contexts. At minimum, escape HTML-significant characters after JSON serialization, particularly `<`, so that a literal `</script>` sequence cannot occur: ```jsx function serializeJsonLd(value) { return JSON.stringify(value) .replace(/</g, "\\u003c") .replace(/>/g, "\\u003e") .replace(/&/g, "\\u0026") .replace(/\u2028/g, "\\u2028") .replace(/\u2029/g, "\\u2029"); } <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: serializeJsonLd(schema) }} /> ``` Additional hardening measures should include: 1. Use a well-reviewed JSON-for-HTML serialization library where available. 2. Validate dynamic schema fields against their expected types, formats, and maximum lengths. 3. Treat CMS, marketplace, imported catalog, and API-provided values as untrusted. 4. Sanitize or reject unexpected markup in fields that should contain plain text. 5. Apply a restrictive Content Security Policy as defense in depth, while not relying on CSP as the primary fix. 6. Add regression tests containing payloads such as `</script><script>...</script>` and verify that no additional DOM elements or executable scripts are created. 7. Update the documentation example so users do not copy an unsafe implementation into production applications. ]]>
