Almost every web hacker has the same origin story. Somebody told them to install DVWA, they spent an evening fighting a database error, and then a single quote in a text box returned five user records and a set of password hashes. That moment is why Damn Vulnerable Web Application is still the default first target nearly two decades after it first appeared.
DVWA is a deliberately broken PHP application you run on your own machine and attack on purpose. It covers most of the OWASP Top 10 in a form you can break in an afternoon, and it shows you the vulnerable source code next to every challenge. This guide covers the install, the first login, and worked solutions for the modules beginners get stuck on. If you would rather skip the setup entirely and start attacking something in the next two minutes, our Web Application Attacks course runs the same vulnerability classes in the browser.
TL;DR: DVWA is a free, open source PHP and MariaDB app built to be hacked. Run docker compose up -d in the cloned repo, open http://localhost:4280, log in with admin / password, click Create / Reset Database, and set the security level to Low. Work each module from Low up to Impossible and read the source code at every level. Never expose it to the internet.
What Is DVWA?
DVWA (Damn Vulnerable Web Application) is a PHP and MariaDB web application built to be insecure on purpose, so that students, developers and penetration testers can practice attacking real vulnerabilities legally on their own hardware. It was written by Ryan Dewhurst in 2008 and is now maintained by Robin Wood (digininja) on GitHub, free and open source under GPLv3.
The current release is version 2.5, published in January 2025, which added a module for vulnerable OpenAPI endpoints. The official repository ships 19 vulnerability modules covering SQL injection, blind SQL injection, command injection, reflected, stored and DOM-based XSS, CSRF, file inclusion, file upload, brute force, weak session IDs, authorization bypass, broken access control, Content Security Policy bypass, JavaScript logic, open redirect, insecure CAPTCHA, cryptography and the API playground.
Two design decisions make it better for learning than most vulnerable apps. First, every page has a View Source button that shows the exact PHP handling your input, so you can see why your payload worked instead of guessing. Second, each module exists at four difficulty levels, so the same vulnerability teaches you filter evasion and then teaches you the fix.
It is listed in the OWASP Vulnerable Web Applications Directory and ships as a package in Kali Linux, which is a reasonable signal that it is the community standard rather than a random GitHub project.
How to Install DVWA With Docker
Docker is the right choice for a first install. The manual route means installing Apache, PHP and MariaDB, editing php.ini, fixing folder permissions and then debugging whatever your distro did differently. The container route is four commands.
- Check Docker is present. Run
docker versionanddocker compose version. Both need to return a version number. Docker Desktop includes Compose already; on Linux, install Docker Engine from the official repository rather than your package manager, which tends to ship an older Compose. - Clone the repository.
git clone https://github.com/digininja/DVWA.gitthencd DVWA. Use the official repo. The mirrors floating around SourceForge and Docker Hub are years behind and missing modules. - Start it.
docker compose up -d. The first run pulls the prebuilt image from GitHub Container Registry and takes a minute or two. - Open it. Browse to
http://localhost:4280. The container listens on 4280, not 80, which is the single most common reason people think the install failed.
On Kali, sudo apt install dvwa gets you the packaged version, documented on the Kali tools page. It works, but it lags the GitHub release, so you may be missing the newest modules. If you want to install by hand on Debian or Ubuntu you need apache2, libapache2-mod-php, mariadb-server, mariadb-client, php, php-mysqli and php-gd, with PHP 7.3 as the hard minimum.
Do not run DVWA on a public server. The project README is blunt about it: put this on an internet facing host and it will be compromised. Keep it on localhost or inside a VM with NAT networking. A Docker container is not a security boundary here, it is a convenience.
First Login and Database Setup
The login page asks for credentials before you have created any. The DVWA default login is username admin and password password. The README notes, with visible amusement, that these can easily be brute forced. That is not an accident, it is the Brute Force module hinting at itself.
Once you are in, the app will complain that the database does not exist. Click Setup DVWA in the left menu, scroll to the bottom, and click Create / Reset Database. You get logged out, you log back in, and the five sample users are now in place. Come back to this button whenever a module starts behaving oddly, because half the "DVWA is broken" questions on forums are solved by resetting the database.
The setup page also runs a checklist of your PHP configuration. Two entries matter early:
allow_url_includeneeds to be on for the Remote File Inclusion half of the file inclusion module. On the Docker image it already is. On a manual install you will be editing/etc/php/8.x/apache2/php.iniand restarting Apache.- reCAPTCHA keys are only needed for the Insecure CAPTCHA module. Skip that module for now and ignore the warning. It is the one lab that requires an external account, and it teaches less than the others.
In practice, the fastest way to lose an hour on a fresh install is the writable uploads directory. The File Upload module writes into ./hackable/uploads/, and if the web server user cannot write there, uploads silently fail in a way that looks like a filter blocking you. The Docker image handles this. A manual install does not always.
The Four Security Levels Are the Actual Curriculum
The DVWA Security page lets you set the level to Low, Medium, High or Impossible, and this is where most beginners waste the tool. They set it to Low, clear ten modules in an evening, and conclude they know web hacking. Low is a tutorial, not a lab.
| Level | What the code does | What it teaches you |
|---|---|---|
| Low | No input handling at all | The raw shape of the vulnerability |
| Medium | A naive blacklist or a client-side check | Why blacklists lose |
| High | A longer blacklist with a gap in it | Patient, methodical filter testing |
| Impossible | The correct defense, and it holds | What secure code looks like |
Impossible is the level people ignore, and it is the one that makes you employable. Reading it after you have broken the other three is the closest thing to a free lesson in secure development. A report that says "you are vulnerable to SQL injection" is worth something. A report that shows the parameterized query that fixes it is worth considerably more.
Walkthrough: SQL Injection From Low to Impossible
The SQL Injection module gives you a box that takes a User ID and returns a name. At Low, the source is a single line of string concatenation:
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
Your input lands inside single quotes with no escaping, so the first job is to break out of the quote. Enter 1' OR '1'='1 and the condition becomes always true, returning all five users instead of one. That is your proof the injection point exists.
Now make it useful. The query returns two columns, so a UNION needs to return two columns as well:
1' UNION SELECT user, password FROM users#
The output now lists every username alongside an MD5 hash of their password. The trailing # comments out the rest of the original query, including its closing quote. If you want the theory behind why the column count has to match, our SQL injection tutorial works through UNION attacks step by step.
Move to Medium and the input box becomes a dropdown. This is a client-side restriction, which means it is not a restriction at all. Intercept the request in a proxy, or just resubmit it with a modified id parameter, and you are back in business. Medium also switches to mysqli_real_escape_string and drops the surrounding quotes, so the payload changes shape: no quote to escape, and 1 OR 1=1 works directly.
Then read Impossible, which is the actual point of the exercise:
if( is_numeric( $id ) ) {
$id = intval( $id );
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
}
Four defenses stacked: a type check, an integer cast, a prepared statement with a bound parameter, and a row limit. There is also an anti-CSRF token on the form. Nothing you type reaches the SQL parser as code, which is why no payload works and why this is the pattern to copy.
Walkthrough: Command Injection, XSS and File Upload
These three modules are where DVWA earns its reputation, because each one has a Medium or High level with a genuinely instructive gap in the filter.
Command Injection
The page pings an IP address you supply. At Low, the code is shell_exec( 'ping -c 4 ' . $target ), so anything you append runs on the host. Try 127.0.0.1; whoami and the ping output arrives with www-data underneath it.
Medium adds a blacklist that deletes && and ; from your input. That is two shell separators removed out of at least five. A pipe still works:
127.0.0.1 | whoami
High extends the blacklist to ||, &, ;, -, $, (, ), backtick, and | followed by a space. Read that last entry again. The filter removes a pipe with a trailing space, and leaves a bare pipe alone. So 127.0.0.1|whoami walks straight through.
That one character is the most valuable lesson in the whole application. Blacklists fail on the case the author did not picture, and finding that case is the job. The OWASP command injection reference lists the separators worth testing systematically rather than by guesswork.
Reflected XSS
At Low, your name parameter is echoed straight into the page, so <script>alert(1)</script> fires immediately. Medium is more interesting. The filter is:
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
One string, removed once, case sensitive. Three separate ways past it, and each teaches a different habit. Change the case with <SCRIPT>alert(1)</SCRIPT>. Nest the tag so removing the inner one rebuilds the outer one, using <scr<script>ipt>alert(1)</script>. Or avoid the word entirely with <img src=x onerror=alert(1)>, which is the payload that keeps working long after the other two stop.
File Upload
Low accepts any file and drops it in hackable/uploads/. Upload a small PHP file that runs a command from a query parameter, browse to the path the page prints back at you, and you have code execution.
Medium checks two things: that the size is under 100000 bytes and that the MIME type is image/jpeg or image/png. The size is real. The MIME type is not, because it comes from the Content-Type header that your browser sends and that you fully control. Intercept the upload, change Content-Type: application/x-php to Content-Type: image/jpeg, forward it, and the PHP file lands intact. Our Burp Suite tutorial covers the intercept and modify workflow if you have not set up a proxy yet.
The general rule this module is teaching: any value the client sends is a suggestion, never a fact. Filename, extension, Content-Type, hidden form fields, cookies. All of it is attacker controlled.
The Modules Beginners Skip
Everyone does SQL injection and XSS. Three modules get ignored and they are the ones that map most directly to paid work.
- Blind SQL Injection. Same vulnerability, no error messages and no visible output. You confirm it with a boolean condition or a time delay and extract data one character at a time. Slower, less satisfying, and far closer to what real applications look like once error reporting is switched off in production.
- Weak Session IDs. Click the button, watch the cookie, notice it increments by one. Predictable session identifiers let you become another user without touching their password. It takes ten minutes and it changes how you look at every cookie afterwards.
- File Inclusion. At Medium the filter strips
../and..\once, so....//collapses back into../after the replacement runs. It also stripshttp://, whichhthttp://tp://defeats the same way. Doubling up your payload to survive a single-pass filter is a pattern you will reuse constantly.
A practical habit worth forming: after you solve a module at Low, do not move to the next module. Move to Medium on the same one. Depth on a single vulnerability class beats breadth across ten of them, especially when the source code for the fix is one click away.
What DVWA Will Not Teach You
DVWA is excellent at one job and mediocre at several others. Being honest about the gap saves you from the plateau that catches a lot of self-taught people.
It has no reconnaissance phase. Every vulnerability is labeled, in a menu, with a button. Real testing starts with a domain and a lot of uncertainty, and finding the vulnerable parameter is most of the work. DVWA hands you that parameter for free.
The stack is dated. It is server-rendered PHP with query strings and form posts. That teaches the fundamentals honestly, and the fundamentals still hold. It teaches you nothing about a React front end talking to a JSON API, JWT handling, GraphQL, or the authorization logic bugs that dominate modern bug bounty reports.
Nothing pushes back. No rate limiting, no WAF, no logging, no lockout. Useful while you are learning the payload. Misleading about how an engagement actually feels.
None of that makes it a bad first target. It makes it a first target. Use it to learn what each vulnerability class looks like from the inside, then move to environments where you have to find the bug before you can exploit it. OWASP Juice Shop, a hosted lab platform, or a live bug bounty program are all reasonable next stops.
Legal and Ethical Considerations
Critical reminder: Always get explicit written authorization before testing any system. DVWA on your own machine is authorized by definition. Everything else needs permission in writing, and the fact that a technique worked in a lab is not a defense anywhere.
- Keep it local. Bind it to localhost or a NAT-mode VM. An exposed DVWA is a working backdoor into your own network, and automated scanners find one within hours.
- Do not reuse the payloads casually. Typing
1' OR '1'='1into a login form you do not own is unauthorised access under the Computer Misuse Act in the UK, the CFAA in the US, and equivalent legislation almost everywhere else. - Bug bounty programs are the legal route. They give you written permission and a defined scope. Read the scope before you test, not after.
- Report what you find responsibly. If you stumble across a real vulnerability outside a program, disclose it to the owner and stop testing. Do not pull data to prove your point.
Frequently Asked Questions
What is the DVWA default login?
The default username is admin and the default password is password. If the login fails on a fresh install, the database has not been created yet: log in, click Setup DVWA in the left menu, and click Create / Reset Database at the bottom of the page.
What port does DVWA run on in Docker?
Port 4280, not port 80. After docker compose up -d the application is at http://localhost:4280. This differs from a manual Apache install, where it sits on port 80 at http://127.0.0.1/DVWA, and it is the most common reason a Docker install looks broken.
Is DVWA safe to install?
It is safe on a machine you control, on localhost or in a VM with NAT networking. It is not safe on any internet facing server. The application is intentionally vulnerable to remote code execution, so exposing it publicly gives an attacker a shell on that host.
How many vulnerabilities does DVWA have?
Version 2.5 ships 19 modules, including SQL injection, blind SQL injection, command injection, three types of XSS, CSRF, file inclusion, file upload, brute force, weak session IDs, CSP bypass, open redirect, cryptography and vulnerable APIs. The documentation also notes there are undocumented vulnerabilities in the code, left in deliberately for you to find.
Is DVWA still worth learning in 2026?
Yes, for the first month of web security. Its vulnerability classes are still in the OWASP Top 10 and the View Source feature teaches root cause better than most paid courses. Move on once you can clear the High level unaided, because it does not cover APIs, JWTs or modern JavaScript applications.
What is the difference between DVWA and OWASP Juice Shop?
DVWA is PHP with a menu of labeled vulnerabilities and visible source code, which makes it better for understanding root cause. Juice Shop is a modern JavaScript single-page application where challenges are hidden and you have to find them, which makes it better practice for realistic discovery. Do DVWA first, then Juice Shop.
Your Next Steps
The DVWA workflow that actually builds skill is short: install with Docker, reset the database, set the level to Low, solve the module, read the source, then push the same module to Medium and High before you move on. Finish by reading Impossible, because knowing the fix is what turns a hobby into a job.
The one thing a local install cannot give you is the pressure of a target you have to figure out yourself. Try Query Quake for SQL injection or Ping Pwn for command injection, both running in the browser with nothing to install. Our Web Application Attacks course walks through each vulnerability class in order, and HackerDNA's free tier needs no credit card to start.
Part of the OWASP Top 10 series
Related articles: