T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/smartsheet_filter.py:428
- Finding
- Stored HTML and JavaScript Injection in Generated Reports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/smartsheet_filter.py`, lines 428–482 **Vulnerability Type**: Stored HTML injection / cross-site scripting in generated HTML reports **Risk Level**: High ### Vulnerable Code ```python html_parts = [ '<!DOCTYPE html>', '<html lang="zh-CN">', '<head>', '<meta charset="UTF-8">', '<meta name="viewport" content="width=device-width, initial-scale=1.0">', f'<title>{title}</title>', '<style>', # Static CSS omitted '</style>', '</head>', '<body>', '<div class="container">', f'<h1>{title}</h1>', f'<p class="subtitle">共筛选出 {len(results)} 条结果</p>', '<div class="stats">', f'<span class="stat-badge total">共 {len(results)} 条</span>', '</div>', '<table><thead><tr><th>#</th>', ] # 表头 for t in resolved_titles: html_parts.append(f'<th>{t}</th>') html_parts.append('</tr></thead><tbody>') # 行 for i, row in enumerate(results, 1): html_parts.append(f'<tr><td>{i}</td>') for t in resolved_titles: val = row.get(t, "") if val.startswith("http"): val = f'<a href="{val}" target="_blank">{val}</a>' else: val = val.replace("<", "<").replace(">", ">") html_parts.append(f'<td>{val}</td>') ``` ### Technical Analysis The HTML generator interpolates several untrusted or externally influenced values directly into HTML: - The report `title` is inserted into both `<title>` and `<h1>` without escaping. - Column titles from the processed spreadsheet are inserted into `<th>` elements without escaping. - Cell values beginning with `http` are inserted into an `href` attribute and anchor body without any attribute escaping. - Other cell values only replace `<` and `>`. This is incomplete HTML encoding and does not provide safe contextual handling for all output locations. The URL branch is particularly dangerous because a malicious spreadsheet value can begin with `http` while containing a qu ...[truncated 2392 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply contextual HTML escaping to every dynamic value. Use `html.escape(value, quote=True)` for report titles, headings, cell text, link text, and attribute values. 2. Validate URLs before creating links. Parse values with `urllib.parse.urlparse` and permit only an explicit allowlist of schemes, preferably `https`. Values that fail validation should be rendered as escaped plain text. 3. Avoid manually concatenating HTML where possible. Use a template engine configured with automatic escaping, such as Jinja2 with autoescape enabled. 4. Separate link validation from output encoding. URL validation determines whether a value may be used as a link, while HTML escaping prevents it from breaking out of its output context. Both controls are required. 5. Add `rel="noopener noreferrer"` to links opened with `target="_blank"`. 6. Add regression tests covering: - Quotes in URL values. - Event-handler injection attempts. - HTML tags and encoded tags in titles and column headings. - `javascript:`, `data:`, and malformed URL schemes. - Ampersands, quotation marks, apostrophes, and angle brackets. A hardened implementation could follow this pattern: ```python import html from urllib.parse import urlparse def escape_html(value) -> str: return html.escape(str(value), quote=True) def is_allowed_url(value: str) -> bool: try: parsed = urlparse(value) return parsed.scheme.lower() == "https" and bool(parsed.netloc) except (TypeError, ValueError): return False safe_title = escape_html(title) html_parts.append(f"<title>{safe_title}</title>") html_parts.append(f"<h1>{safe_title}</h1>") for column_title in resolved_titles: html_parts.append(f"<th>{escape_html(column_title)}</th>") for column_title in resolved_titles: raw_value = str(row.get(column_title, "")) safe_value = escape_html(raw_value) if is_allowed_url(raw_value): html_parts.append( f'<td><a h ...[truncated 179 chars]
