EXERCISE 3 — "ORM + MongoDB, so injection isn't a concern" — rebuttal ===================================================================== THE CLAIM: "We switched to an ORM and MongoDB, so injection is no longer a concern." THE REBUTTAL (high level): ORMs and NoSQL make CLASSIC string-concatenated SQL injection RARER by making the safe path the default — but they do NOT remove injection. It's the same "less, not gone" story as XSS framework auto-escaping: the bug survives in the escape hatches and in any place untrusted input is spread into query STRUCTURE. Both technologies have well-documented residual injection classes. RESIDUAL RISKS IN THE ORM: 1. RAW-SQL ESCAPE HATCHES: every ORM has raw-query methods (sequelize.query, $queryRawUnsafe, .raw()/.extra(), find_by_sql, whereRaw, DB::raw, createNativeQuery). If user input is CONCATENATED into these rather than bound, it's full SQLi — "using the ORM" doesn't help. 2. RAW FRAGMENTS: whereRaw("age > " + input), where("col = #{x}") — injectable even within the ORM's API. 3. DYNAMIC IDENTIFIERS: ORMs can't parameterize table/column/ORDER BY names (Chapter 7). A user-chosen sort column passed through is injectable unless mapped via an ALLOWLIST. 4. OPERATOR/STRUCTURE INJECTION: ORMs/query builders that build queries from untrusted objects can let an attacker inject operators/structure (overlaps with NoSQL below). RESIDUAL RISKS IN MONGODB / NoSQL: 1. OPERATOR INJECTION: spreading req.body into a query lets an attacker send { "password": { "$ne": null } } -> always-true -> AUTH BYPASS (Exercise 2). Same root cause as ' OR '1'='1. 2. $where: accepts a JavaScript expression evaluated server-side; user input reaching $where can run arbitrary JS / enable injection and DoS. 3. $regex: user-controlled regexes enable blind-style inference (a NoSQL analogue of boolean extraction) and ReDoS. 4. Aggregation/pipeline and mapReduce constructs built from untrusted input can similarly be subverted. THE UNIFYING PRINCIPLE: - Every one of these is the SAME bug from Chapter 1: UNTRUSTED INPUT BECOMES PART OF THE QUERY'S STRUCTURE/LOGIC instead of staying inert DATA. The target (relational vs document DB) and syntax (' vs {$ne}) differ; the confusion of data and code is identical. Switching databases or adding an ORM changes the surface, not the principle. THE DEFENCES THAT STILL APPLY: - KEEP INPUT AS DATA, NEVER STRUCTURE: * SQL/ORM: use the structured ORM API or BOUND parameters in raw queries; never concatenate input into SQL or raw fragments. * Identifiers (column/table/sort): use an ALLOWLIST mapping user keys to known-good names — never user text directly. * NoSQL: VALIDATE/COERCE TYPES so a value can't arrive as an operator object (password must be a String, not an object); forbid $where; don't pass raw input to $regex; use schema enforcement (Mongoose). - DEFENCE IN DEPTH (Chapter 8) still applies: least-privilege DB account, input validation, and a WAF as a backstop — none a replacement for the above. - AUDIT METHOD: grep for the raw/unsafe methods (ORM) and for places request bodies/params are spread directly into queries (NoSQL), exactly like grepping XSS escape hatches. CONCLUSION: ORMs and NoSQL reduce injection rates but do not make injection "no longer a concern." The vulnerability persists in raw queries, dynamic identifiers, and operator/structure handling. The same data-vs-code discipline — keep input as data, parameterize, allowlist identifiers, validate types — is still required.