T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/kuaishou_comment_search.py:137
- Finding
- Stored HTML and Script Injection in Generated Reports<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/kuaishou_comment_search.py:137-148` - `scripts/consolidate_report.py:35-46` - `scripts/backfill_html.py:38-44` - `scripts/consolidate_report.py:174-181` - `assets/report_template.html:439-458` - `assets/consolidate_report_template.html:378-397` **Vulnerability Type**: Stored HTML injection and cross-site scripting **Risk Level**: High ### Vulnerable Code The avatar URL returned by the API or supplied through page JSON is inserted into an HTML attribute without escaping or URL validation: ```python avatar = c.get("user_avatar", "") or "" name = escape_html(c.get("user_name", "")) content = escape_html(c.get("content", "")) like = c.get("like_count", 0) or 0 reply = c.get("reply_count", 0) or 0 time_str = (c.get("create_time", "") or "")[5:16] ip = escape_html(c.get("ip_location", "")) row = ( f'<tr{row_class}>' f'<td>' f'<div class="user-cell">' f'<img src="{avatar}" class="user-avatar" alt="" onerror="this.style.display=\'none\'" referrerpolicy="no-referrer">' f'<span class="user-name">{name}{pin_badge}</span>' f'</div>' f'</td>' ) ``` AI-generated analysis summaries are also inserted as raw HTML: ```python summary_map = { "{{SUMMARY_POSITIVE}}": analysis.get("positive_summary", ""), "{{SUMMARY_NEGATIVE}}": analysis.get("negative_summary", ""), "{{SUMMARY_DEMAND}}": analysis.get("demand_summary", ""), "{{SUMMARY_COMPETITOR}}": analysis.get("competitor_summary", ""), } for key, val in summary_map.items(): html = html.replace(key, val) ``` The consolidated report generator uses the same unsafe pattern: ```python "{{POSITIVE_RATIO}}": str(analysis.get("positive_ratio", "--")), "{{NEGATIVE_RATIO}}": str(analysis.get("negative_ratio", "--")), "{{DEMAND_RATIO}}": str(analysis.get("demand_ratio", "--")), "{{COMPETITOR_RATIO}}": str(analysis.get("competitor_ratio", "--")), "{{SUMMARY_POSITIVE}}": analysis.get("positive_summary", ""), "{{SUMMARY_NEG ...[truncated 3101 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply contextual HTML attribute escaping to avatar URLs using `html.escape(value, quote=True)`. 2. Parse avatar URLs with `urllib.parse.urlparse` and permit only `https` URLs from explicitly approved hosts. 3. Reject URLs containing credentials, control characters, unsupported schemes, or malformed hostnames. 4. Replace raw HTML summaries with structured arrays of plain-text bullet points and generate `<ul>` and `<li>` elements internally. 5. If formatted summary HTML must be supported, sanitize it with a strict allowlist that permits only necessary elements and no attributes. 6. Validate ratio fields as bounded numeric values rather than inserting arbitrary strings. 7. Add a restrictive Content Security Policy that blocks inline event handlers, unapproved scripts, and unapproved network destinations. 8. Add tests containing quotation marks, event handlers, `<script>` elements, SVG payloads, and dangerous URL schemes. ]]>
