T09 · Insecure Skill Coding Practices
- Location
src/index.js:14- Finding
HTML and JavaScript Injection Through Unsafe JSON-LD Embedding
- Content
View full analysis
Vulnerability Details
File Location:
src/index.js:14-17andsrc/index.js:23-26
Vulnerability Type: Cross-site scripting through unsafe JSON-LD embedding
Risk Level: HighVulnerable Code
javascript "text": item.answer } })) }; return JSON.stringify(jsonLd, null, 2);javascript injectJSONLD: (html, jsonLD) => { // Simple injection of JSON-LD script into HTML head const scriptTag = `<script type="application/ld+json">\n${jsonLD}\n</script>`; return html.replace('</head>', `${scriptTag}\n</head>`); },Technical Analysis
generateFAQPage()serializes attacker-controlled FAQ content withJSON.stringify(), but JSON serialization alone does not safely encode data for placement in an HTML<script>element. In HTML parsing, the character sequence</script>terminates a script element even when it appears inside a JSON string.Additionally,
injectJSONLD()accepts an arbitraryjsonLDstring and interpolates it directly into an HTML script block without parsing, validation, or context-appropriate escaping. Consequently, data containing a payload such as:text </script><script>alert(document.domain)</script>can close the intended
application/ld+jsonelement and introduce executable HTML or JavaScript.Attack Path
- An attacker gains control of an FAQ question or answer consumed by
generateFAQPage(), or directly controls thejsonLDargument passed toinjectJSONLD(). - The attacker includes
</script>followed by an executable<script>element or other active HTML content. - The application generates the JSON-LD string and passes it to
injectJSONLD(). - The resulting HTML is stored, published, or returned to a user without additional output encoding or sanitization.
- When a browser parses the page, it terminates the JSON-LD element at the injected closing tag.
- The browser interprets and executes the attacker-contro ...[truncated 793 chars]
- An attacker gains control of an FAQ question or answer consumed by
- Remediation
View remediation
Remediation Suggestions
- Parse JSON-LD input and reject any value that is not valid JSON. Do not accept arbitrary strings as trusted script contents.
- Re-serialize the parsed value with
JSON.stringify()and apply HTML script-context escaping. At minimum, encode<as\u003C; also encode>,&, U+2028, and U+2029 for defense in depth. - Centralize safe serialization so every generator and injection function uses the same hardened implementation.
- Validate expected JSON-LD structure and types before embedding it.
- Where practical, use a trusted DOM or framework mechanism designed to create a script element rather than constructing HTML through string interpolation.
- Apply a restrictive Content Security Policy as defense in depth, without treating it as a replacement for correct output encoding.
- Add regression tests for payloads containing
</script>, mixed-case closing tags,<,>,&, U+2028, U+2029, and malicious values in every attacker-controllable FAQ field.
A hardened serialization pattern can follow this approach:
javascript function serializeJSONLD(value) { return JSON.stringify(value, null, 2) .replace(/</g, '\\u003C') .replace(/>/g, '\\u003E') .replace(/&/g, '\\u0026') .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); } function injectJSONLD(html, jsonLDString) { const parsed = JSON.parse(jsonLDString); const safeJSONLD = serializeJSONLD(parsed); const scriptTag = `<script type="application/ld+json">\n${safeJSONLD}\n</script>`; return html.replace('</head>', `${scriptTag}\n</head>`); }
