T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/report.py:365
- Finding
- Stored HTML and Script Injection in Generated Financial Reports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/report.py:365-378` **Vulnerability Type**: Stored HTML injection / stored cross-site scripting **Risk Level**: High ### Vulnerable Code ```python # Top merchants if report["top_merchants"]: html += " <h2>Top Merchants</h2>\n <table>\n" html += " <tr><th>Merchant</th><th>Amount</th><th>Transactions</th></tr>\n" for m in report["top_merchants"]: html += f" <tr><td>{m['merchant']}</td><td>{m['total_formatted']}</td>" html += f"<td>{m['count']}</td></tr>\n" html += " </table>\n" # Largest transactions if report["largest_transactions"]: html += " <h2>Largest Transactions</h2>\n <table>\n" html += " <tr><th>Date</th><th>Description</th><th>Amount</th><th>Category</th></tr>\n" for l in report["largest_transactions"]: html += f" <tr><td>{l['date']}</td><td>{l['description']}</td>" html += f"<td>{l['amount_formatted']}</td><td>{l['category'] or ''}</td></tr>\n" html += " </table>\n" ``` ### Technical Analysis Merchant names and transaction descriptions originate from imported CSV or OFX/QFX statements. These values are persisted in SQLite and later interpolated directly into HTML without HTML entity escaping. Because characters such as `<`, `>`, `"`, `'`, and `&` are not escaped, a crafted transaction description can break out of the intended table cell and introduce arbitrary HTML or JavaScript-capable elements. For example, a description containing an image element with an event handler would be emitted as active markup rather than displayed as text. This is a stored injection vulnerability: the malicious value is first saved in the transaction database and can execute later whenever an HTML report containing that transaction is generated and opened. ### Attack Path 1. An attacker creates or modifies a CSV, OFX, or QFX statement containing a transaction description with malicious HTML. 2. T ...[truncated 1221 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Escape every dynamic value before placing it in HTML: ```python from html import escape merchant = escape(str(m["merchant"]), quote=True) description = escape(str(l["description"]), quote=True) category = escape(str(l["category"] or ""), quote=True) ``` 2. Prefer a template engine with automatic escaping enabled instead of constructing HTML through string concatenation. 3. Apply escaping according to output context. HTML text, attributes, URLs, CSS, and JavaScript require different encoding rules. 4. Add a restrictive Content Security Policy, such as disallowing inline scripts and restricting network destinations. 5. Add regression tests using descriptions containing HTML metacharacters, event handlers, script elements, encoded payloads, and malformed tags. 6. Treat statement contents as untrusted even when files appear to originate from a supported bank. ]]>
