L35 · SQL Injection & Code Injection

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

What this lesson covers

The lesson, slide by slide

1. When Data Becomes Code

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

2. By the end of this lesson you can…

Objectives

  1. Explain code injection as the single principle behind SQLi, shell, and LDAP attacks: attacker-controlled DATA gets interpreted as CODE.
  2. Trace a SQL injection on a toy evals table — show the intended query versus the injected query side by side.
  3. Dissect an injection payload piece by piece (', ;, OR 1=1, --) and build the classic login-bypass step by step.
  4. Explain why escaping is fragile — including the escape-the-escape bypass — and why you must never roll your own.
  5. Use parameterized queries / prepared statements to separate code from data, the one defense that prevents ALL SQL injection.

3. What survived from L34 · Bitcoin: Identities, Transactions, Hash Chains &…?

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.

4. An ethics note before we attack anything

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.

The principle
§17.1 data interpreted as code
The attack
§17.2–17.3 SQLi & login bypass
The fix
§17.4–17.5 escaping, then parameterization

5. Which is which: An ethics note before we attack anything

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.

  • c1. The principle
  • c2. The attack
  • c3. The fix
  • b1. §17.1 data interpreted as code
  • b2. §17.2–17.3 SQLi & login bypass
  • b3. §17.4–17.5 escaping, then parameterization

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.

6. Code Injection — the General Principle

Section

Part 1 · §17.1 data becomes code

7. §17.1 The one idea: data interpreted as 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.

8. Break it if you can: §17.1 The one idea: data interpreted as code

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.

9. §17.1 A toy calculator that runs eval

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'.

10. By analogy: §17.1 A toy calculator that runs eval

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.

11. §17.1 The same calculator, weaponized

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 run

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.

12. Teach it back: §17.1 The same calculator, weaponized

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.

13. What has to happen first: §17.1 Trace the eval injection

Ranking

Put in order

Put the moves of §17.1 Trace the eval injection into the order they have to happen.

  1. Start from the server template: eval("___")
  2. Drop in the benign input 2+3
  3. Verify: two statements now run — the eval, then the attacker's os.system

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.

14. §17.1 Trace the eval injection

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 inputwhat the interpreter does with it
2+3the (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.

15. Draw the shape of it: §17.1 Trace the eval injection

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.

16. §17.1 One principle, many targets

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.

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.)

17. Something is wrong here: 'only SQL is injectable'

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.

18. Trap: 'only SQL is injectable'

Trap

The 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.

The fix

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.

19. SQL Injection — the Canonical Case

Section

Part 2 · §17.2 the evals table

20. §17.2 The setup: a course-evals site

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.

idcourserating
1cs61a4.8
2cs61b4.5
3cs704.7

21. Fill in: rating for §17.2 The setup: a course-evals site

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.

idcourserating
1cs61a4.8
2cs61b4.5
3cs704.7

22. §17.2 The server builds the query by concatenation

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.

23. §17.2 Intended query vs injected query

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.

24. §17.2 Why concatenation is the root cause

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.)

25. Plan first: §17.2 Trace how the injected query is built

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:

  1. Start from the template: SELECT rating FROM evals WHERE course = '___'
  2. Drop in the malicious value: garbage'; SELECT password FROM passwords WHERE username = 'admin
  3. Verify: the server runs two statements; the second leaks the admin password

26. §17.2 Trace how the injected query is built

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 inputwhat the parser does with it
garbagethe (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 = 'adminthe 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.

27. §17.2 What the attacker can reach

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.

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.)

28. Something is wrong here: 'validating length/format makes concatenation safe'

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.

29. Trap: 'validating length/format makes concatenation safe'

Trap

The 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.

The fix

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.

30. Anatomy of a Payload

Section

Part 3 · §17.3 the OR 1=1 bypass

31. §17.3 Each character has a job

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.

32. §17.3 Why the closing quote is the hinge

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.)

33. §17.3 The classic target: a login query

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.

34. Where does each piece belong: L35 · SQL Injection & Code Injection

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.

