Skip to content

injection

A query built from strings

Why concatenation loses, and why a bound parameter cannot.

A database does not receive your values. It receives a statement, as text, and parses it. Everything that happens after that is a consequence of what the parser found — and the parser has no idea which characters you wrote and which ones somebody sent you.

That is the whole of SQL injection, and it is worth sitting with for a moment, because almost every wrong mental model of this defect comes from imagining the database can tell the difference.

  • GET /orders/search?q=shipped

    An ordinary request. Watch the statement the store parsed, in the transcript.

This build's search handler puts the term into the query with a bound parameter. Send a quote in it and nothing interesting happens: the quote arrives at the database as a character in a value, because the statement was already parsed before the value was bound.

Now send the same quote to a handler that built its statement by joining strings. The quote ends the literal early, and everything after it is parsed as SQL — clauses the handler never wrote, in a statement it thought it controlled.

  • GET /orders/search?q=%27%20OR%20%271%27%3D%271

    A quote through a bound parameter. It is a character, and it matches nothing.

  • GET /orders?status=x%27%20OR%20%271%27%3D%271

    The same characters, against a concatenated statement. Compare the two transcripts.

Two things a parameter cannot do, and both of them are where the remaining injections live. It cannot be a table name, and it cannot be a column name — so a handler that lets you choose a sort column has to interpolate it, and the only defence left is a list of the columns that route allows.

A semicolon is the other one worth knowing. This store parses every statement in the text it is given and records how many it found, so an input that added a second statement is visible as a number rather than as a guess about what the response looked like.

  • GET /orders?status=x%27%3B%20DELETE%20FROM%20notes%20WHERE%20%271%27%3D%271

    Look at the statement count in the transcript, not at the response.

This build

shop.example.com

Nothing sent yet. Every request goes to the application in this tab and nowhere else.