agentggagentgg
Back to all findings
MEDIUMconfirmedmissing-security-headersweak-csp-headersd22c1d59c0dd

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.

Fileroutes/userProfile.ts
Lines8696
Confidence
95%
File statusvalidated
Details

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:

  1. script-src 'self' 'unsafe-eval''unsafe-eval' in any directive is explicitly called out as a CSP smell because it re-enables eval, new Function, and setTimeout(string) execution, allowing many XSS payloads that a hardened CSP would block. Note that this very route also runs user-controlled eval(code) a few lines above, making 'unsafe-eval' particularly dangerous in combination.
  2. user.profileImage is 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 adjacent usernameXssChallenge test 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.
  3. The policy has no default-src, no object-src 'none', and no frame-ancestors, so clickjacking and several other vectors are also unblocked on this HTML response. The response also sets no X-Frame-Options, X-Content-Type-Options, or Referrer-Policy for this rendered HTML page, relying entirely on this defective CSP.
Proof of concept
  1. Authenticate as a user and update profileImage to a value containing ; script-src 'unsafe-inline' (or any directive that broadens script execution).
  2. Set username to a payload like <script>alert(\xss\)</script> (or trigger the SSTI/eval branch via #{...}).
  3. Visit /profile — the response includes Content-Security-Policy: img-src 'self' <attacker-injected>; script-src 'self' 'unsafe-eval'; script-src 'unsafe-inline', allowing inline scripts to execute.
  4. Even without injection, any eval(...)-style XSS payload runs because 'unsafe-eval' is permitted.
Impact

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.

Validation
confirmed

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.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N
Base score: 4.6 · MEDIUM

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.

References