agentggagentgg
Back to all findings
MEDIUMconfirmederror-message-leakerror-message-leakb002818ef650

Outer catch leaks error message via utils.getErrorMessage in upgradeToDeluxe response

The outer catch block in upgradeToDeluxe concatenates utils.getErrorMessage(err) into the JSON response, leaking raw underlying error text (likely err.message) to the client.

Fileroutes/deluxe.ts
Lines5759
Confidence
80%
File statusvalidated
Details

In routes/deluxe.ts, the outer try/catch around the deluxe-upgrade flow returns the underlying error message directly to the HTTP client:

} catch (err: unknown) {
  res.status(400).json({ status: 'error', error: 'Something went wrong: ' + utils.getErrorMessage(err) })
}

utils.getErrorMessage(err) is the project's helper that extracts err.message from an unknown error. Concatenating that into the response body matches the true-positive pattern described in the brief: a catch block in an HTTP route handler that emits raw error message content to the caller. Errors thrown inside this block can originate from Sequelize (UserModel.findOne, WalletModel.findOne, WalletModel.decrement, CardModel.findOne) and from security.deluxeToken, security.authorize, utils.queryResultToJson, etc. A Sequelize/DB driver error includes SQL fragments, table/column names, constraint names, and offending values — exactly the fingerprinting information described in the brief.

Note: the inner catch (around the user.update / token issuance) correctly returns a generic message and is not an issue. The outer catch is the offending one.

Proof of concept
  1. Send a POST to the deluxe-upgrade endpoint with a malformed payload that triggers a database-layer exception before the inner try (e.g. send a UserId of an unexpected type or a value that violates a Sequelize constraint in WalletModel.findOne / WalletModel.decrement).

Example: POST /rest/deluxe-membership with body { "UserId": "' OR 1=1", "paymentMode": "wallet" }.

  1. The response will be HTTP 400 with body { "status": "error", "error": "Something went wrong: <raw DB driver message>" }, revealing the underlying ORM/SQL error text, constraint or column names.
Impact

Unauthenticated or low-privileged attackers probing the deluxe upgrade endpoint can elicit raw error messages from the database/ORM layer, exposing schema details (table/column/constraint names), SQL syntax fragments, and other internal infrastructure fingerprints that aid further exploitation (e.g., SQL injection refinement, enumeration).

Validation
confirmed

The outer catch at lines 57-59 concatenates utils.getErrorMessage(err) (which extracts err.message) into the JSON response body returned with status 400. Errors thrown by Sequelize calls before the inner try (UserModel.findOne, WalletModel.findOne/decrement, CardModel.findOne) will surface raw ORM/DB driver text — including SQL fragments, column/constraint names — to the client. An attacker can trigger this by sending malformed values for UserId/paymentId (e.g., a type that fails Sequelize validation) to POST /rest/deluxe-membership. Scope explicitly forbids dismissing findings due to Juice Shop's training purpose, so this is a confirmed error-message leak.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Base score: 5.3 · MEDIUM

The sink is an HTTP POST endpoint (/rest/deluxe-membership) reachable remotely with no authentication check visible in the route handler, so AV:N and PR:N. Exploitation only requires sending a malformed body (e.g., a UserId that triggers a Sequelize/DB error in WalletModel.findOne/decrement) to make the outer catch concatenate utils.getErrorMessage(err) into the JSON response — no race conditions or victim interaction, hence AC:L and UI:N. The leak exposes raw ORM/DB error text (schema/column/constraint fragments), which is partial information disclosure the attacker doesn't fully control, so C:L; the bug doesn't modify state or affect availability (I:N/A:N), and impact stays inside the web app component (S:U).

References