Lab environment & Burp setup
PortSwigger's Web Security Academy gives you disposable lab instances — a real application running with a real vulnerability, not a VM you set up yourself. Each lab has a defined goal: extract a specific piece of data, bypass a login, escalate a privilege. The objective is on the page before you start.
The setup is minimal: Burp Suite Community, Chromium through Burp's embedded browser (or your own with the proxy set to 127.0.0.1:8080), and Burp's intercept on. The embedded browser skips the certificate dance, which saves five minutes every session.
Lab 1 — Retrieve hidden data (WHERE clause bypass)
The first lab is a shopping site with a category filter. The URL looks like this:
Appending a single quote (') to the value causes a 500. That's the injection point confirmed. The application's query is roughly SELECT * FROM products WHERE category = 'Gifts' AND released = 1. The released = 1 condition hides unreleased products.
The payload to expose them all:
Lab 2 — Subvert application logic (login bypass)
The login form accepts a username and password. The backend query is likely SELECT * FROM users WHERE username='...' AND password='...'. If any row is returned, the login succeeds.
To log in as administrator without the password:
The database finds the administrator row, returns it, and the application grants access. No password required.
Labs 3–4 — UNION-based data extraction
UNION injection lets you append a second SELECT to the original query and get its output in the response. Before you can do that you need to know two things: how many columns the original query returns, and which columns render text.
Step 1: enumerate columns
Step 2: find text-renderable columns
Step 3: extract data
The one fix that stops all of them
Parameterized queries (prepared statements). Every lab above works because the application concatenates user input directly into a SQL string. A parameterized query separates the SQL structure from the data — the database never parses user input as SQL, because it was already compiled before the value was substituted in.
# ✅ safe query = "SELECT * FROM users WHERE username = %s" cursor.execute(query, (username,))
What the labs actually teach
- A 500 response is a signal — it means the application processed your input as SQL and failed. That's an injection point.
- Comments are essential —
--lets you discard the developer's trailing SQL so your payload doesn't break the syntax. - UNION attacks require matching column count and type — enumerate first, then extract.
- Boolean conditions are predictable —
1=1is always true,1=2is always false. Use the difference in response to infer data. - The fix is architectural — no amount of regex filtering catches all possible SQLi variants. Parameterize the query.