Choose your stack

Node.js · Express

Fix SQL injection in Node.js and Express

Bind every value: put ? placeholders in the SQL and pass the values to node:sqlite, as in db.prepare(sql).all(name, status).

Open the fix card
Python · Flask

Fix SQL injection in Python and Flask

Keep values out of the SQL text: use ? placeholders and pass a tuple, as in db.execute(sql, (name, status)).

Open the fix card
Java · Spring Boot

Fix SQL injection in Java and Spring Boot

Bind values with NamedParameterJdbcTemplate: write :name placeholders and pass a MapSqlParameterSource (or use JdbcTemplate with ? and arguments).

Open the fix card
C# · ASP.NET Core

Fix SQL injection in C# and ASP.NET Core

Keep CommandText constant and bind values: write $name placeholders and call command.Parameters.AddWithValue("$name", name).

Open the fix card
TypeScript · Next.js

Fix SQL injection in TypeScript and Next.js

Bind every value: put ? placeholders in the SQL and pass the values to the driver, as in db.prepare(sql).all(name, status), inside the route handler's data layer.

Open the fix card
Python · FastAPI

Fix SQL injection in Python and FastAPI

Pass values as bound parameters: text("... WHERE c.name = :customer") with session.execute(sql, {"customer": customer}), or build the query with select() and column comparisons.

Open the fix card
Python · Django

Fix SQL injection in Python and Django

Let the ORM build the query: Order.objects.filter(customer__name=name, status=status) sends both values as bound parameters.

Open the fix card
Go · net/http

Fix SQL injection in Go and net/http

Keep the SQL text constant and pass values as arguments: db.QueryContext(ctx, "... WHERE c.name = ?

Open the fix card
Ruby · Rails

Fix SQL injection in Ruby on Rails

Pass values to Active Record as hash conditions or placeholders: where(customers: { name: name }, status: status) or where("customers.name = ?", name).

Open the fix card
PHP · Laravel

Fix SQL injection in PHP and Laravel

Let the query builder bind values: DB::table('orders as o')->where('c.name', $name)->where('o.status', $status) sends both as PDO parameters, and raw SQL takes ? placeholders with a bindings array.

Open the fix card

How it happens

  • The code builds the SQL string by concatenation, interpolation or formatting, so the database parses user data as SQL.
  • A quote in the input closes the string literal and the rest becomes SQL: OR '1'='1 widens the WHERE clause, UNION SELECT appends rows from elsewhere, and -- comments out the rest of the line, such as a status filter.
  • Identifiers are a second route. Column names and ASC/DESC cannot be bound, so code that pastes a sort parameter into ORDER BY lets an attacker sort by an expression, and the row order then reveals hidden data one yes/no question at a time.
  • Hand-made fixes fail. Escaping rules differ between databases: backslash escaping is a MySQL habit, but SQLite, like standard SQL, treats a backslash as an ordinary character, so the 'escaped' quote still closes the string. Keyword blocklists miss payloads that need none of the blocked words.

Fix principles

  • Keep the SQL text constant and pass every value as a bound parameter through the driver's placeholder API.
  • For parts of the SQL that cannot be parameters (column and table names, sort direction), map the caller's choice to a fixed fragment from an allowlist and reject everything else with a 400.
  • Allowlist only what the caller may see: sorting by a hidden column leaks its order even when the column is never returned.
  • Do not escape by hand or filter keywords; both depend on the SQL dialect and on predicting every payload.
  • Check types before binding: a query-string value can be missing, repeated or not a string.
  • Keep database error text away from clients and give the application's database account only the privileges it needs, so a query you missed does less damage.

How the cards prove it

  • Run each card's tests in three modes: the fixed implementation must pass, and the vulnerable and the plausible-but-wrong implementations must fail. run-all.mjs does this for every card.
  • The regression tests send a tautology, a UNION, a comment and a sort-expression payload and require that no extra rows come back or that the request is rejected with 400. Functional tests keep O'Brien, non-ASCII names and empty results working.
  • Static analysis helps but is uneven. File-local taint rules miss SQL built in a separate data-access module, and CodeQL does not model node:sqlite yet; each card's scannerRules lists what was checked and what it found.

References

  1. OWASP SQL Injection Prevention Cheat Sheet
  2. OWASP Query Parameterization Cheat Sheet
  3. OWASP Top 10:2025 A05 Injection
  4. CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
  5. CAPEC-66: SQL Injection
  6. SQLite: SQL language expressions (string literals and backslashes)
  7. MySQL 8.4: String literals (backslash escapes and NO_BACKSLASH_ESCAPES)

Attack patterns:

Practice in the browser

Guides on this weakness

Recipe

Fix SQL injection in Python sqlite3

Bind query values separately from SQL syntax, then prove the fix preserves legitimate behavior and blocks an injection probe.

Guide

Combine static and dynamic tests around a verified finding

Use analysis tools to find evidence, then retain an application regression that demonstrates the failure and the fix.

Guide

Keep generated output out of executable contexts

Choose safe rendering and narrow operations so model text cannot become browser code, database syntax or an unrestricted command.

Case study

Case study: MOVEit Transfer mass exploitation (CVE-2023-34362)

How one unauthenticated SQL injection in an internet-facing file transfer product let the Cl0p group steal data from thousands of organizations in days in 2023, how defenders found the web shell, and the vendor and customer controls that would have limited it.

Reference

Threat pattern library for threat modeling

Thirty-two recurring threat patterns grouped by identity, authorization, parsers, concurrency, integrations, data exposure, supply chain, availability and AI agents, each with where it shows up on a data-flow diagram, mitigations, a verification step and STRIDE, CWE and CAPEC links, plus a YAML catalog and a script that lists patterns per diagram element.

Guide

Apply CISA Secure by Design: principles, pledge goals and evidence

What CISA's Secure by Design guidance and pledge ask of software manufacturers as of October 2026, and how to turn each of the three principles, the seven pledge goals and the Product Security Bad Practices into engineering actions, metrics and evidence.