SQL Injection / practice lab

Sandboxed, no server Real SQL engine Works offline

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

Easy

Authentication bypass

Technique:

Harbourline Supply

the target

This 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 attacking

The 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

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'='1 in the username field fails, because AND binds tighter than OR and the password comparison survives. The same payload in the password field works. Precedence, not payload.
  • 'abc' = 0 is true, because MySQL converts a non-numeric string to 0.
  • ORDER BY 4 on a three-column SELECT raises Unknown column '4' in 'order clause' — the actual signal column-count discovery reads.
  • A UNION with the wrong arity raises The 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 / bWAPPPortSwigger AcademyThis lab
SetupDocker or LAMP plus a databaseAccount, and a hosted target per labNone — it is a web page
Works offlineYes, once installedNoYes
Graded difficulty4 security levelsApprentice to Expert4 levels per vulnerability
View the vulnerable sourceYesNoYes, plus the fixed version
See the executed SQLNoNoYes, live
Progressive hintsNoOne solution, all at onceThree, taken as needed
Server to attackYours to run and secureTheirsNone — 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_string makes 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.