CS 161, Lesson 35, in 50 slides. It presents code injection as the pattern in which data becomes code, in section 17.1, then SQL injection as its canonical instance in section 17.2, and the anatomy of an injection payload together with the OR 1=1 login bypass, in section 17.3. It covers escaping and why it is fragile in section 17.4, and parameterized queries as the real fix, since they separate code from data, in section 17.5. This is authorized security education using toy examples in a sandbox only, and it is anchored to textbook sections 17.1 to 17.5.
Subject: Computer Security · 81 slides · applied lesson
Open the interactive version of this deck · Homework for this lesson
Title
CS 161 · Lesson 35 of 45
code injection as a single principle · SQL injection as the canonical case · payload anatomy & the OR 1=1 bypass · escaping vs parameterized queries — authorized education, sandbox examples only
Objectives
', ;, OR 1=1, --) and build the classic login-bypass step by step.Warm-up
Discussion prompt
Before we open L35 · SQL Injection & Code Injection: without looking back, what was the main idea of L34 · Bitcoin: Identities, Transactions, Hash Chains & Proof of Work, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
CS 161, Lesson 34, in 51 slides. It presents Bitcoin as a digital currency that has the properties of physical money but NO trusted bank, in section 16.1, then public keys as identities in sections 16.2 and 16.3, and the signed transaction ledger with balances computed by replay, in sections 16.4 and 16.5. It covers the append-only hash chain and tamper detection in sections 16.6 and 16.7, and consensus by proof of work under the longest-chain rule, in sections 16.8 and 16.9. It is anchored to textbook sections 16.1 to 16.9.
Concept
Every example here runs against a toy, sandboxed database we control. We study attacks to defend systems — never to target machines you don't own. Unauthorized access is illegal under the CFAA and your campus policy.
Matching
Match the pairs
From An ethics note before we attack anything — match each one to what it actually does. The descriptions have been shuffled.
Why: The principle, The attack, The fix are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.
Section
Part 1 · §17.1 data becomes code
Concept
A program expects some input to be data — a course name, a search term. Code injection happens when an attacker crafts that input so the program treats it as code and runs it.
Code injection — A vulnerability in which attacker-controlled data crosses into a context where it is interpreted as program instructions rather than inert data. SQL injection is one instance; the same root cause hits shell commands, LDAP queries, and more.
Counterexample
Discussion prompt
A program expects some input to be data — a course name, a search term. Code injection happens when an attacker crafts that input so the program treats it as code and runs it.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Concept
Imagine a calculator site that evaluates whatever you type. Behind it, the server calls Python's eval on your input. Type 2+3 and the server runs eval("2+3") and returns 5.
user_input = request.args["expr"] # the attacker controls this
result = eval("" + user_input + "") # treats input as Python CODE
return str(result)As long as the input is a harmless arithmetic string, this looks fine. The problem is that eval does not distinguish 'arithmetic the user meant' from 'any Python the user wrote'.
Analogy
Discussion prompt
Explain §17.1 A toy calculator that runs eval by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Imagine a calculator site that evaluates whatever you type. Behind it, the server calls Python's eval on your input. Type 2+3 and the server runs eval("2+3") and returns 5.
Concept
Now the attacker submits a string that closes the arithmetic and tacks on its own statement. On a sandbox you'd see the server execute BOTH halves.
# attacker submits: 2+3"); os.system("rm -rf /
result = eval("2+3"); os.system("rm -rf /") # two statements now runThe eval happily evaluates 2+3, then the injected os.system("rm -rf /") runs — deleting files. The DATA the user typed became CODE the server executed.
Explain it
Discussion prompt
Explain §17.1 The same calculator, weaponized to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
The eval happily evaluates 2+3, then the injected os.system("rm -rf /") runs — deleting files. The DATA the user typed became CODE the server executed.
Ranking
Put in order
Put the moves of §17.1 Trace the eval injection into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The blank is the user's expr, concatenated straight into a string the interpreter will run.
Worked example
Start from the server template: eval("___")
Why: The blank is the user's expr, concatenated straight into a string the interpreter will run.
Drop in the benign input 2+3
Why: eval("2+3") is a single arithmetic expression — it returns 5 and nothing else happens.
| piece of malicious input | what the interpreter does with it |
|---|---|
| 2+3 | the (throwaway) arithmetic result |
| ") | CLOSES the string and the eval(...) call |
| ; | ENDS the statement, begins a new one |
| os.system("rm -rf / | the attacker's own statement; the template's trailing ") closes it |
Verify: two statements now run — the eval, then the attacker's os.system
Why: §17.1: the input's quote and semicolon were read as code structure, not data. This is the same mechanism SQL injection uses, just with a different interpreter.
Blank canvas
Draw it
Draw what §17.1 Trace the eval injection just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Intuition
The calculator used eval, but the pattern is universal. Anywhere a program builds an instruction string out of user input and hands it to an interpreter, injection is possible.
; rm -rf /).Ask yourself: what do all of these share? (An interpreter that can't tell the developer's intended structure from the attacker's injected structure.)
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'Injection is a database thing — if I'm not using SQL, I'm safe.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: §17.1: injection is ANY place user data becomes code.
A student: where can injection happen?
Why: §17.1: injection is ANY place user data becomes code. The toy eval calculator, a shell command built from input, an LDAP filter, an HTML page — all are injectable. SQL is just the most famous instance.
Trap
A student: 'Injection is a database thing — if I'm not using SQL, I'm safe.'
Treat injection as a SQL-only problem
Why: Wrong. §17.1: injection is ANY place user data becomes code. The toy eval calculator, a shell command built from input, an LDAP filter, an HTML page — all are injectable. SQL is just the most famous instance.
A student: where can injection happen?
Watch every interpreter boundary: SQL, shell, LDAP, eval, HTML/JS
Why: §17.1: the root cause is untrusted data crossing into a code context. Audit every spot where you build an instruction string from input, not just your SQL.
Section
Part 2 · §17.2 the evals table
Concept
A toy site stores course evaluations in a table evals(id, course, rating). A browser request GET /evals?course=cs61a asks for the ratings of one course.
| id | course | rating |
|---|---|---|
| 1 | cs61a | 4.8 |
| 2 | cs61b | 4.5 |
| 3 | cs70 | 4.7 |
Comparison
Comparison matrix
From §17.2 The setup: a course-evals site: refill the rating column from what you know. The rest of the table is as it appeared.
| id | course | rating |
|---|---|---|
| 1 | cs61a | 4.8 |
| 2 | cs61b | 4.5 |
| 3 | cs70 | 4.7 |
Concept
The server takes the course parameter and glues it into a query string. With course=cs61a, this produces exactly the query we want.
course = request.args["course"] # attacker controls this
query = "SELECT rating FROM evals WHERE course = '" + course + "'"
db.execute(query)For the intended input the resulting query is benign — the single quotes wrap cs61a as a string literal.
Concept
Now the attacker supplies a malicious course. Watch the same concatenation produce a totally different query.
-- INTENDED (course = cs61a)
SELECT rating FROM evals WHERE course = 'cs61a'
-- INJECTED (course = garbage'; SELECT password FROM passwords WHERE username = 'admin)
SELECT rating FROM evals WHERE course = 'garbage'; SELECT password FROM passwords WHERE username = 'admin'The attacker's ' closed the string, the ; ended the intended query, and a second query reads the admin password. The DATA became a new SQL statement.
Intuition
The SQL parser receives ONE big string and parses it from scratch. It has no idea which characters the developer wrote and which the attacker injected — they're all just text by the time the parser sees them.
So a quote in the user's input isn't 'data' to the parser; it's a real SQL quote that ends the string. From there, the rest of the input is parsed as SQL code.
Ask yourself: what gave the attacker power — the quote, or the concatenation? (The concatenation. The quote only matters because the input was glued into code the parser then re-reads.)
Step zero
Discussion prompt
§17.2 Trace how the injected query is built — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Start from the template: SELECT rating FROM evals WHERE course = '___'
Answer:
Worked example
Start from the template: SELECT rating FROM evals WHERE course = '___'
Why: The blank is where the user's course value is concatenated in — surrounded by the developer's single quotes.
Drop in the malicious value: garbage'; SELECT password FROM passwords WHERE username = 'admin
Why: The value contains its own quote and semicolon, so it won't stay inside the blank as inert data.
| piece of input | what the parser does with it |
|---|---|
| garbage | the (empty) result of the intended WHERE — a throwaway |
| ' | CLOSES the opening quote, ending the string literal |
| ; | ENDS the first query, begins a second statement |
| SELECT password FROM passwords WHERE username = 'admin | the attacker's own query; the trailing template quote closes its string |
Verify: the server runs two statements; the second leaks the admin password
Why: §17.2: because the value was concatenated into code, the parser reads its quote and semicolon as real SQL syntax, not data. That is SQL injection.
Concept
Once you can inject SQL, you're not limited to the evals table. The injected statement runs with the web app's database privileges — so it can touch any table that account can.
SELECT password FROM passwords) — data theft.UPDATE, INSERT) — tampering.DROP TABLE) — availability loss.This is why a single concatenation bug is so dangerous: it hands the attacker the app's full database power. (Least-privilege DB accounts shrink this blast radius — Part 5.)
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'I check that course is under 32 chars and alphanumeric-ish, so concatenating it into the query string is fine.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: §17.2: validation narrows inputs but the STRUCTURE can still be broken.
A student: what actually stops the injection?
Why: §17.2: validation narrows inputs but the STRUCTURE can still be broken. A short value like x' OR 1=1-- passes many length checks yet still injects. One overlooked field, one looser check, and you're vulnerable.
Trap
A student: 'I check that course is under 32 chars and alphanumeric-ish, so concatenating it into the query string is fine.'
Rely on length/format validation while still concatenating
Why: Wrong. §17.2: validation narrows inputs but the STRUCTURE can still be broken. A short value like x' OR 1=1-- passes many length checks yet still injects. One overlooked field, one looser check, and you're vulnerable.
A student: what actually stops the injection?
Stop concatenating user input into query CODE at all
Why: §17.5: the real fix separates code from data (parameterized queries). Validation is at best defense-in-depth — never the thing you rely on to prevent injection.
Section
Part 3 · §17.3 the OR 1=1 bypass
Concept
An injection payload isn't magic — every piece does one specific thing to the surrounding query. Take the leak payload from Part 2 apart.
garbage — a dummy value so the intended WHERE returns nothing; the attacker doesn't care about the original result.' — closes the opening quote, so the rest is parsed as CODE, not as the inside of a string.; — ends the intended query and starts a new one.Intuition
Without the ', everything you type stays trapped INSIDE the string literal — it's just a weird course name, treated as data. Harmless.
The moment you supply the matching ', you 'break out' of the string and your remaining characters land in code position, where the parser reads them as SQL keywords, operators, and statement separators.
Ask yourself: why is the quote the single most important character in most SQLi payloads? (It's the boundary between data and code — escape it and you've crossed from inert text into executable SQL.)
Concept
Login checks usually run a query and log you in if it returns more than zero rows — i.e. a matching user exists with that password.
SELECT username FROM users WHERE username = 'alice' AND password = 'password123'If that returns a row, the server concludes 'valid credentials' and grants access. The attacker's goal: make the WHERE clause true without knowing a password.
Sorting
Sort into buckets
These are the pieces of L35 · SQL Injection & Code Injection, out of order. Put each one back under the part of the lesson it belongs to.
Concept
The attacker types alice' OR 1=1 into the username field. The ' closes the username string, and OR 1=1 makes the WHERE always true.
-- username field: alice' OR 1=1
SELECT username FROM users WHERE username = 'alice' OR 1=1 AND password = '...'Tautology injection — An injected condition that is always true (like OR 1=1), forcing a WHERE clause to match every row regardless of the real data. The query returns rows, so a 'returns >0 rows = success' login is bypassed.
Definition probe
Sort into buckets
Every line below is part of the definition of Code injection or of Tautology injection — one or the other, never both. Put each where it belongs.
OR 1=1), forcing a WHERE clause to match every row regardless of the real data.; The query returns rows, so a 'returns >0 rows = success' login is bypassed.OR 1=1), forcing a WHERE clause to match every row regardless of the real data. The query returns rows, so a 'returns >0 rows = success' login is bypassed.Intuition
The login never compares your password to a stored one in code — it asks the database 'is there a row where username AND password match?' and trusts the row count as the verdict.
So if the attacker can make the WHERE return ANY row, the app reads that as 'valid credentials.' The tautology manufactures a row out of nothing.
Ask yourself: is the bug really in the password check, or in trusting a query the attacker can rewrite? (The latter — the WHERE clause itself is attacker-controllable through concatenation.)
Concept
There's still that dangling AND password = '...' to deal with. SQL's -- starts a comment: everything after it on the line is ignored. Append it and the password check vanishes.
-- username field: alice' OR 1=1--
SELECT username FROM users WHERE username = 'alice' OR 1=1-- AND password = '...'Everything from -- onward is a comment, so the password condition is gone entirely. The WHERE is just username = 'alice' OR 1=1 → always true → rows returned → logged in.
Ranking
Put in order
Put the moves of §17.3 Build the OR 1=1-- login bypass step by step into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The username blank is the field we control; the password blank we want to neutralize.
Worked example
Start from the template: WHERE username = '___' AND password = '___'
Why: The username blank is the field we control; the password blank we want to neutralize.
Inject alice' to close the username string and reach code position
Why: The ' ends the username literal so the next characters are parsed as SQL, not as part of the name.
Add OR 1=1 to force the WHERE to be true for every row
Why: 1=1 is always true; OR-ing it in means the WHERE matches regardless of the username or password — a tautology.
Append -- to comment out the rest of the query (the AND password check)
Why: -- begins a SQL comment; the trailing AND password = '...' is ignored, so no real password is ever compared.
-- final injected query (username = alice' OR 1=1--)
SELECT username FROM users WHERE username = 'alice' OR 1=1--' AND password = '...'
-- everything after -- is a comment; WHERE is 'alice' OR 1=1 => always trueVerify: the query returns rows, so the 'returns >0 rows = success' login grants access
Why: §17.3: the attacker supplied NO valid password and didn't even need a real username — the tautology plus the comment did all the work.
Error analysis
Annotate
Walk the callouts on §17.3 Build the OR 1=1-- login bypass step by step. Each one is a place this is easy to get subtly wrong.
' ends the username literal so the next characters are parsed as SQL, not as part of the name.Trap
A student: 'The bypass only works because alice is a real user — OR 1=1 just skips her password.'
Assume you need a valid username for OR 1=1 to work
Why: Wrong. §17.3: nobody' OR 1=1-- works just as well. The WHERE is true because of 1=1, not because any row matches username = 'nobody'. You need neither a real username NOR a password.
A student: what does the attacker actually need to know?
Recognize the tautology makes the WHERE true on its own
Why: §17.3: OR 1=1 matches every row regardless of the username string, and -- deletes the password check. The attack needs no credentials at all — only the injection point.
Section
Part 4 · §17.4 the escape-the-escape bypass
Concept
Escaping tells SQL to treat a special character as a literal piece of the string, not as syntax. An escaped quote \' does NOT end the string — it's just a quote character inside it.
Escaping — Replacing dangerous characters in user input with escaped versions (e.g. a quote ' becomes \') so the SQL parser reads them as ordinary string contents instead of as structure-changing syntax.
Concept
An escaper has to neutralize every character that can change a query's structure — and that list is longer than it first looks, which is exactly why it's easy to get wrong.
| character | why it's dangerous | escaped as |
|---|---|---|
| ' | closes a string literal -> reaches code position | \' |
| \ | the escape character itself; mishandle it and quotes survive | \\ |
| -- | begins a comment that deletes the rest of the query | (must be neutralized) |
| ; | chains a second statement | (must be neutralized) |
Miss any one — especially the backslash — and a crafted input slips through. The next slide shows exactly that.
Comparison
Comparison matrix
From §17.4 The dangerous characters to handle: refill the escaped as column from what you know. The rest of the table is as it appeared.
| character | why it's dangerous | escaped as |
|---|---|---|
| ' | closes a string literal -> reaches code position | \' |
| \ | the escape character itself; mishandle it and quotes survive | \\ |
| -- | begins a comment that deletes the rest of the query | (must be neutralized) |
| ; | chains a second statement | (must be neutralized) |
Concept
Run the attacker's ' through an escaper and the quote is neutralized — it no longer closes the string, so the whole payload stays trapped as data.
-- attacker input: garbage' ; SELECT ...
-- after escaping the quote -> \'
SELECT rating FROM evals WHERE course = 'garbage\' ; SELECT ...'
-- the \' is a literal quote; nothing breaks out of the stringWhen it works, escaping does block the attack. The trouble is making it work everywhere, every time, with no edge cases.
Intuition
Suppose your escaper only escapes quotes and forgets that the backslash is itself special. The attacker inputs \' — a backslash followed by a quote.
A naive escaper escapes only the quote, producing \\'. But the parser reads \\ as ONE literal backslash, which means the quote that follows is not escaped — it ends the string, and injection survives.
Ask yourself: what did the escaper forget? (To escape the escape character itself. Handling the backslash is exactly the kind of edge case that makes hand-rolled escapers leak.)
Step zero
Discussion prompt
§17.4 Walk the escape-the-escape bypass — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Attacker inputs the two characters: backslash, then quote (\')
Answer:
Worked example
Attacker inputs the two characters: backslash, then quote (\')
Why: The backslash is bait; the goal is to get a quote that survives unescaped into code position.
The naive escaper escapes only the quote -> backslash, backslash, quote (\\')
Why: It doubled the quote's escape but never escaped the attacker's original backslash.
| characters | how the parser reads them |
|---|---|
| \\ | one LITERAL backslash (the two backslashes escape each other) |
| ' | a REAL quote — nothing is escaping it now — so it CLOSES the string |
| …rest… | now in code position: injection continues |
Verify: the quote escaped past the escaper, so the payload injects anyway
Why: §17.4: one missed edge (the backslash) defeats the whole defense. This is why you must never write your own escaper — and why even library escaping is fragile if any query is missed.
Blank canvas
Draw it
Draw what §17.4 Walk the escape-the-escape bypass just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Concept
Escaping can work, but it is error-prone: it must be applied correctly to EVERY input on EVERY query, and it must handle every special character including the escape character.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'I escape every user input, so I'm completely safe from SQLi.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: §17.4: escaping is fragile — the escape-the-escape bypass survives a naive escaper, and a single missed escape call on any query reopens the hole.
A student: what's a defense that doesn't depend on perfect escaping?
Why: §17.4: escaping is fragile — the escape-the-escape bypass survives a naive escaper, and a single missed escape call on any query reopens the hole. 'Escape everything correctly, forever' is a promise code rarely keeps.
Trap
A student: 'I escape every user input, so I'm completely safe from SQLi.'
Treat escaping as a complete, sufficient defense
Why: Wrong. §17.4: escaping is fragile — the escape-the-escape bypass survives a naive escaper, and a single missed escape call on any query reopens the hole. 'Escape everything correctly, forever' is a promise code rarely keeps.
A student: what's a defense that doesn't depend on perfect escaping?
Use parameterized queries so input can NEVER reach code position
Why: §17.5: parameterization fixes the query structure before data is plugged in, so escaping correctness stops mattering. That's the real fix; escaping is just extra depth.
Section
Part 5 · §17.5 separate code from data
Concept
A parameterized query (prepared statement) sends the query with placeholders to the database, which compiles the structure first. Only THEN is the user input plugged in — strictly as data.
Parameterized query / prepared statement — A query whose SQL structure is fixed and compiled by the database before any user input is supplied. Inputs are passed separately as parameters and can never be reinterpreted as SQL code — preventing all SQL injection.
Concept
The difference is whether course is glued into the query string (code) or passed as a separate parameter (data).
# VULNERABLE: user input concatenated into the query CODE
query = "SELECT rating FROM evals WHERE course = '" + course + "'"
cursor.execute(query)
# SAFE: structure compiled first; course passed as DATA via ?
cursor.execute("SELECT rating FROM evals WHERE course = ?", (course,))In the safe version, the ? is a placeholder. Whatever course contains — quotes, semicolons, OR 1=1 — is bound as a single string value and never parsed as SQL.
Explain it
Discussion prompt
Explain §17.5 Vulnerable concatenation vs parameterized to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
The difference is whether course is glued into the query string (code) or passed as a separate parameter (data).
Intuition
With concatenation, the parser sees code and data mixed in one string and re-parses everything. With parameterization, the parser fixes the structure before it ever sees the input — so the input has no structural slot to fill.
A ' in the parameter is just a quote character in a value; a ; is just a semicolon character. There's no 'breaking out' because there's no string being re-parsed around the input.
Ask yourself: which Part-3 step does parameterization defeat first? (The closing '. The quote can never reach code position, so the whole payload collapses to a harmless literal course name.)
Analogy
Discussion prompt
Explain §17.5 Why this prevents ALL injection by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
A ' in the parameter is just a quote character in a value; a ; is just a semicolon character. There's no 'breaking out' because there's no string being re-parsed around the input.
Ranking
Put in order
Put the moves of §17.5 Rewrite the vulnerable login as parameterized into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. This is the query the OR 1=1-- bypass attacked in Part 3.
Worked example
Start from the vulnerable login that concatenated username and password
Why: This is the query the OR 1=1-- bypass attacked in Part 3.
# VULNERABLE
q = "SELECT username FROM users WHERE username = '" + u + "' AND password = '" + p + "'"
cursor.execute(q)
# PARAMETERIZED
cursor.execute(
"SELECT username FROM users WHERE username = ? AND password = ?",
(u, p)
)Feed the old attack u = alice' OR 1=1-- into the parameterized version
Why: The whole string is bound to the first ? as a literal username value.
| where alice' OR 1=1-- lands | result |
|---|---|
| concatenated (old) | parsed as SQL: tautology + comment => login bypassed |
| bound to ? (new) | a literal username value; no user is named that => 0 rows => login denied |
Verify: the injection string is searched for as a literal username and simply isn't found
Why: §17.5: because the structure was compiled before binding, the attacker's quote/keywords are inert data. Parameterization prevents the injection entirely.
Error analysis
Annotate
Walk the callouts on §17.5 Rewrite the vulnerable login as parameterized. Each one is a place this is easy to get subtly wrong.
? as a literal username value.Concept
Parameterized queries need database support, and there's no single universal placeholder syntax (?, %s, :name all appear across libraries). That's the only real downside.
In practice every modern library and database supports them. If yours somehow doesn't, the answer is to switch libraries, not to fall back to hand-escaping.
Counterexample
Discussion prompt
Parameterized queries need database support, and there's no single universal placeholder syntax (?, %s, :name all appear across libraries). That's the only real downside.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
In practice every modern library and database supports them. If yours somehow doesn't, the answer is to switch libraries, not to fall back to hand-escaping.
Concept
⊕ Supplemental — beyond §17.1–17.5. A few attack variants and defense-in-depth layers worth naming (you'd meet these in a web-security course).
UNION SELECT ... to pull data from other tables into the result.SLEEP).Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'My WAF blocks requests containing OR 1=1 and quotes, so I'm protected.' ⊕
It is wrong. Say what breaks — and say it before you turn the page.
Correct: ⊕ WAFs are bypassable (encodings, comments inside keywords like OR/**/1=1, case tricks).
A student: what belongs at the foundation?
Why: ⊕ WAFs are bypassable (encodings, comments inside keywords like OR/**/1=1, case tricks). A blocklist is a guess about every payload; attackers only need one it missed. Defense-in-depth, never the foundation.
Trap
A student: 'My WAF blocks requests containing OR 1=1 and quotes, so I'm protected.' ⊕
Rely on a WAF / blocklist as the primary defense
Why: Wrong. ⊕ WAFs are bypassable (encodings, comments inside keywords like OR/**/1=1, case tricks). A blocklist is a guess about every payload; attackers only need one it missed. Defense-in-depth, never the foundation.
A student: what belongs at the foundation?
Parameterize queries; add least-privilege DB users and a WAF as extra layers
Why: §17.5 + ⊕: parameterization removes the vulnerability at the source; least privilege limits the blast radius; a WAF catches noise. Layers on top of a fix, not instead of one.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'Input validation and escaping protect me just as well as parameterized queries — pick whichever.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: §17.5: only parameterization PREVENTS ALL injection by separating code from data structurally.
A student: how should you rank the defenses?
Why: §17.5: only parameterization PREVENTS ALL injection by separating code from data structurally. Validation narrows inputs and escaping is fragile — both can be bypassed or forgotten. They are not equivalent.
Trap
A student: 'Input validation and escaping protect me just as well as parameterized queries — pick whichever.'
Treat validation and escaping as equivalent to parameterization
Why: Wrong. §17.5: only parameterization PREVENTS ALL injection by separating code from data structurally. Validation narrows inputs and escaping is fragile — both can be bypassed or forgotten. They are not equivalent.
A student: how should you rank the defenses?
Parameterize first; treat validation/escaping as supplementary depth
Why: §17.5: parameterized queries are the only defense that makes attacker input structurally incapable of becoming code. Everything else is defense-in-depth layered on top.
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
eval happily evaluates 2+3, then the injected os.system("rm -rf /") runs — deleting files. The DATA the user typed became CODE the server executed.; Ask yourself: what do all of these share? (An interpreter that can't tell the developer's intended structure from the attacker's injected structure.); A toy site stores course evaluations in a table evals(id, course, rating). A browser request GET /evals?course=cs61a asks for the ratings of one course.Constraint
Discussion prompt
Run The injection-defense playbook with this step confiscated:
Don't trust validation alone: length/format checks narrow inputs but can't guarantee the query STRUCTURE stays intact.
Is it still possible? If it is, say what takes its place and what it costs you. If it is not, say exactly what that step was providing that nothing else does.
Hint: A step you can drop for free was never load-bearing. If you cannot drop it, name the thing that goes wrong the moment it is gone.
Answer:
' closes the string (data→code), ; chains a new statement, OR 1=1 is an always-true tautology, -- comments out the rest.execute("… WHERE course = ?", (course,))) — prevents ALL SQL injection.Pattern
' closes the string (data→code), ; chains a new statement, OR 1=1 is an always-true tautology, -- comments out the rest.execute("… WHERE course = ?", (course,))) — prevents ALL SQL injection.Edge cases
Discussion prompt
The injection-defense playbook works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".
Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.
Answer:
' closes the string (data→code), ; chains a new statement, OR 1=1 is an always-true tautology, -- comments out the rest.execute("… WHERE course = ?", (course,))) — prevents ALL SQL injection.Elimination
Eliminate the wrong options
Which statement is TRUE about this login and how to fix it?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: §17.3–17.5: the input anything' OR 1=1-- closes the username string with ', forces the WHERE true with the tautology OR 1=1, and comments out the AND password check with --, so the query returns rows and the 'rows>0 = success' login grants access — no valid username or password needed. The structural fix is a parameterized query (cursor.execute("… WHERE username = ? AND password = ?", (u, p))): the database compiles the query structure first, then binds the input as pure data, so the attacker's quote and keywords can never reach code position. That is the only defense that prevents ALL SQL injection.
Check
A toy site builds SELECT username FROM users WHERE username = '" + u + "' AND password = '" + p + "' by concatenation, and logs you in if it returns any rows. Think it through before choosing.
Check your understanding
Which statement is TRUE about this login and how to fix it?
Answer: A
Why: §17.3–17.5: the input anything' OR 1=1-- closes the username string with ', forces the WHERE true with the tautology OR 1=1, and comments out the AND password check with --, so the query returns rows and the 'rows>0 = success' login grants access — no valid username or password needed. The structural fix is a parameterized query (cursor.execute("… WHERE username = ? AND password = ?", (u, p))): the database compiles the query structure first, then binds the input as pure data, so the attacker's quote and keywords can never reach code position. That is the only defense that prevents ALL SQL injection.
Concept
Concept
Concept
', ;, OR 1=1, --) and the login bypass.Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — Code Injection — the General Principle · SQL Injection — the Canonical Case · Anatomy of a Payload · Defense 1 — Escaping (and why it's fragile) · Defense 2 — Parameterized Queries (the real fix). Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
You can now explain code injection as the data-becomes-code principle, trace a SQL injection on the toy evals table, dissect a payload and build the OR 1=1-- login bypass, explain why escaping is fragile (escape-the-escape), and use parameterized queries to separate code from data — the one defense that prevents ALL SQL injection. Everything here ran on a sandbox; never target systems you don't own.
| Idea | § | The one-line version |
|---|---|---|
| Code injection | 17.1 | Attacker DATA gets interpreted as CODE (SQL, shell, eval, HTML) |
| SQL injection | 17.2 | Concatenated input breaks the query: ' ends the string, ; chains a query |
| Payload anatomy | 17.3 | ' closes string, OR 1=1 always-true, -- comments out the rest |
| Login bypass | 17.3 | alice' OR 1=1-- => WHERE true => rows => logged in, no password |
| Escaping | 17.4 | Fragile: escape-the-escape bypass; one miss reopens it; don't roll your own |
| Parameterized queries | 17.5 | Compile structure first, bind input as data => prevents ALL injection |
| Defense-in-depth | 17.5 | ⊕ least-privilege DB user + WAF on TOP of parameterization |
Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.