CSP allows 'unsafe-eval' and is built from user-controlled profileImage
The Content-Security-Policy returned by /profile interpolates the user-controlled profileImage value and explicitly permits 'unsafe-eval' in script-src, defeating the policy and enabling XSS.
In getUserProfile, the response CSP is built by string concatenation with user.profileImage and hard-codes 'unsafe-eval':
const CSP = `img-src 'self' ${user?.profileImage}; script-src 'self' 'unsafe-eval'`
res.set({
'Content-Security-Policy': CSP
})
Problems flagged by the brief:
script-src 'self' 'unsafe-eval'—'unsafe-eval'in any directive is explicitly called out as a CSP smell because it re-enableseval,new Function, andsetTimeout(string)execution, allowing many XSS payloads that a hardened CSP would block. Note that this very route also runs user-controlledeval(code)a few lines above, making'unsafe-eval'particularly dangerous in combination.user.profileImageis concatenated directly into the header value. Because the user can put;and arbitrary CSP tokens in their profileImage, they can extend the policy with directives like; script-src 'unsafe-inline'(this is exactly what the adjacentusernameXssChallengetest is checking for —user?.profileImage.match(/;[ ]*script-src(.)*'unsafe-inline'/g)). That turns the already-weak policy into effectively no CSP at all for inline<script>payloads.- The policy has no
default-src, noobject-src 'none', and noframe-ancestors, so clickjacking and several other vectors are also unblocked on this HTML response. The response also sets noX-Frame-Options,X-Content-Type-Options, orReferrer-Policyfor this rendered HTML page, relying entirely on this defective CSP.
- Authenticate as a user and update profileImage to a value containing
; script-src 'unsafe-inline'(or any directive that broadens script execution). - Set username to a payload like
<script>alert(\xss\)</script>(or trigger the SSTI/eval branch via#{...}). - Visit
/profile— the response includesContent-Security-Policy: img-src 'self' <attacker-injected>; script-src 'self' 'unsafe-eval'; script-src 'unsafe-inline', allowing inline scripts to execute. - Even without injection, any
eval(...)-style XSS payload runs because'unsafe-eval'is permitted.
Any authenticated user viewing the profile page is exposed to XSS that would otherwise be blocked by a strong CSP. An attacker can both (a) execute eval-based payloads thanks to 'unsafe-eval', and (b) inject arbitrary CSP directives via profileImage to fully neutralize the policy (e.g. enable 'unsafe-inline'). Combined with the eval(code) SSTI sink on the username field, this turns CSP from a defense-in-depth control into no protection at all.
The line const CSP = img-src 'self' ${user?.profileImage}; script-src 'self' 'unsafe-eval'` literally interpolates user-controlled user.profileImage into the Content-Security-Policy header and hard-codes 'unsafe-eval'. An authenticated user can set profileImage to a string containing ; plus arbitrary directives (the adjacent usernameXssChallenge regex /;[ ]*script-src(.)*'unsafe-inline'/g` confirms this injection path is reachable), which combined with the username eval/SSTI sink several lines above makes XSS exploitation trivial. Scope explicitly says to treat Juice Shop findings as a real production app, so the intentionally-vulnerable nature is not a mitigating factor.
The defective CSP is emitted over HTTP by /profile, so the attack vector is network. Exploitation requires the attacker to be logged in (the route calls security.authenticatedUsers.get(req.cookies.token) and bails out otherwise) so they can set their own profileImage to inject ; script-src 'unsafe-inline' and set a #{...} username that hits the eval(code) sink — hence PR:L. A victim still has to load /profile for the resulting script to fire (UI:R), and since the script executes in the same application origin the scope is unchanged. The CSP weakness itself is a defense-in-depth failure that enables XSS in the user's own session context, giving partial confidentiality and integrity loss (cookie/DOM access, limited action-on-behalf-of) but no direct availability impact, so C:L / I:L / A:N.