Safeguard
DevSecOps

SQL Injection Cheat Sheet: Detection and Defensive Patterns

A defensive SQLi cheat sheet that shows how injection works conceptually, how to spot it in code and traffic, and the parameterization patterns that actually stop it.

Aisha Rahman
Security Analyst
5 min read

This SQLi cheat sheet is written for defenders: it explains how SQL injection works conceptually, how to recognize vulnerable code, and which fixes reliably close the hole, without handing anyone a ready-made attack payload. SQL injection has sat at or near the top of the OWASP risk lists for two decades because the root cause is stubborn: an application builds a SQL statement by gluing untrusted input into a query string, and the database cannot tell the difference between code the developer wrote and code an attacker smuggled in.

The one pattern behind every SQL injection

Almost every case reduces to string concatenation into a query. In pseudocode the vulnerable shape looks like this:

query = "SELECT * FROM users WHERE email = '" + userInput + "'"

Because userInput is dropped inside the quotes verbatim, an input containing a single quote breaks out of the string literal and the rest is parsed as SQL. That is the entire mechanism. Whether the payload aims to bypass a login, read another table, or trigger an error, they all rely on this failure to separate data from code.

The categories you will see referenced in scanners and reports are worth knowing by name:

  • In-band (classic) injection, where results come back in the same response.
  • Blind injection, where the app returns no data but behaves differently for true and false conditions.
  • Time-based blind injection, where the attacker infers answers from how long a query takes.
  • Out-of-band injection, which exfiltrates data over a separate channel such as DNS.

You do not need working exploits for any of these to defend against them. You need to recognize the vulnerable code and fix it.

How to spot vulnerable code

Grep is your first-pass detection tool. Look for string building that flows into a query executor. In Java, Statement.executeQuery(sql) where sql was assembled with + is a red flag; the safe API is PreparedStatement. In Python, an f-string or %-formatted string passed to cursor.execute() is the tell:

# Vulnerable: input is formatted directly into the SQL text
cursor.execute(f"SELECT * FROM orders WHERE id = {order_id}")

In PHP, watch for mysqli_query with concatenated $_GET or $_POST values. In Node.js, template literals passed to query() are the equivalent smell. ORMs are safer by default, but raw-query escape hatches (sequelize.query, queryset.raw, db.Exec with Sprintf) reintroduce the risk.

The fix that actually works: parameterized queries

Parameterized queries, also called prepared statements, are the definitive defense. You send the SQL text and the parameter values to the database separately, so the values are never parsed as SQL:

# Safe: the driver binds order_id as a value, not as SQL
cursor.execute("SELECT * FROM orders WHERE id = %s", (order_id,))
// Safe: PreparedStatement binds the parameter
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE email = ?");
ps.setString(1, email);
ResultSet rs = ps.executeQuery();

This is not escaping and it is not sanitization. It is a structural separation that makes injection impossible for the bound values, regardless of what characters they contain.

When you can't parameterize

Sometimes the untrusted value is an identifier, a table or column name, that cannot be bound as a parameter. Here you cannot use placeholders, so use an allowlist. Map the incoming value against a fixed set of permitted identifiers and reject anything that is not an exact match. Never build identifiers by escaping user input; escaping is the approach that keeps failing.

For ORDER BY direction, map input to the literal strings ASC or DESC yourself rather than passing it through.

Detection in running systems

Beyond code review, you can detect injection attempts and defects at runtime:

  • Web application firewalls flag classic injection strings in requests, though they are a mitigation, not a fix.
  • Database error monitoring catches the syntax errors that failed injection attempts produce.
  • Dynamic scanners probe endpoints with benign test inputs and watch for behavioral differences; our DAST product covers how that black-box testing fits into a pipeline.
  • Least-privilege database accounts limit blast radius: the app's user should not own tables or hold FILE and admin grants.

Defense in depth, in order of value

Parameterized queries first, always. Then least-privilege database accounts, input validation as a secondary control, and monitoring to catch what slips through. Input validation alone is not sufficient, plenty of valid data contains quotes and semicolons, but it does reduce noise and stop obviously malformed input early. The Safeguard Academy has a hands-on lab that walks through converting a concatenated query codebase to prepared statements.

FAQ

Is a SQLi cheat sheet only useful to attackers?

No. Defenders use the same taxonomy of injection types to recognize vulnerable patterns, write detection rules, and verify that fixes hold. Understanding how an attack class works is a prerequisite to preventing it.

Do parameterized queries stop all SQL injection?

They stop injection through bound parameter values completely. The remaining gap is dynamic identifiers such as table or column names, which cannot be parameterized; handle those with a strict allowlist.

Can an ORM make me immune to SQL injection?

Mostly, for its standard query methods. The risk returns whenever you use the ORM's raw-query escape hatch and concatenate input into it. Treat those raw paths with the same care as hand-written SQL.

Is input sanitization enough on its own?

No. Escaping and sanitization have a long history of bypasses. Use parameterized queries as the primary control and treat validation as a secondary, defense-in-depth layer.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.