Loading
0x70Lesson 8 of 15

Recognize common web risks

Keep data separate from code: parameterized queries, output encoding and allowlists.

22 min 7-question quiz 1 code exercise
By the end of this lesson you can
  • Explain why mixing untrusted data into code causes injection
  • Use parameterized queries and allowlists
  • Encode output for the context it appears in

Web applications constantly cross trust boundaries: text typed in a browser travels to a server, into a database query, back into an HTML page. The root of a whole family of bugs - injection (OWASP A05:2025) and cross-site scripting - is the same: untrusted data gets treated as code.

If a program builds a database query by gluing user input into the SQL text, a cleverly chosen input can change the query’s meaning, not just its values. The fix is to never mix the two: send the query and the values separately.

Unsafe and safe queries
1# Unsafe: the input becomes part of the SQL text
2cursor.execute("SELECT * FROM users WHERE name = '" + name + "'")
3
4# Safe: the query and the value travel separately
5cursor.execute("SELECT * FROM users WHERE name = ?", (name,))

With a parameterized query (also called a prepared statement), the database receives the SQL with placeholders (? or %s or :name) and the values on their own. A value can contain quotes, keywords or anything else - it will only ever be compared as data. Every mainstream database library supports this; ORMs do it for you.

Some parts of a query can’t be parameters - table names, column names, sort direction. For those, use an allowlist: accept only values from a fixed list you wrote, and fall back to a safe default otherwise.

Practice: attack a login form

Try it

Up to 15 XP

Break into a simulated app with SQL injection

Watch the query change as you type. First, sign in normally with username ana and password blue-kettle-42. Then try a single quote in the username and see what the error tells an attacker.

Now complete the goals. Use the hints if you’re stuck.

A simulation: no real database or network is involved.

Sign in to Acme Store
SELECT * FROM users WHERE username = '' AND password = ''
  • Sign in as admin without the password
  • Make the query match every user
  • Turn on parameters and watch an attack fail

Try it

Up to 15 XP

Safe or unsafe?

Decide whether each snippet keeps untrusted data separate from code.

0 of 6 sortedScore 0/0
  • “cursor.execute("SELECT * FROM users WHERE email = %s", (email,))”

  • “cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")”

  • “order = sort if sort in {"name", "price"} else "name"”

  • “os.system("convert " + uploaded_filename + " out.png")”

  • “subprocess.run(["convert", uploaded_filename, "out.png"])”

  • “element.innerHTML = "Hello " + username”

Encoding output

The same idea applies when showing data in a page: to display a comment as text, characters that mean something in HTML (<, >, &, quotes) must be converted to entities like &lt;. Templating engines such as Jinja, React and most others do this automatically - the danger comes from switching it off (|safe, dangerouslySetInnerHTML, innerHTML).

Key takeaways

  • Injection happens when untrusted data is interpreted as code.

  • Use parameterized queries, argument lists and allowlists to keep code and data apart.

  • Encode output for its context; keep the framework’s auto-escaping on.

Lesson quiz

7 questions · pass with 5 correct · up to 50 XP

Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.

Practice: write Python

Write Python in the editor and run it against sample inputs. Python runs locally in your browser using a WebAssembly runtime.

Exercise 1

Allowlist a sort column

+25 XP

Column names can’t be query parameters. Each input line is column direction requested by a user. Print ORDER BY COLUMN DIRECTION where the column must be one of name, price, created_at (otherwise use created_at) and the direction ASC or DESC, case-insensitive (otherwise DESC).

  • Valid and invalid requests
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Questions about this lesson

Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.

Loading posts…