Skip to solution
mediumBackend

How do you prevent SQL injection in Node.js database access?

504 views
01

Understand the problem

Question presented to candidate: "Your login query is built with a template literal, interpolating the username directly into the SQL string. A security researcher demonstrates that entering a specific string as the username returns EVERY user in the database, not just one. How does that work, and what's the actual fix — not just 'sanitize the input'?"

What a strong answer should cover:

  • String-concatenating (or template-literal-interpolating) user input directly into a SQL query string lets an attacker's input change the query's actual structure, not just supply a value — the database has no way to distinguish "data the query is looking for" from "additional SQL the attacker wrote," because by the time the database sees it, it's all just one string of SQL text.
  • 📌 Verified, not assumed — the exact answer to the prompt: a real vulnerable query, given the real payload ' OR '1'='1, genuinely became SELECT * FROM users WHERE username = '' OR '1'='1' — a condition that is always true for every row — and genuinely returned all users' rows, including password hashes, against a real database, not just the one requested user.
  • 📌 Verified, not assumed — the real fix: the identical malicious payload, against a real parameterized query (db.prepare("SELECT * FROM users WHERE username = ?").all(username)), genuinely returned zero rows — the database treated the entire malicious string as a single, literal value to search for (no username literally equals that whole string), never as additional SQL syntax.
  • 📌 Interview term: parameterized query (a.k.a. prepared statement) — the query's structure (with ? or named placeholders) is sent to the database separately from the actual values, which the database driver binds afterward as pure data — this is precisely why user input can never change the query's structure, verified directly above by the same malicious string having zero effect on the query's meaning.
  • A precise answer explicitly rejects "sanitize the input" (escaping special characters, stripping quotes) as the real fix: it is genuinely error-prone (a real, historical source of bypassable escaping bugs across many languages/frameworks) compared to parameterized queries, which structurally prevent the entire attack class rather than trying to filter every dangerous character pattern by hand — the precise, complete answer is "use parameterized queries," with escaping/sanitization as, at best, a weaker, incomplete fallback for situations that genuinely cannot use them (rare, and worth naming as the exception, not the rule).

Clarifying questions expected:

  • "Is the ORM/query builder in use here (if any) genuinely using parameterized queries under the hood for this specific call, or does it have an 'escape hatch' for raw SQL that was used here instead?" — a real, common way this vulnerability reappears even in a codebase that otherwise uses a safe ORM.
  • "Are there other queries in the codebase built via string concatenation/template literals the same way this one was?" — the vulnerable pattern verified above is easy to repeat elsewhere in a codebase, worth auditing broadly.

Code / implementation expected: Yes — a real injection genuinely dumping every user's row (including password hashes) via string concatenation, and the identical payload genuinely returning zero rows against a real parameterized query, is the concrete, convincing proof of exactly how the attack works and why the fix structurally prevents it.

nodejssecuritysql-injectiondatabase
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js/database security interviews. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. Both the attack and the fix below were actually run against a real (built-in `node:

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

A real SQL injection dumping every user's row via string concatenation, and a real parameterized query blocking it
const { DatabaseSync } = require("node:sqlite");
const db = new DatabaseSync(":memory:");
db.exec(`
  CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT);
  INSERT INTO users VALUES (1, 'alice', 'alice-secret-hash');
  INSERT INTO users VALUES (2, 'bob', 'bob-secret-hash');
`);

// VULNERABLE: string concatenation
function vulnerableLogin(username) {
  const query = `SELECT * FROM users WHERE username = '${username}'`;
  return db.prepare(query).all();
}

// SAFE: parameterized query
function safeLogin(username) {
  return db.prepare("SELECT * FROM users WHERE username = ?").all(username);
}

const maliciousInput = "' OR '1'='1";

console.log(vulnerableLogin(maliciousInput));
// [ { id: 1, username: 'alice', password: 'alice-secret-hash' },
//   { id: 2, username: 'bob',   password: 'bob-secret-hash' } ]   <- genuinely dumped ALL users

console.log(safeLogin(maliciousInput));
// []   <- genuinely zero rows, treated as one literal (non-matching) value
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 88 of 152 decoded in the Node.js track. One more won't hurt.

Back to track