Raw sequelize.query with template-literal interpolation in login handler
sequelize.query is called with a backtick string interpolating req.body.email instead of using the replacements/bind option, the Sequelize raw-SQL escape hatch.
Sequelize exposes parameter binding through { replacements: [...] } or { bind: [...] }; passing a pre-interpolated SQL string short-circuits that protection. In routes/login.ts:33:
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 second argument is only { model, plain } — no replacements/bind. The email value, taken straight from the JSON request body, is concatenated into the SQL. The safe form is sequelize.query('SELECT * FROM Users WHERE email = ? AND password = ? AND deletedAt IS NULL', { replacements: [email, hash], model: UserModel, plain: true }).
curl -X POST http://target/rest/user/login -H 'Content-Type: application/json' -d '{"email":"admin@juice-sh.op'--","password":"x"}' — authenticates as admin without knowing the password.
Authentication bypass and arbitrary read against the application database via the unauthenticated login endpoint.
Line 33 calls models.sequelize.query with a template literal that interpolates req.body.email directly into the SQL string, and the options object only contains { model: UserModel, plain: true } — no replacements or bind. An unauthenticated attacker can POST to the login endpoint with an email like ' OR 1=1-- to bypass authentication (the well-known Juice Shop admin login bypass). The exploit chain is reachable from untrusted JSON body input on a public endpoint, and per scope rules the training-app nature is irrelevant.
The login route handler at routes/login.ts:33 interpolates req.body.email directly into a raw SQL string passed to sequelize.query with no replacements/bind, and the route is reachable pre-auth over HTTP (PoC: curl -X POST /rest/user/login with a crafted email payload). An attacker can trivially close the quote with '-- to bypass the password check and authenticate as admin, gaining a valid session token via security.authorize(user) — yielding full read access to user records and the ability to act as any user (high C and I). Availability is rated Low rather than High because UNION/boolean injection on SQLite can cause query errors or limited resource exhaustion, but there's no clear path to fully disable the service from this single SELECT sink. Scope is Unchanged since the impact remains within the application/database security authority.
- CWE-89
- Sequelize raw query docs (replacements vs interpolation)