Code Injection — the General Principle
§17.1 The one idea: data interpreted as code; §17.1 A toy calculator that runs eval; §17.1 The same calculator, weaponized
SQL Injection — the Canonical Case
§17.2 The setup: a course-evals site; §17.2 The server builds the query by concatenation; §17.2 Intended query vs injected query
Anatomy of a Payload
§17.3 Each character has a job; §17.3 Why the closing quote is the hinge; §17.3 The classic target: a login query
s1
Code Injection — the General Principle is where L35 · SQL Injection & Code Injection puts §17.1 The one idea: data interpreted as code, §17.1 A toy calculator that runs eval, §17.1 The same calculator, weaponized. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
SQL Injection — the Canonical Case is where L35 · SQL Injection & Code Injection puts §17.2 The setup: a course-evals site, §17.2 The server builds the query by concatenation, §17.2 Intended query vs injected query. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
Anatomy of a Payload is where L35 · SQL Injection & Code Injection puts §17.3 Each character has a job, §17.3 Why the closing quote is the hinge, §17.3 The classic target: a login query. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

35. §17.3 The tautology: OR 1=1

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.

36. Take the definitions apart: Code injection vs Tautology injection

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.

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.
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.
b1
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.
b2
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.

37. §17.3 Why 'rows > 0' is the soft spot

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.)

38. §17.3 The cleaner version: the -- comment

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.

39. What has to happen first: §17.3 Build the OR 1=1-- login bypass step by step

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.

  1. Start from the template: WHERE username = '___' AND password = '___'
  2. Inject alice' to close the username string and reach code position
  3. Add OR 1=1 to force the WHERE to be true for every row
  4. Append -- to comment out the rest of the query (the AND password check)
  5. Verify: the query returns rows, so the 'returns >0 rows = success' login grants access

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.

40. §17.3 Build the OR 1=1-- login bypass step by step

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 true

Verify: 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.

41. Inspect it line by line: §17.3 Build the OR 1=1-- login bypass step…

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.

  • The username blank is the field we control; the password blank we want to neutralize.
  • The ' ends the username literal so the next characters are parsed as SQL, not as part of the name.
  • 1=1 is always true; OR-ing it in means the WHERE matches regardless of the username or password — a tautology.

42. Trap: 'OR 1=1 still needs a real account'

Trap

The 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.

The fix

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.

43. Defense 1 — Escaping (and why it's fragile)

Section

Part 4 · §17.4 the escape-the-escape bypass

44. §17.4 What escaping does

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.

45. §17.4 The dangerous characters to handle

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.

characterwhy it's dangerousescaped 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.

46. Fill in: escaped as for §17.4 The dangerous characters to handle

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.

characterwhy it's dangerousescaped 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)

47. §17.4 Escaping the leak payload

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 string

When it works, escaping does block the attack. The trouble is making it work everywhere, every time, with no edge cases.

48. §17.4 The escape-the-escape problem

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.)

49. Plan first: §17.4 Walk the escape-the-escape bypass

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:

  1. Attacker inputs the two characters: backslash, then quote (\')
  2. The naive escaper escapes only the quote -> backslash, backslash, quote (\\')
  3. Verify: the quote escaped past the escaper, so the payload injects anyway

50. §17.4 Walk the escape-the-escape bypass

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.

charactershow 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.

51. Draw the shape of it: §17.4 Walk the escape-the-escape bypass

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.

52. §17.4 The takeaway on escaping

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.

53. Something is wrong here: 'escaping inputs fully prevents SQL injection'

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.

54. Trap: 'escaping inputs fully prevents SQL injection'

Trap

The 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.

The fix

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.

55. Defense 2 — Parameterized Queries (the real fix)

Section

Part 5 · §17.5 separate code from data

56. §17.5 Compile first, then plug in 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.

57. §17.5 Vulnerable concatenation vs parameterized

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.

58. Teach it back: §17.5 Vulnerable concatenation vs parameterized

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).

59. §17.5 Why this prevents ALL injection

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.)

60. By analogy: §17.5 Why this prevents ALL injection

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.

