Skip to content
CWE-89A03:2021 – Injection

How Injection Happens

Why string-concatenated queries break: the database never sees your intent, only a finished string it must parse as code.

The Root Cause

SQL injection is not a database bug. It is a consequence of building a query by gluing strings together.

When your code does this:

"SELECT * FROM users WHERE name = '" + input + "'"

the database receives one finished string. It has no way to know which characters you wrote and which the user supplied. It parses the whole thing as SQL. If the user's text contains a quote, that quote closes the literal you opened, and everything after it is parsed as code, not data.

This is the entire vulnerability. Every technique on this site is a variation on that one fact.

Watching the Boundary Break

Take a lookup that finds a user by name. With ordinary input the query is exactly what the developer intended:

Benign input

SQL
-- input: alice
SELECT * FROM users WHERE name = 'alice'
--                              ^^^^^^^ one string literal, as intended

Input containing a quote

SQLVulnerable
-- input: alice' OR '1'='1
SELECT * FROM users WHERE name = 'alice' OR '1'='1'
--                              ^^^^^^^ literal ends early
--                                      ^^^^^^^^^^^^^^^ now parsed as SQL logic

-- The WHERE clause is now `name = 'alice' OR TRUE`, which matches every row.

Data Is Not Code

The fix is not "remove dangerous characters". The fix is to stop sending data through the code channel at all.

A prepared statement sends the query and the parameters over separate paths. The database parses the query text first, builds an execution plan, and only then binds your values into placeholder slots. At that point parsing is finished. A quote in the value is just a quote — there is no parser left running to be confused by it.

This is why parameterisation is the only complete defence. Escaping tries to neutralise input while still concatenating it; parameterisation removes the concatenation.

Concatenation vs. Parameterisation

Pythonlookup.pyVulnerable
# The value is baked into the query text before the driver ever sees it.
# By the time it reaches the database it is indistinguishable from code.
cursor.execute(
    "SELECT * FROM users WHERE name = '" + name + "'"
)

Why Escaping Is Not Enough

Manual escaping fails in ways that are easy to miss:

  • Numeric contexts have no quotes to escape. WHERE id = 5 has no delimiter. Escaping quotes does nothing; 5 OR 1=1 sails straight through.
  • Identifiers cannot be parameterised. Table names, column names, and ORDER BY targets are not values. No placeholder syntax accepts them, so developers concatenate — see Injection Contexts.
  • Character-set confusion. Historically, multi-byte encodings such as GBK allowed a crafted byte sequence to consume the backslash that mysql_real_escape_string had inserted, freeing the quote.
  • Second-order. Input escaped correctly on the way in is stored decoded, then concatenated unescaped somewhere else later. See Second-Order.

Every one of these is a way for a value to re-enter the code channel. Parameterisation closes the channel.

What Follows

Once you can inject, the practical questions become:

  1. Where does my input land?Injection Contexts
  2. How do I confirm it?Detection
  3. How do I get data out?UNION, Error-Based, Boolean-Blind, Time-Blind
  4. Which engine am I talking to? — the DBMS Reference section, since syntax diverges sharply

You can try the whole chain against a real database in the Sandbox.