Injection and access control — Examples

Example 1 — A quote that changes the statement

A review of the order lookup below turns up a query built by joining strings, with the user-supplied identifier interpolated straight into the statement. Rewrite it so the input travels as data, and name the character the attack needs in order to reach the database.

// orders.ts — as reviewed
app.get("/orders/:id", (req, res) => {
  const rows = db.query(
    `SELECT * FROM orders WHERE id = '${req.params.id}'`
  );
  res.json(rows);
});

Solution

// orders.ts — after the fix
app.get("/orders/:id", (req, res) => {
  const rows = db.query(
    "SELECT * FROM orders WHERE id = $1",
    [req.params.id]
  );
  res.json(rows);
});

The value now travels in a separate parameter and cannot alter the statement’s structure. The injection needs a single quote to close the string literal and start writing SQL; as a bound parameter that character is just data. The usual half-fix is to parameterise the identifier and leave the same concatenation below for a sort column, where a bind placeholder cannot stand in.

Example 2 — Someone else’s invoice

The detail endpoint below was added after the list page, which filters by the signed-in account. It loads whatever identifier arrives. Given a valid session and an id belonging to another account, write the check that closes the hole and choose the response deliberately.

GET /invoices/1042
  session.accountId = 7
  invoice 1042.accountId = 9
  -> 200 OK, body: another account's invoice

Solution

GET /invoices/1042
  invoice = invoices.find(req.params.id)
  if (!invoice || invoice.accountId !== session.accountId)
    -> 404 Not Found

Authorising the route is not the same as authorising the object, so the check runs on every fetch against the session’s account rather than a client-supplied id. A not-found is returned instead of a forbidden, because admitting that invoice 1042 exists is itself a disclosure. The common slip is to trust the filter on the list page and assume the detail page inherits it; it does not.