T09 · Insecure Skill Coding Practices
- Location
- scripts/generate_portal.py:275
- Finding
- Stored HTML and JavaScript Injection in the News Portal Generator<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_portal.py:275-288` **Vulnerability Type**: Stored HTML injection, stored cross-site scripting, and unsafe URL injection **Risk Level**: High ### Vulnerable Code ```python for article in articles: title = article.get("title", "Bez tytułu") url = article.get("url", "#") source = article.get("source", "Unknown") summary = article.get("summary", "")[:200] if summary: summary += "..." html += f''' <article class="card"> <div class="card-title"><a href="{url}" target="_blank">{title}</a></div> <div class="card-meta">{source}</div> <div class="card-summary">{summary}</div> </article> ''' ``` ### Technical Analysis The generator places remotely supplied article fields directly into an HTML document without contextual output encoding: - `title`, `source`, and `summary` are inserted into HTML element bodies without HTML escaping. - `url` is inserted into an `href` attribute without attribute escaping or URL-scheme validation. - The content is obtained from external news services through `scripts/fetch_news.py`, making these values untrusted. - No Content Security Policy is added to the generated page to reduce the impact of injected scripts. - The external link uses `target="_blank"` without `rel="noopener noreferrer"`. An attacker-controlled title such as the following could introduce executable markup: ```html <img src=x onerror="alert(document.domain)"> ``` An attacker-controlled URL could use a dangerous scheme: ```text javascript:alert(document.domain) ``` The first payload may execute when the generated portal is opened. The second generally requires the user to click the malicious article link. ### Attack Path 1. An attacker submits a story or other content to one of the configured news sources, or compromises a source response. 2. The source returns an attacker-controlled title, description, source f ...[truncated 1249 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Escape every value inserted into an HTML text context: ```python from html import escape safe_title = escape(str(article.get("title", "Bez tytułu")), quote=True) safe_source = escape(str(article.get("source", "Unknown")), quote=True) safe_summary = escape(str(article.get("summary", ""))[:200], quote=True) ``` 2. Validate article URLs with `urllib.parse.urlsplit` and allow only explicitly approved schemes: ```python from urllib.parse import urlsplit from html import escape def safe_external_url(value): value = str(value or "") parsed = urlsplit(value) if parsed.scheme not in {"https", "http"} or not parsed.netloc: return "#" return escape(value, quote=True) ``` 3. Prefer an auto-escaping template engine such as Jinja2 rather than constructing HTML through f-strings. 4. Add `rel="noopener noreferrer"` to links opened in new tabs: ```html <a href="..." target="_blank" rel="noopener noreferrer">...</a> ``` 5. Add a restrictive Content Security Policy appropriate for the generated portal. Avoid inline scripts where possible and move JavaScript into a separately trusted asset. 6. Add automated tests for hostile inputs, including: ```text <img src=x onerror=alert(1)> "><svg onload=alert(1)> javascript:alert(1) data:text/html,<script>alert(1)</script> ``` The tests should verify that markup is rendered as text and that dangerous URL schemes are replaced or rejected. ]]>
