T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/generate_worksheet.py:487
- Finding
- Stored HTML and JavaScript Injection in Generated Worksheets## Vulnerability Details **File Location**: `scripts/generate_worksheet.py`, lines 487-523 and 544-546 **Vulnerability Type**: Unescaped user-controlled data in HTML output **Risk Level**: Medium ### Vulnerable Code ```python def render_html(qs, title, subtitle, per_page, with_answers, cols=2, name=""): pages = [qs[i:i + per_page] for i in range(0, len(qs), per_page)] or [[]] total_pages = len(pages) + (1 if with_answers else 0) out = [] for pi, page in enumerate(pages): cells = [] for q in page: lvtag = f'<span class="lvtag">{q["level"]}</span>' if len({x["level"] for x in qs}) > 1 else "" cells.append(f'<div class="q"><span class="no">{q["no"]}.</span>{lvtag}{q["prompt"]}</div>') out.append( f'<section class="sheet">' f'<div class="head"><div class="title">{title}</div>' f'<div class="sub">{subtitle}</div>' f'<div class="meta">姓名:<span>{name}</span>日期:<span></span>用时:<span></span>' f'做对:<span style="min-width:16mm"></span>题</div></div>' f'<div class="grid" style="grid-template-columns:repeat({cols},1fr)">{"".join(cells)}</div>' f'<div class="foot"><span>幼儿园数学练习 · {title}</span>' f"<span>第 {pi + 1} 页 / 共 {total_pages} 页</span></div>" f"</section>" ) # ... html = ( '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">' f"<title>{title}</title><style>{CSS}</style></head><body>" '<div class="no-print"><button onclick="window.print()">🖨 打印 / 另存为 PDF</button>' " <span style=\"font-size:12px;color:#888\">打印设置:A4 纵向、边距默认、勾选「背景图形」</span></div>" + "".join(out) + "</body></html>" ) return html ``` The affected values are populated through command-line parameters without validation or escaping: ```python ap.add_argument("--title", help="练习页标题") ap.add_argument("--name", default="", help="页眉预填的孩子姓名") ``` ### Techni ...[truncated 2756 chars]
- Remediation
- ## Remediation Suggestions Apply context-appropriate HTML escaping to every non-static value before interpolation. At minimum, encode `title`, `subtitle`, and `name` with Python's standard `html.escape` function: ```python from html import escape def render_html(qs, title, subtitle, per_page, with_answers, cols=2, name=""): safe_title = escape(str(title), quote=True) safe_subtitle = escape(str(subtitle), quote=True) safe_name = escape(str(name), quote=True) # Use only safe_title, safe_subtitle, and safe_name in HTML output. ``` Replace all affected interpolations: ```python f'<div class="head"><div class="title">{safe_title}</div>' f'<div class="sub">{safe_subtitle}</div>' f'<div class="meta">Name: <span>{safe_name}</span>...' f'<div class="foot"><span>Kindergarten Math Worksheet · {safe_title}</span>' f"<title>{safe_title}</title>" ``` Additional hardening should include: 1. Use a template engine with automatic HTML escaping if rendering complexity increases. 2. Keep trusted generated question markup separate from untrusted user-supplied text. 3. Validate title and name length to prevent excessively large generated documents. 4. Add regression tests covering `<`, `>`, `&`, single and double quotes, event-handler attributes, and closing tags such as `</title>` and `</script>`. 5. Verify that generated output renders hostile input as visible text rather than executable markup. 6. Consider a restrictive Content Security Policy in the generated HTML as defense in depth, while retaining output encoding as the primary control: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; script-src 'none'"> ```
