T09 · Insecure Skill Coding Practices
Error
- Location
- src/index.js:14
- Finding
- HTML and JavaScript Injection Through Unsafe JSON-LD Embedding## Vulnerability Details **File Location**: `src/index.js:14-17` and `src/index.js:23-26` **Vulnerability Type**: Cross-site scripting through unsafe JSON-LD embedding **Risk Level**: High ### Vulnerable 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 with `JSON.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 arbitrary `jsonLD` string 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+json` element and introduce executable HTML or JavaScript. ### Attack Path 1. An attacker gains control of an FAQ question or answer consumed by `generateFAQPage()`, or directly controls the `jsonLD` argument passed to `injectJSONLD()`. 2. The attacker includes `</script>` followed by an executable `<script>` element or other active HTML content. 3. The application generates the JSON-LD string and passes it to `injectJSONLD()`. 4. The resulting HTML is stored, published, or returned to a user without additional output encoding or sanitization. 5. When a browser parses the page, it terminates the JSON-LD element at the injected closing tag. 6. The browser interprets and executes the attacker-contro ...[truncated 793 chars]
- Remediation
- ## Remediation Suggestions 1. Parse JSON-LD input and reject any value that is not valid JSON. Do not accept arbitrary strings as trusted script contents. 2. 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. 3. Centralize safe serialization so every generator and injection function uses the same hardened implementation. 4. Validate expected JSON-LD structure and types before embedding it. 5. Where practical, use a trusted DOM or framework mechanism designed to create a script element rather than constructing HTML through string interpolation. 6. Apply a restrictive Content Security Policy as defense in depth, without treating it as a replacement for correct output encoding. 7. 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>`); } ```
