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/datasheetRender.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 { type Request, type Response, type NextFunction } from 'express'
import { exec } from 'child_process'

// Renders a product datasheet to PDF for the storefront download button.
module.exports = function datasheetRender () {
return (req: Request, res: Response, next: NextFunction) => {
const sku = req.query.sku ?? ''
exec(`/usr/bin/wkhtmltopdf ftp/datasheets/${sku}.html /tmp/${sku}.pdf`, (error, stdout) => {

@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.

OS Command Injection in datasheetRender route (Severity: CRITICAL)

The datasheet endpoint is vulnerable to OS command injection by interpolating the attacker-supplied sku query parameter into a shell command, which causes arbitrary commands to run as the Node.js process and can overwrite arbitrary /tmp files. This occurs in routes/datasheetRender.ts where req.query.sku is interpolated into exec(/usr/bin/wkhtmltopdf ftp/datasheets/${sku}.html /tmp/${sku}.pdf). Evidence shows unauthenticated access via server.ts:572 that mounts this function without validation, enabling inputs like sku=x.html;touch /tmp/pwned;# to execute commands.
View details in ZeroPath

Suggested fix

Unable to apply as inline suggestion. Download .diff and apply from repo root with git apply 13d2f2b1.diff

diff --git a/routes/datasheetRender.ts b/routes/datasheetRender.ts
--- a/routes/datasheetRender.ts
+++ b/routes/datasheetRender.ts
@@ -4,13 +4,17 @@
  */
 
 import { type Request, type Response, type NextFunction } from 'express'
-import { exec } from 'child_process'
+import { execFile } from 'child_process'
 
 // Renders a product datasheet to PDF for the storefront download button.
 module.exports = function datasheetRender () {
   return (req: Request, res: Response, next: NextFunction) => {
-    const sku = req.query.sku ?? ''
-    exec(`/usr/bin/wkhtmltopdf ftp/datasheets/${sku}.html /tmp/${sku}.pdf`, (error, stdout) => {
+    const sku = req.query.sku
+    if (typeof sku !== 'string' || !/^[a-zA-Z0-9_-]+$/.test(sku)) {
+      res.status(400).send('Invalid SKU')
+      return
+    }
+    execFile('/usr/bin/wkhtmltopdf', [`ftp/datasheets/${sku}.html`, `/tmp/${sku}.pdf`], (error, stdout) => {
       if (error != null) {
         next(error)
         return

💬 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.

OS Command Injection in datasheetRender.ts (sku param) (Severity: CRITICAL)

An attacker can inject arbitrary shell commands via the sku query parameter which is interpolated into a shell invocation, leading to remote code execution with the Node.js process user. This happens in routes/datasheetRender.ts where req.query.sku is interpolated into /usr/bin/wkhtmltopdf ... and executed, with the public route registered in server.ts:572 and no authentication.
View details in ZeroPath

Suggested fix

Unable to apply as inline suggestion. Download .diff and apply from repo root with git apply c2abb0e9.diff

diff --git a/routes/datasheetRender.ts b/routes/datasheetRender.ts
--- a/routes/datasheetRender.ts
+++ b/routes/datasheetRender.ts
@@ -4,13 +4,13 @@
  */
 
 import { type Request, type Response, type NextFunction } from 'express'
-import { exec } from 'child_process'
+import { execFile } from 'child_process'
 
 // Renders a product datasheet to PDF for the storefront download button.
 module.exports = function datasheetRender () {
   return (req: Request, res: Response, next: NextFunction) => {
-    const sku = req.query.sku ?? ''
-    exec(`/usr/bin/wkhtmltopdf ftp/datasheets/${sku}.html /tmp/${sku}.pdf`, (error, stdout) => {
+    const sku = typeof req.query.sku === 'string' && /^[\w-]+$/.test(req.query.sku) ? req.query.sku : ''
+    execFile('/usr/bin/wkhtmltopdf', [`ftp/datasheets/${sku}.html`, `/tmp/${sku}.pdf`], (error, stdout) => {
       if (error != null) {
         next(error)
         return

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

All commands

if (error != null) {
next(error)
return
}
res.type('text/plain').send(stdout)
})
}
}
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 datasheetRender = require('./routes/datasheetRender')
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/datasheet', datasheetRender())
app.get('/rest/basket/:id', basket())
app.post('/rest/basket/:id/checkout', order())
app.put('/rest/basket/:id/coupon/:coupon', coupon())
Expand Down