Conducted as part of the course
Web Network Security
École des Mines de Saint-Étienne
In this report, we will present the exploitation of vulnerabilities on the DVWA (Damn Vulnerable Web Application) web application. We will detail the exact steps followed, the results obtained, and the recommended countermeasures.
Figures 1 and 2 show the completion of the 40 required labs.
FIGURE 1 - Progress on PortSwigger - 48 labs completed.
At this level, the application does not implement any protection against brute force attacks. No delay between attempts, no limit, no CAPTCHA: you can try as many combinations as you want, as fast as you want. We therefore chose to automate the attack with Burp Suite.
We started by attempting a normal login with the username admin and a random password, just to capture the HTTP request. Once intercepted with Burp Suite, we sent it to the Intruder tab.
There, we kept the username field set to admin and placed an injection point on password. Then, we loaded a custom wordlist containing different variations of common passwords around “password”: for example, password123, adminpass, p@ssword, etc.
We launched the attack, and by analyzing the responses, one of them stood out because of its size: it was longer. That immediately indicated that the attempt had succeeded, allowing us to retrieve the correct password.
FIGURE 3 - Login form on which we tested brute force.
FIGURE 4 - Request intercepted in Burp Suite.
FIGURE 5 - Burp Intruder configuration with the wordlist.
FIGURE 6 - Successful login detected by the response size.
This type of vulnerability is very basic, but unfortunately still common. To protect against it, it is essential to limit login attempts, introduce a delay between each try, or add a CAPTCHA or two-factor authentication.
At this level, DVWA introduces a first layer of protection against brute force attacks: an artificial delay of about two seconds is added after each failed login attempt. This does not block the attack but significantly slows it down.
To test this protection, we used exactly the same method as for the Low level: intercepting the request with Burp Suite, configuring the Intruder tab, and using our custom wordlist containing variants around "password."
The application’s behavior was different: each attempt took longer to respond, confirming the existence of a deliberate server-side delay. The attack was therefore much slower, but still entirely feasible, especially if the attacker is patient or uses a background script.
To support this, we observed in the browser console that each request took around 2 seconds to process, as shown below.
FIGURE 7 - 2-second response time visible in the browser console.
As in the Low level, we were able to identify the correct password by observing a different response in Burp Intruder.
FIGURE 8 - Password found despite the presence of the anti-brute force delay.
This type of countermeasure is a good start but is not enough to block a brute force attack. As long as there is no temporary lockout or strict limit on the number of attempts, the attack remains feasible — simply slower.
At this level, DVWA introduces two significant protections against brute force attacks:
— A per-request anti-CSRF token: it changes each time the login form is displayed.
— A random waiting delay between two responses, ranging between 0 and 3 seconds.
To automate our attack despite these protections, we used Burp Suite Intruder’s advanced features. The principle is as follows: we must dynamically retrieve a new token at each password attempt and then reinject it into the next request.
Here are the steps we followed:
- We intercepted a POST request to the login page and identified the
user_tokenfield.
FIGURE 9 - Dynamic extraction of the anti-CSRF token via Burp Intruder.
- In Intruder, we defined two positions:
— Payload 1: the password to test, from a customized dictionary.
— Payload 2: the anti-CSRF token, configured to be dynamically retrieved using the Recursive Grep mode.
- In the Options tab, we:
- Enabled “follow redirections.”
— Limited the number of threads to 1 (because of the random delay between responses).
- We also specified an initial valid token (from the original request) to start the session.
This configuration allowed us to properly bypass the dynamic CSRF token. Burp automatically retrieved a new valid token before each attempt, making the attack possible despite the protection.
The random delay, however, cannot be bypassed directly. One simply needs patience: each attempt takes between 1 and 3 seconds, but with a reasonable wordlist, we were still able to find the administrator’s password.
As in the previous levels, it was the size of the HTTP response that allowed us to identify the correct attempt — the successful response had different content compared to the others.
FIGURE 10 - The response containing the correct password is recognizable by its size.
These protections are effective against naive attacks but do not resist well-configured tools like Burp. They must be reinforced with temporary account lockout, logging of failed attempts, and ideally, multi-factor authentication.
In this exploitation, the objective was to retrieve two sensitive pieces of information on the target machine: the username under which the web service is running, and the hostname of the machine. To do this, we targeted a Remote Command Execution (RCE) vulnerability present in DVWA’s "Command Injection" module.
At the "Low" security level, the application does not implement any validation or filtering on the data entered into the field intended for entering an IP address to ping. This allows direct injection of system commands by chaining them with Linux shell operators.
On Kali Linux, as on any Unix system, it is common to use operators such as ; or && in terminals to execute multiple commands:
— ; allows multiple commands to run, regardless of whether the previous one fails.
— && allows the next command to run only if the previous one succeeds.
By exploiting this, we injected the following commands into the vulnerable field:
127.0.0.1; whoami 127.0.0.1; hostname
The first command displayed the username used by the web server. The second revealed the hostname of the target machine. These results are illustrated below.
FIGURE 11 - Result of the whoami command injected via the IP field.
FIGURE 12 - Result of the hostname command injected via the IP field.
This type of flaw allows an attacker to execute arbitrary commands on the target system, which can lead to full host compromise if protections are not implemented.
To protect against this type of attack, several countermeasures must be adopted:
— Never directly concatenate user input into system commands.
— Strictly validate the expected format of the data (e.g., IPv4 only).
— Use secure functions such as escapeshellarg() or equivalent, sanitizing the result of the operation.
At this level, the DVWA application attempts to protect against command injection by filtering only two specific operators: the semicolon (;) and the double ampersand (&&). This filtering is performed server-side using a simple str_replace() that removes these sequences of characters if they appear in the user input. The corresponding source code is as follows:
$substitutions = array(
'&&' => '',
';' => '',
);
$target = str_replace(array_keys($substitutions), $substitutions, $target);
This security mechanism relies on an extremely limited blacklist, completely ignoring other valid operators in a Unix/Linux shell. The pipe operator | is an excellent example: it allows the output of one command to be redirected into another.
In practice, this means that the input: 127.0.0.1 | whoami
is interpreted as execution of the command: ping -c 4 127.0.0.1 | whoami
The command whoami will be executed, even if ping fails, because it is called via the pipe. This technique therefore completely bypasses the filtering in place.
We applied this approach with two commands:
127.0.0.1 | whoami → to retrieve the identity of the user running the web service.
127.0.0.1 | hostname → to obtain the name of the target machine.
The results confirmed that command injection is still possible despite the protections in place. This highlights the fragility of blacklists when they are incomplete.
FIGURE 13 - Bypass using | whoami (Medium level).
FIGURE 14 - Bypass using | hostname (Medium level).
Conclusion: This exploitation proves that the filtering applied at the Medium level is insufficient and easily bypassed. Effective security cannot rely on removing a few characters but requires strict validation (whitelisting), combined with a design that avoids exposing system commands to uncontrolled user input.
At this level, DVWA applies stricter filtering to block classic command injection attempts. This filtering relies on the removal of specific characters via a substitution array in the source code, including: ; (semicolon), &&, | (pipe followed by a space), $, ', (), etc.
However, this protection remains incomplete. In fact, the | character alone (without a space) is not removed, which still allows commands to be chained.
We exploited this flaw by injecting the following command:
127.0.0.1|whoami
This command was interpreted as: ping -c 4 127.0.0.1 | whoami
This resulted in the execution of the whoami command on the server. The displayed result (www-data) confirms that the system command was successfully executed.
FIGURE 15 - Successful execution of the whoami command via injection with the | character.
This bypass shows that even reinforced filtering based on a blacklist can be ineffective if it does not cover all cases. The use of a single allowed operator is enough to circumvent the intended security.
— Strictly forbid any injection into a system command.
— Never directly concatenate user input into a shell command.
— Validate input through whitelisting, for example by verifying that the input strictly matches an IP address using a regular expression.
— Use secure functions such as escapeshellarg() to neutralize special characters.
At this level, the DVWA application applies practically no control over uploaded files. No filtering of extension or content is performed, which allows an attacker to upload a malicious file and execute it directly on the server.
We took advantage of this vulnerability by creating a file exploit.php containing the following code:
<?php system($_GET['cmd']); ?>This file allows us to execute any system command by passing the cmd parameter in the URL.
We then submitted this file via the upload form:
FIGURE 16 - Upload of the exploit.php file via the form (Low level).
The application displayed a message confirming the successful upload, with a direct link to the file:
FIGURE 17 - Confirmation message after uploading exploit.php with a link.
Finally, we accessed the uploaded file and executed a remote system command via the URL:
http://localhost/DVWA/hackable/uploads/exploit.php?cmd=whoami
This command returned the name of the user under which the web service was running (e.g., www-data):
FIGURE 18 - Remote execution of the whoami command via exploit.php.
— Block all executable extensions (e.g., .php, .asp, .jsp).
— Check the MIME type and the actual file content.
— Store files in a non-executable directory (e.g., outside the web root).
— Rename uploaded files with random names or store their content in a database.
At this level, DVWA applies stricter filtering based on file extension and MIME type. The application only allows files with image extensions such as .jpg, .png, or .gif, and attempts to block PHP files to prevent malicious code execution.
To bypass this filter, we used Burp Suite to intercept the HTTP upload request and manually modify the fields related to the file type.
Here are the steps followed:
-
Create a malicious file
exploit.phpcontaining the code:<?php system($_GET['cmd']); ?>
-
Intercept the request with Burp Suite during file upload via the form.
-
Manually modify the following part of the HTTP request:
- Content-Type: replace image/jpeg with application/x-php.
- Send the modified request. Thanks to this manipulation, the server accepted the malicious PHP file despite its true nature.
We then accessed the file via the URL provided by DVWA and executed the following command:
http://localhost/DVWA/hackable/uploads/exploit.php?cmd=whoami
The command was successfully interpreted, proving that system code injection was still possible.
FIGURE 19 - Modification of Content-Type with Burp Suite to force PHP file upload.
FIGURE 20 - Execution of the whoami command from the uploaded file.
— Verify both the extension AND the MIME type on the server side using functions such as finfo_file().
— Refuse execution of uploaded files via server configuration (disable PHP in the upload directory).
— Store files in a directory that is inaccessible via the browser.
At the High level, DVWA applies advanced protections to secure the file upload mechanism. The server not only checks the extension and MIME type, but also uses functions such as getimagesize() to ensure that the file is a genuinely valid image.
Classic injection techniques (such as renaming a PHP file to .jpg or appending PHP code to an image) no longer work. To bypass these protections, we used a more discreet method: injecting PHP code into the EXIF metadata of a JPEG image using the ExifTool utility.
-
Create a base image (e.g.,
photo.jpg). -
Inject PHP code into the metadata with the command:
exiftool -Comment='<?php system($_GET["cmd"]); ?>' photo.jpg -
Rename the file to shell.php.jpg.
-
Intercept the upload request with Burp Suite:
-
Extension: .php.jpg
-
Content-Type: image/jpeg
- Send the modified request.
The file was accepted by DVWA because the image was valid from the perspective of both metadata and JPEG structure. By then accessing the file via:
http://localhost/DVWA/hackable/uploads/shell.php.jpeg?cmd=whoami
the PHP code inserted in the metadata was executed by the server, confirming the compromise.
FIGURE 21 - PHP code injection in EXIF metadata using ExifTool. 
FIGURE 22 - Remote execution of the whoami command via injected image file.
— Disable PHP code execution in the upload directory.
— Clean file metadata after upload.
— Verify the actual file content (not only its extension or MIME type).
Password extraction: We exploited the SQL Injection vulnerability by using a query that explicitly extracts the users and their passwords stored in the database. The payload used was the following:
%’ UNION SELECT user,password FROM users #
This injection allowed us to directly display the complete list of users along with their MD5-hashed passwords, as illustrated in Figure 23.
FIGURE 23 - Successful extraction of passwords (Low level).
We then used the online tool CrackStation to attempt to retrieve the plaintext passwords from the obtained MD5 hashes. This tool relies on a massive database of common and pre-cracked passwords. Thanks to this, we were able to successfully decode the hashes extracted during the SQL injection. The results are presented in the table below:
FIGURE 24 - Decoding of MD5 hashes obtained using the online tool.
At the Medium level of DVWA, the application implements a basic protection against SQL injections by using the function mysql_real_escape_string() and by restricting user input to a dropdown list. However, by intercepting the HTTP request with Burp Suite, it is possible to modify the id parameter to inject a malicious SQL query.
The payload used was the following:
1 UNION SELECT user, password FROM users --This injection allowed us to retrieve the list of users and their hashed passwords, as illustrated in Figures 25 and 26.
FIGURE 25 - HTTP request modified with the injected payload (Medium level).
FIGURE 26 - Result of the SQL injection showing the users and their hashed passwords.
The results are presented in the table below:
Methodology: At the High level, DVWA applies stronger protections such as the use of prepared statements and input validation. However, the page provides a pop-up window that allows modifying the used ID.
By directly entering an SQL command into this pop-up, it was possible to bypass the protections in place.
1’ UNION SELECT user, password FROM users #This injection allowed us to extract the list of users along with their hashed passwords. The request sent to the server, captured via Burp Suite, was encoded as follows: id=1%27+UNION+SELECT+user%2C+password+FROM+users+%23 The results are illustrated in the figures below:
FIGURE 27 - Direct input of the payload in the pop-up window (High level).
FIGURE 28 - Request intercepted in Burp Suite showing the encoded payload.
FIGURE 29 - Result of the SQL injection (High level).
This challenge is based on a blind SQL injection, where the application never directly returns data from the database. Instead, it only indicates whether a user "exists" or not, which allows us to infer the validity of certain SQL conditions.
We used this weakness to extract the admin user's password, character by character, using Burp Suite.
The basic idea relies on the following query:
1’ AND SUBSTRING((SELECT password FROM users WHERE user='admin'), N, 1) = 'x' --Where:
— N is the position of the character to test. — 'x' is the character guessed at that position.
Automation with Burp Suite
To automate the tests, we used a Cluster Bomb attack in Burp Intruder, with two payloads:
— A Numbers payload to vary the position N from 1 to 32. — A Brute Forcer payload containing alphanumeric characters to test (a-z, 0-9).
We then:
— Intercepted a valid request for the test page. — Defined two injection points in the request (position and character). — Activated the Cluster Bomb attack to combine each character with each position. — Filtered HTTP responses with status 404, which indicated a negative result. — Sorted results by increasing position (N), keeping only the valid responses (different status or different content).
This method allowed us to reconstruct a complete hash, character by character. The obtained hash was: 5f4dcc3b5aa765d61d8327deb882cf99 We immediately recognized it as a common MD5 hash. Submitting it to an online cracking tool (CrackStation) revealed that the corresponding password was simply:
password
FIGURE 30 - Cluster Bomb attack configured with position and character.
FIGURE 31 - Results sorted by position after filtering out 404 responses.
FIGURE 32 - MD5 hash decoded using an online tool.
This approach shows that even without direct data returned, a database can still be compromised if responses allow the attacker to infer the truth of a query. A patient attacker (or one equipped with automated tools) can eventually recover the entirety of sensitive information. Although we performed this attack on the "admin" user, the same method could be generalized to any existing user in the database.
At the Medium level, the interface changes slightly, but the SQL query on the server side remains exactly the same. The application simply replaces the text field with a drop-down menu, which restricts input on the client side but does not prevent exploitation via a manual request.
We therefore repeated exactly the same method as for the Low level: intercepting the request with Burp Suite, then configuring a Cluster Bomb attack in Intruder with two payloads:
— A numeric payload to iterate over each position of the password (from 1 to 32).
— An alphanumeric payload to guess each character.
The application's response varied in the same way depending on whether the SQL condition was true or false, allowing us to extract the password in an identical manner.
This level clearly shows that restricting input on the interface side (such as with a drop-down menu) does not protect against attacks, as long as the server-side logic remains vulnerable.
At the High level, DVWA completely changes the way the request is sent. The input field is no longer a simple form integrated into the page, but instead a link that opens a new window where an ID can be entered. Once the ID is submitted, the main page automatically refreshes to display the result.
This behavior changes the request pattern: the field is indeed sent to the server, but the HTTP response obtained in the secondary window is always the same (status code 200), regardless of the injected content. In other words, there is no longer any observable difference directly in the raw response, which makes automation with Burp Intruder ineffective.
For this reason, we were forced to revert to the exact same principle as in the previous levels (injection with SUBSTRING), but perform it entirely manually. At each attempt, we manually entered an ID containing an SQL condition, and visually observed whether the main page displayed the message “User ID exists in the database” or not.
This makes the process much longer, but still feasible with rigor. The SQL injection mechanism remains the same, only the analysis of the response becomes manual.
Through this project, we practiced different attack techniques on vulnerable web applications, particularly DVWA. The objective was not only to successfully exploit the vulnerabilities but also to understand how they work, their implications, and how to protect against them.
We completed more than 40 labs on the PortSwigger platform, which allowed us to build a solid foundation on common vulnerabilities such as SQL injection, XSS, file upload, and command injection.
Some protections, such as dynamic CSRF tokens, delays, or asynchronous JavaScript interfaces, make attacks slower or harder to automate, but they can still be bypassed with proper tools or rigorous manual testing.
This work helped us better understand how much implementation details matter in web security. Even simple filters can be ineffective if not properly configured. Above all, we learned that it is not enough to secure the user interface: the entire server-side logic must be protected.
In summary, this project was very educational, as it forced us to think like an attacker while maintaining a rigorous and methodical approach.
And of course, after dozens of labs on PortSwigger, we ended up developing a certain fondness for wiener — our faithful testing partner — even if our real target was almost always carlos.