SQL injection in /rest/user/login via template-literal email/password
The login query is built by interpolating req.body.email straight into a raw SQL string passed to sequelize.query, enabling authentication bypass and arbitrary SELECT.
routes/login.ts:33 constructs the authentication query with template-literal interpolation of unsanitized request fields:
models.sequelize.query(
`SELECT * FROM Users WHERE email = '${req.body.email || ''}' AND password = '${security.hash(req.body.password || '')}' AND deletedAt IS NULL`,
{ model: UserModel, plain: true }
)
The email arrives directly from the unauthenticated POST body and is concatenated into the SQL — no parameter binding, no escaping, no allowlist. sequelize.query with a plain string executes the statement verbatim. An attacker can inject SQL fragments to terminate the string literal and comment out the password check (' --), inject UNION SELECT payloads to log in as any user, dump arbitrary tables, or null out the returned totpSecret column so the handler skips the 2FA branch at line 36 entirely.
The password field is run through security.hash() before interpolation so it’s technically constrained to a hex string, but the email field has no such guard — that one input is sufficient.
POST /rest/user/login HTTP/1.1 Content-Type: application/json
{"email":"' or 1=1 ORDER BY id LIMIT 1 --","password":"anything"}
Resulting query: SELECT * FROM Users WHERE email = '' or 1=1 ORDER BY id LIMIT 1 --' AND password = '...' AND deletedAt IS NULL → returns the first user (admin) and the handler issues a session token for that account.
Unauthenticated remote attacker can bypass authentication and log in as any user (including admin), bypass the 2FA branch by zeroing the totpSecret column in a UNION payload, and exfiltrate arbitrary data from the database. No credentials required.
Line 33 builds the auth query via template-literal interpolation of req.body.email directly into models.sequelize.query(...) with no binding or escaping. The email field is unauthenticated POST input, and security.hash() is only applied to password, leaving email fully attacker-controlled. The PoC ' or 1=1-- terminates the email literal and comments out the password check, returning the first user; the handler then issues a session token via afterLogin. Scope explicitly says to treat Juice Shop findings as real production bugs, so this is a confirmed SQLi/auth-bypass.
The vulnerable sink at routes/login.ts:33 is reachable via an unauthenticated HTTP POST to /rest/user/login (AV:N, PR:N, UI:N), and the PoC requires only a single crafted JSON body with no race or environmental prerequisites (AC:L). The injection executes in the same application/database security authority (S:U) but enables full authentication bypass as admin and arbitrary SELECT/UNION exfiltration of the Users table including password hashes and totpSecret (C:H, I:H — attacker can log in as any account, including the admin session token issued at afterLogin). Because sequelize.query runs raw SQL with database-user privileges, the attacker can also issue destructive statements (e.g., stacked queries or DROP/DELETE depending on driver) or simply wedge logins, supporting A:H.
- CWE-89
- OWASP A03:2021 Injection