SQL Injection / practice lab
Practise SQL injection on something that actually is vulnerable
The application below concatenates your input into a SQL string, and a
real MySQL-flavoured engine parses and runs whatever that string turns out
to mean. Nothing here matches payloads — which is why
' OR '1'='1 works in the password field and not in the
username field, exactly as on a real target. Four levels escalate by
defence and context rather than payload length: no
defence, then escaping applied to the wrong context, then a hidden result,
then a denylist. Every statement the database receives is shown to you as
you go.
Objective
EasyAuthentication bypass
Technique:
Harbourline Supply
the targetThis frame is sandboxed without allow-same-origin, so it
runs on an opaque origin: it cannot read this site's storage, and this page
cannot read its DOM. That is what makes shipping a genuinely vulnerable app
on a public site reasonable.
Solution and walkthrough — open when you want it
View source
the code you are attackingThe vulnerable code for this level, and the version that actually fixes it. Reading both is the part that carries over to reviewing real code.
As deployed
Fixed
What the filter rejects
Reference
The questions that come up while working through these.
Is this real SQL injection, or a simulation?
The injection is real; the runtime is not a server. The app builds a query by string concatenation and a genuine tokeniser, parser and executor run the result. There is no list of accepted payloads, which is why the dialect details bite exactly as they do in the field:
' OR '1'='1in the username field fails, becauseANDbinds tighter thanORand the password comparison survives. The same payload in the password field works. Precedence, not payload.'abc' = 0is true, because MySQL converts a non-numeric string to 0.ORDER BY 4on a three-column SELECT raisesUnknown column '4' in 'order clause'— the actual signal column-count discovery reads.- A
UNIONwith the wrong arity raisesThe used SELECT statements have a different number of columns.
What is emulated is the server: the engine runs in your browser rather than behind a network socket. Nothing else about the exercise changes.
How does this compare with DVWA and PortSwigger's labs?
| DVWA / bWAPP | PortSwigger Academy | This lab | |
|---|---|---|---|
| Setup | Docker or LAMP plus a database | Account, and a hosted target per lab | None — it is a web page |
| Works offline | Yes, once installed | No | Yes |
| Graded difficulty | 4 security levels | Apprentice to Expert | 4 levels per vulnerability |
| View the vulnerable source | Yes | No | Yes, plus the fixed version |
| See the executed SQL | No | No | Yes, live |
| Progressive hints | No | One solution, all at once | Three, taken as needed |
| Server to attack | Yours to run and secure | Theirs | None — sandboxed in the page |
The trade is honest: a real server can be attacked in ways a page cannot, and for the classes where that matters the lab says so. For SQL injection specifically, nothing is lost — the engine is a real parser and the vulnerability is a real one.
Which SQL features are implemented?
Enough MySQL to practise properly, and it is worth knowing the shape of it so you are not fighting the engine:
SELECT [DISTINCT] cols FROM t [WHERE ...] [GROUP BY ...] [HAVING ...]
[ORDER BY expr|position [ASC|DESC]] [LIMIT n [OFFSET m] | LIMIT m,n]
UNION [ALL] SELECT ...
Comments -- (needs trailing space) # /* ... */
Operators = != <> < <= > >= <=> LIKE IN BETWEEN IS [NOT] NULL
AND && OR || NOT ! XOR + - * / % DIV MOD
Strings CONCAT CONCAT_WS SUBSTRING SUBSTR MID LEFT RIGHT LENGTH
UPPER LOWER ASCII ORD CHAR HEX UNHEX REVERSE REPLACE
TRIM INSTR LOCATE
Logic IF IFNULL NULLIF COALESCE CASE WHEN ... THEN ... END
Aggregates COUNT SUM AVG MIN MAX GROUP_CONCAT
Metadata @@version VERSION() DATABASE() USER() @@hostname @@datadir
Timing SLEEP() BENCHMARK()
Catalogue information_schema.tables .columns .schemata
Literals 'quoted' "quoted" 0x48656C6C6F numbers
Subqueries scalar in expressions, and IN (SELECT ...)
Stacked SELECT 1; SELECT 2
Not implemented: writes (INSERT, UPDATE,
DROP), JOIN, out-of-band channels such as
LOAD_FILE and INTO OUTFILE, and stored
procedures. Out-of-band exfiltration needs a network the page does not
have; the rest are on the list for later labs.
Why do the levels change the defence rather than the payload?
Because that is how real code arrives at being vulnerable. Nobody sets out to write level 4; they write level 1, get a finding, and patch the symptom. Each level here is a real patch someone actually ships:
- Level 1 — nothing. The value is inside quotes and unescaped.
- Level 2 — escaping, in the wrong context.
mysqli_real_escape_stringmakes a value safe to sit inside quotes. This value is interpolated with no quotes, so the escaping protects nothing at all. - Level 3 — hide the output. The result becomes one word and errors are caught. One bit per request is still enough to read the whole database.
- Level 4 — a denylist. Ten patterns describing what attacks have looked like before. Every one has an alternative spelling.
Only the parameterised version in View source ends the class, because it is the only one that stops treating the value as part of the statement.
Can I use sqlmap or my own script against this?
Not directly, and that is the one genuine limitation.
There is no HTTP endpoint — the engine is in the page — so there is nothing
for sqlmap to point at.
Do level 3 by hand at least once anyway. Extracting six characters
through a one-bit oracle teaches you what the tool is doing, and the people
who can only run sqlmap are the ones who stall the moment a
target needs a payload shaped slightly differently. Once the loop is
familiar, automating it is a short script.
If you want a target you can point tooling at over the network, run a real vulnerable app locally — DVWA or the OWASP Juice Shop — which is the right shape for that job.
Is my progress stored anywhere?
Which levels you have solved are kept in your browser's
localStorage under the key pt-practice, and
nowhere else. There is no account, no server and no telemetry, so
clearing your site data clears your progress.
The flags are derived from the lab and level rather than stored as strings, but everything here runs in your browser, so treat them as self-verification and not as proof of anything. That is the same property that makes the lab safe to hand to anyone.