T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/md_to_html.py:6
- Finding
- Unescaped Markdown Enables Script Injection in Generated HTML<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md_to_html.py`, lines 6-23 **Vulnerability Type**: Generated-document HTML and JavaScript injection **Risk Level**: High ### Vulnerable Code ```python def md_to_html(md): html = md # Headers html = re.sub(r"^### (.+)$", r"<h3>\1</h3>", html, flags=re.MULTILINE) html = re.sub(r"^## (.+)$", r"<h2>\1</h2>", html, flags=re.MULTILINE) html = re.sub(r"^# (.+)$", r"<h1>\1</h1>", html, flags=re.MULTILINE) # Bold/italic html = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", html) html = re.sub(r"\*(.+?)\*", r"<em>\1</em>", html) # Code html = re.sub(r"`(.+?)`", r"<code>\1</code>", html) # Links html = re.sub(r"\[(.+?)\]\((.+?)\)", r'<a href="\2">\1</a>', html) # Paragraphs paragraphs = [p.strip() for p in html.split(" ") if p.strip()] html = "\n".join(f"<p>{p}</p>" if not p.startswith("<") else p for p in paragraphs) return f"""<!DOCTYPE html> <html><head><meta charset="utf-8"><title>Converted</title></head> <body>{html}</body></html>""" ``` ### Technical Analysis The converter initially assigns the complete, untrusted Markdown input to `html` and then performs regular-expression substitutions without first HTML-encoding input text. Any HTML elements already present in the Markdown therefore remain active in the generated document. The paragraph construction further preserves content beginning with `<` instead of wrapping or sanitizing it: ```python html = "\n".join(f"<p>{p}</p>" if not p.startswith("<") else p for p in paragraphs) ``` Consequently, payloads such as the following can become executable browser content: ```html <script>alert(document.domain)</script> ``` ```html <img src=x onerror=alert(document.domain)> ``` The link conversion also inserts the attacker-controlled destination directly into an `href` attribute without attribute escaping or URL-scheme validation: ```python html = re.sub(r"\[(.+?)\]\((.+?)\)", r'<a href= ...[truncated 2427 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Escape untrusted text by default** - Use `html.escape(value, quote=True)` for text and attribute values. - Do not treat input beginning with `<` as trusted HTML. 2. **Use a structured Markdown parser** - Replace regular-expression-based conversion with a maintained parser that supports disabling raw HTML. - Configure the parser to escape or remove embedded HTML by default. 3. **Sanitize HTML when raw HTML is required** - Make raw HTML support an explicit opt-in feature. - Process generated HTML through an allowlist-based sanitizer. - Exclude scripts, event-handler attributes, dangerous embedded content, and other active elements. 4. **Validate link destinations** - Parse URLs before emitting them. - Permit only explicitly required schemes, such as `https`, `http`, and optionally `mailto`. - Reject or neutralize `javascript:`, dangerous `data:` content, and other executable schemes. - HTML-encode the validated URL before placing it in `href`. 5. **Correct the syntax error safely** - Replace the literal multiline string in the `split` call with: ```python paragraphs = [p.strip() for p in html.split("\n\n") if p.strip()] ``` - Apply the output-encoding and sanitization corrections at the same time so restoring execution does not expose the injection flaw. 6. **Add security regression tests** - Test raw `<script>` elements. - Test event attributes such as `onerror`. - Test attribute-breaking quotation marks. - Test `javascript:` and dangerous `data:` URLs. - Verify that ordinary Markdown still produces correctly encoded HTML. ]]>