61. What has to happen first: §17.5 Rewrite the vulnerable login as parameterized

Ranking

Put in order

Put the moves of §17.5 Rewrite the vulnerable login as parameterized into the order they have to happen.

  1. Start from the vulnerable login that concatenated username and password
  2. Feed the old attack u = alice' OR 1=1-- into the parameterized version
  3. Verify: the injection string is searched for as a literal username and simply isn't found

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.

62. §17.5 Rewrite the vulnerable login as parameterized

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-- landsresult
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.

63. Inspect it line by line: §17.5 Rewrite the vulnerable login as…

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.

  • This is the query the OR 1=1-- bypass attacked in Part 3.
  • The whole string is bound to the first ? as a literal username value.
  • §17.5: because the structure was compiled before binding, the attacker's quote/keywords are inert data. Parameterization prevents the injection entirely.

64. §17.5 The one downside: compatibility

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.

65. Break it if you can: §17.5 The one downside: compatibility

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.

66. §17.5 ⊕ Beyond this textbook: more SQLi & defenses

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).

67. Something is wrong here: 'a WAF or blocklist alone stops SQLi'

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.

68. Trap: 'a WAF or blocklist alone stops SQLi'

Trap

The 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.

The fix

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.

69. Something is wrong here: 'validation/escaping is as good as parameterization'

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.

70. Trap: 'validation/escaping is as good as parameterization'

Trap

The 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.

The fix

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.

71. Which of these survive contact with L35 · SQL Injection & Code Injection?

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.

Holds up
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.; 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.
Breaks
A student: 'Injection is a database thing — if I'm not using SQL, I'm safe.'; A student: 'I check that course is under 32 chars and alphanumeric-ish, so concatenating it into the query string is fine.'
sound
These are stated as this lesson states them — each one survives the edge cases L35 · SQL Injection & Code Injection puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

72. Without one step: The injection-defense playbook

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:

  1. Name the root cause: attacker-controlled DATA reaching a context where it's interpreted as CODE (SQL, shell, LDAP, eval, HTML).
  2. Spot the injection point: any query/command built by CONCATENATING user input into an instruction string.
  3. Read the payload: ' closes the string (data→code), ; chains a new statement, OR 1=1 is an always-true tautology, -- comments out the rest.
  4. Don't trust validation alone: length/format checks narrow inputs but can't guarantee the query STRUCTURE stays intact.
  5. Escaping is fragile: never roll your own (escape-the-escape), and one missed call reopens the hole — defense-in-depth only.
  6. Parameterize as the fix: compile structure first, bind input as data (execute("… WHERE course = ?", (course,))) — prevents ALL SQL injection.
  7. ⊕ Add depth: least-privilege DB accounts and a WAF on TOP of parameterization, never instead of it.

73. The injection-defense playbook

Pattern

  1. Name the root cause: attacker-controlled DATA reaching a context where it's interpreted as CODE (SQL, shell, LDAP, eval, HTML).
  2. Spot the injection point: any query/command built by CONCATENATING user input into an instruction string.
  3. Read the payload: ' closes the string (data→code), ; chains a new statement, OR 1=1 is an always-true tautology, -- comments out the rest.
  4. Don't trust validation alone: length/format checks narrow inputs but can't guarantee the query STRUCTURE stays intact.
  5. Escaping is fragile: never roll your own (escape-the-escape), and one missed call reopens the hole — defense-in-depth only.
  6. Parameterize as the fix: compile structure first, bind input as data (execute("… WHERE course = ?", (course,))) — prevents ALL SQL injection.
  7. ⊕ Add depth: least-privilege DB accounts and a WAF on TOP of parameterization, never instead of it.

