Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions routes/productRevenue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
* SPDX-License-Identifier: MIT
*/

import * as models from '../models/index'
import { type Request, type Response, type NextFunction } from 'express'

// Product revenue lookup for the merchant dashboard. Callers filter by product
// name fragment, e.g. /rest/products/revenue?name=Juice.
module.exports = function productRevenue () {
return (req: Request, res: Response, next: NextFunction) => {
const name = req.query.name ?? ''
models.sequelize.query(`SELECT id, name, price FROM Products WHERE name LIKE '%${name}%' AND deletedAt IS NULL ORDER BY price DESC`)

@zeropath-ai-staging zeropath-ai-staging Bot Sep 9, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL Injection in /rest/products/revenue (routes/productRevenue.ts) (Severity: HIGH)

This SQL injection risk allows an unauthenticated user to inject SQL via the name query parameter, which is interpolated directly into a LIKE clause in productRevenue, leading to potential data disclosure or modification. The vulnerability arises from building the query with string interpolation in routes/productRevenue.ts lines 13-14, and mounting the endpoint in server.ts:572 without authentication middleware, which causes unvalidated input to reach the database.
View details in ZeroPath

Suggested change
models.sequelize.query(`SELECT id, name, price FROM Products WHERE name LIKE '%${name}%' AND deletedAt IS NULL ORDER BY price DESC`)
models.sequelize.query('SELECT id, name, price FROM Products WHERE name LIKE ? AND deletedAt IS NULL ORDER BY price DESC', { replacements: [`%${name}%`] })

💬 Reply @ZeroPath false-positive because … or @ZeroPath accepted-risk because … to triage this finding, or ask it any question.

All commands

@zeropath-ai-staging zeropath-ai-staging Bot Sep 9, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL Injection in /rest/products/revenue (routes/productRevenue.ts:14) (Severity: HIGH)

The endpoint is exploitable by remote attackers who can inject SQL via the name query parameter, which causes the raw query to include unescaped input in the LIKE clause and return unintended data. This is worsened by the route registering without authentication or authorization, leading to unauthorized access to product data via /rest/products/revenue?name=...
View details in ZeroPath

Suggested change
models.sequelize.query(`SELECT id, name, price FROM Products WHERE name LIKE '%${name}%' AND deletedAt IS NULL ORDER BY price DESC`)
models.sequelize.query('SELECT id, name, price FROM Products WHERE name LIKE :name AND deletedAt IS NULL ORDER BY price DESC', { replacements: { name: `%${name}%` } })

💬 Reply @ZeroPath false-positive because … or @ZeroPath accepted-risk because … to triage this finding, or ask it any question.

All commands

.then(([rows]: any) => {
res.json({ name, rows })
}).catch((error: Error) => {
next(error)
})
}
}
2 changes: 2 additions & 0 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const resetPassword = require('./routes/resetPassword')
const securityQuestion = require('./routes/securityQuestion')
const search = require('./routes/search')
const coupon = require('./routes/coupon')
const productRevenue = require('./routes/productRevenue')
const basket = require('./routes/basket')
const order = require('./routes/order')
const verify = require('./routes/verify')
Expand Down Expand Up @@ -568,6 +569,7 @@ restoreOverwrittenFilesWithOriginals().then(() => {
app.get('/rest/user/whoami', security.updateAuthenticatedUsers(), currentUser())
app.get('/rest/user/authentication-details', authenticatedUsers())
app.get('/rest/products/search', search())
app.get('/rest/products/revenue', productRevenue())
app.get('/rest/basket/:id', basket())
app.post('/rest/basket/:id/checkout', order())
app.put('/rest/basket/:id/coupon/:coupon', coupon())
Expand Down