Fix bug: remove eval()/Pug SSTI of username in GET /profile - #333
devin-ai-integration[bot] wants to merge 1 commit into
Conversation
Signed-off-by: Devin <devin@cognition.ai>
| const username = user.username | ||
|
|
||
| if (username?.match(/#{(.*)}/) !== null && utils.isChallengeEnabled(challenges.usernameXssChallenge)) { | ||
| req.app.locals.abused_ssti_bug = true |
There was a problem hiding this comment.
🟡 Inert username falsely solves SSTi
Any #{...} username sets abused_ssti_bug although Pug now escapes it. The server-side verifier then awards SSTi without command execution.
Learn more
The global abused_ssti_bug flag records whether the SSTi exploit occurred. This assignment now records only that a username matched the old payload syntax. The username is passed as a Pug local and escaped, so no template evaluation or command execution occurs. The server-side verifier trusts this flag and solves sstiChallenge on the next request with the known key.
Example: Set the username to #{1+1}, load /profile, then request /solve/challenges/server-side?key=tRy_H4rd3r_n0thIng_iS_Imp0ssibl3. The challenge becomes solved although the page displays #{1+1} and no command ran.
Recommended fix: Remove this obsolete marker path and retire or redesign the SSTi challenge verification. Do not set an exploit-success flag from payload shape alone.
Was this helpful? React with 👍 or 👎 to provide feedback.
| div.s12.m12.l6 | ||
| img.img-rounded(src=profileImage, alt='profile picture', width='90%', height='236', style='margin-right: 5%; margin-left: 5%;') | ||
| p(style='margin-top: 8px; color: _textColor_; text-align: center;') _username_ | ||
| p(style='margin-top: 8px; color: _textColor_; text-align: center;')= username |
There was a problem hiding this comment.
🟡 Escaped payload falsely solves CSP Bypass
An escaped script username still satisfies usernameXssChallenge when the profile image injects unsafe-inline. Loading the profile awards CSP Bypass without executing XSS.
Learn more
The profile previously inserted the username into Pug source, allowing the challenge payload to become executable markup after CSP injection. The new = interpolation correctly escapes the username into text. However, solveIf still awards usernameXssChallenge from the raw username and profile-image strings alone. It does not observe browser execution, so the now-inert payload still solves the challenge.
Example: Store <script>alert(xss)</script> as the username and a profile image ending in ; script-src 'unsafe-inline'. A GET of /profile renders escaped text but marks CSP Bypass solved.
Recommended fix: Remove or redesign the obsolete challenge solver and its metadata alongside the rendering fix. Challenge completion must not rely on a raw payload that the response escapes.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
|
||
| describe('challenge "usernameXss"', () => { | ||
| it('Username field should be susceptible to XSS attacks after disarming CSP via profile image URL', () => { | ||
| describe('username rendering', () => { |
There was a problem hiding this comment.
Description
Fixes server-side RCE / SSTI in
GET /profile(routes/userProfile.ts). The logged-in user'susername(attacker-controlled viaPOST /profile) was matched against/#{(.*)}/, the inner code was passed toeval(), and the result was string-spliced into the Pug template source beforepug.compile().Changes:
eval()branch entirely; keepreq.app.locals.abused_ssti_bug = truemarker so the existing SSTi verification check keeps working._username_into the template source.views/userProfile.pugnow rendersp(...)= username, receiving the value as an escaped template local (fn({ ..., username })), so no user data reaches the Pug compiler oreval.#{...}username is rendered verbatim (not evaluated) and Cypress spec updated accordingly.Resolved or fixed issue: none
AI Tool Disclosure
Devin (Cognition AI)DevinFix security finding: server-side RCE via eval() of attacker-controlled username (SSTI) in routes/userProfile.ts; implement fix and open PR.Affirmation
Devin-Org: engineering