74. Where does it stop working: The injection-defense playbook

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:

  1. Name the root cause: attacker-controlled DATA reaching a context where it's interpreted as CODE (SQL, shell, LDAP, eval, HTML).
  2. Spot the injection point: any query/command built by CONCATENATING user input into an instruction string.
  3. Read the payload: ' closes the string (data→code), ; chains a new statement, OR 1=1 is an always-true tautology, -- comments out the rest.
  4. Don't trust validation alone: length/format checks narrow inputs but can't guarantee the query STRUCTURE stays intact.
  5. Escaping is fragile: never roll your own (escape-the-escape), and one missed call reopens the hole — defense-in-depth only.
  6. Parameterize as the fix: compile structure first, bind input as data (execute("… WHERE course = ?", (course,))) — prevents ALL SQL injection.
  7. ⊕ Add depth: least-privilege DB accounts and a WAF on TOP of parameterization, never instead of it.

75. Rule out three: Checkpoint — which defense, and what does the…

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.

  • A. Entering u = anything' OR 1=1-- logs the attacker in even with no valid account, and ONLY parameterized queries prevent ALL such injection by separating code from data.
  • B. Correctly escaping every input is fully sufficient and is as safe as a parameterized query.
  • C. Validating that the username is alphanumeric and under 32 characters reliably stops the injection.
  • D. The OR 1=1 bypass only works if the attacker supplies a real, existing username.

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.

76. Checkpoint — which defense, and what does the payload do?

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?

  • A. Entering u = anything' OR 1=1-- logs the attacker in even with no valid account, and ONLY parameterized queries prevent ALL such injection by separating code from data. (correct)
  • B. Correctly escaping every input is fully sufficient and is as safe as a parameterized query.
  • C. Validating that the username is alphanumeric and under 32 characters reliably stops the injection.
  • D. The OR 1=1 bypass only works if the attacker supplies a real, existing username.

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.

Why B tempts people
Escaping is fragile, not equivalent. The escape-the-escape bypass (input \' defeating a naive escaper) and any single missed escape call reopen the hole. Only parameterization structurally separates code from data, so it — not escaping — prevents all injection.
Why C tempts people
Validation narrows inputs but cannot guarantee the query's structure. A short value like x' OR 1=1-- can pass length and loose format checks and still inject. Validation is defense-in-depth, never the thing that prevents injection.
Why D tempts people
OR 1=1 is a tautology — it makes the WHERE true for every row regardless of the username string, so nobody' OR 1=1-- works just as well. The attacker needs neither a real username nor a password, only the injection point.

77. Misconceptions to retire

Concept

78. Synthesis — injection is the data-becomes-code pattern

Concept

79. Primary sources & where to read more

Concept

80. Connect it up: L35 · SQL Injection & Code Injection

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.

81. Recap — Lesson 35

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 injection17.1Attacker DATA gets interpreted as CODE (SQL, shell, eval, HTML)
SQL injection17.2Concatenated input breaks the query: ' ends the string, ; chains a query
Payload anatomy17.3' closes string, OR 1=1 always-true, -- comments out the rest
Login bypass17.3alice' OR 1=1-- => WHERE true => rows => logged in, no password
Escaping17.4Fragile: escape-the-escape bypass; one miss reopens it; don't roll your own
Parameterized queries17.5Compile structure first, bind input as data => prevents ALL injection
Defense-in-depth17.5⊕ least-privilege DB user + WAF on TOP of parameterization

Sources

  1. CS 161 Computer Security Textbook §17.1–17.5 — Wagner, Weaver, Kao et al., UC Berkeley — code injection as attacker-controlled data interpreted as code (§17.1), SQL injection as the canonical example with the evals table (§17.2), injection-payload anatomy and the OR 1=1 / -- login bypass (§17.3), escaping and the escape-the-escape bypass (§17.4), and parameterized queries / prepared statements as the defense that separates code from data (§17.5)
  2. OWASP SQL Injection Prevention Cheat Sheet — OWASP Foundation — parameterized queries as the primary defense, with input validation and escaping as secondary defense-in-depth
  3. A Classification of SQL-Injection Attacks and Countermeasures — W. G. J. Halfond, J. Viegas & A. Orso, Proc. IEEE Int'l Symposium on Secure Software Engineering (ISSSE) 2006 — taxonomy of SQLi attack types (tautologies, UNION, blind, second-order) and defensive techniques
  4. CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') — MITRE Common Weakness Enumeration — the canonical catalog entry for SQL injection

Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108