Topic 39

SQL Injection and Why Parameters Matter

Safety

The Marquee's website has a search box: type a film title, see its screenings. One evening, somebody types this into it: '; DROP TABLE bookings; --. If the application was built carelessly, that visitor just deleted forty thousand bookings — without ever logging in, using nothing but a text field meant for film titles. The trick is called SQL injection, and it means exactly what it sounds like: a visitor injecting their own SQL into the application's queries.

Injection has sat at or near the top of the published rankings of web vulnerabilities for over two decades — not because it is clever, but because the mistake that enables it is so natural that generations of developers keep making it. The fix fits on this one page. Understanding it needs no fear, just a careful look at how text becomes SQL, so let's take that look calmly.

The Gluing Mistake

Here is how the careless version of the search feature gets built. The application has a query with a hole where the title goes, and it fills the hole by gluing the visitor's text into the SQL string, quotes and all:

Pseudocode — the vulnerable version. This is the mistake; do not build this
search_text = whatever the visitor typed in the box

sql = "SELECT title FROM films WHERE title = '" + search_text + "'"

rows = connection.run(sql)

Test it, and it works beautifully. A visitor types Night Bus, the gluing produces the following statement, and the right film comes back:

What the glued query looks like with an innocent search
SELECT title FROM films WHERE title = 'Night Bus';

This is why the mistake survives testing and code review: on every normal input it behaves perfectly. Nothing about it looks broken, because the flaw only appears on an input the developer never imagined — and attackers try millions of inputs the developer never imagined.

The Attack, Dissected

Now glue in the hostile input instead. The visitor typed '; DROP TABLE bookings; --, so after gluing, the application sends this to the database:

The same gluing, fed the hostile input — one search became two statements
SELECT title FROM films WHERE title = ''; DROP TABLE bookings; --';

Read the hostile input piece by piece, because each character has a job. The leading single quote closes the string the application opened — the search text is now over, as far as SQL is concerned. The semicolon ends the SELECT statement. What follows is no longer inside any string: DROP TABLE bookings now stands as a second, complete statement of its own. And the trailing -- starts a SQL comment, which silences the leftover quote the application glued on the end, so nothing trips a syntax error.

The visitor's text has escaped from value-land into statement-land. And here is the uncomfortable part: the database did nothing wrong. It received two well-formed statements over an authorized connection and, obedient as ever, ran them both. It has no way to know that half of that SQL was written by a stranger in a search box. Only the application knew where the text came from, and the application glued away that knowledge.

Parameters: Data Through the Data Door

The fix is not smarter gluing — it is no gluing. Every driver supports parameters: you write the query with a placeholder where the value goes, and hand the value over separately, as a second item. The two travel to the database side by side but never mix:

Pseudocode — the safe version: a placeholder in the SQL, the value passed separately
sql = "SELECT title FROM films WHERE title = ?"

rows = connection.run(sql, search_text)

The question mark is the placeholder (some engines write it $1 or :title — same idea). The database receives the query and the value through two separate channels, so it knows with certainty which part is the question and which part is merely data. The value gets compared against film titles, character by character, and that is all it can ever do. It arrived through the data door, and nothing that comes through the data door is ever executed as SQL.

Feed the hostile input to this version and the drama evaporates: the database searches for a film whose title is literally '; DROP TABLE bookings; --. The Marquee has never screened a film with that name, so the search returns zero rows. No error, no deletion, no story. The attack doesn't bounce off a shield; it simply never becomes an attack, because the text never becomes SQL.

The same hostile input, through two different doors
Glued into the querythe vulnerable path
The text becomes a statement: the quote closes the string, the semicolon ends the SELECT, and DROP TABLE bookings runs as real SQL. Forty thousand bookings, gone.
Passed as a parameterthe safe path
The text stays a value: the database searches for a film literally named '; DROP TABLE bookings; -- , finds none, and returns zero rows. No story.

An everyday picture, if you want one: a visitor form with a "reason for visit" field. Whatever someone writes in that box stays in the box — a receptionist files it; nobody reads the box aloud and obeys it as instructions. Gluing is the act of cutting the box out and pasting its contents into the building's rulebook. Parameters keep the box a box. That is the whole distinction, so let the form go and keep the terms: values through the data channel, never spliced into the statement.

The Rule with No Exceptions

The rule this page leaves you with has no asterisks: user input never becomes part of SQL text. Not in any language, not for any field, not because the input "is just a number", not this once because the deadline is tomorrow. Anything a user can type — search boxes, login forms, seat labels, email addresses — goes through parameters. The safe pattern costs nothing extra to write; the two versions on this page differ by a handful of characters.

You might wonder about escaping — writing code that hunts through the input and defuses the quotes by hand. Decades of bypass tricks (character encodings, engine quirks, corner cases in the escaping itself) have made hand-escaping a losing arms race. Parameters win not by being better hygiene but by being a different channel: there is no splice point to attack, because no splicing ever happens. And one reassurance for the next chapter of your career: the ORM libraries you'll meet in Topic 42 use parameters by default, which is one of their genuine virtues. The next page adds the second layer of defense — deciding what the website's account could destroy even if an injection ever got through.

Common Confusions
  • "I'd notice a malicious input." The glued version behaves perfectly on every normal input — that's exactly why it survives testing and review. The flaw is invisible until the one input you didn't imagine, and attackers try millions of them, automatically.
  • "Escaping the quotes myself is just as good." Hand-escaping is a losing arms race with decades of documented bypasses. Parameters aren't better hygiene — they're a different channel, with no splice point for an attacker to aim at.
  • "Injection is a hacker topic, not a beginner topic." It's a gluing topic. The mistake is made at exactly the level of SQL you now write, which is why it's taught here and not in some advanced security course.
  • "The database should refuse suspicious-looking queries." The database can't read intent — it received two well-formed statements over an authorized connection and did its job. Only the application knows which characters came from a visitor, so only the application can keep them in the data channel.
Why It Matters
  • Injection has ranked among the top published web-security risks for over twenty years and still breaches real companies. This one page is the difference between causing such a breach and preventing one.
  • "Data through the data door" is a security instinct that generalizes far beyond SQL — the same discipline protects every place where user text meets executable anything.

Knowledge Check

Why does the glued version pass testing and code review?

  • On every normal input it works flawlessly, so nothing looks wrong
  • Because reviewers rarely check whether the SQL syntax is valid
  • Because the database silently fixes dangerous queries during testing
  • Because the gluing happens in a hidden part of the application

In the hostile input '; DROP TABLE bookings; -- , what job does the leading single quote do?

  • It quietly starts a SQL comment that hides the whole rest of the input line
  • It closes the string the app opened, so what follows becomes live SQL
  • It ends the SELECT statement so a new one can begin
  • It's the character that actually deletes the bookings table

What does the parameter mechanism actually guarantee?

  • The driver carefully scans each value and blocks any dangerous characters
  • The query's results are hidden from untrusted visitors
  • The value reaches the database as data forever — it can never become SQL
  • Parameterized queries always run faster than glued ones

The hostile input is submitted to the parameterized search. What happens?

  • The database rejects the query with a security error
  • The bookings table is quietly deleted, but the SELECT still returns safely
  • The driver strips out the quote and semicolon before sending
  • The database searches for a film with that strange title and finds none

You got correct