Project 3: Web Security
Due: 2026-04-26 23:59
Goal
The goal of this project is to exploit a vulnerable web application using several approaches.
We’re going to be using the Damn Vulnerable Web Application (DVWA) which is designed specifically for this purpose. The Virtual machine modifications section below will walk you through installing DVWA in the same virtual machine you used for projects 1 and 2.
DVWA is a PHP application that uses the MariaDB database. Part of this project will involve you learning enough PHP, SQL (specifically, the MariaDB flavor of SQL), HTML, and JavaScript to exploit DVWA in multiple way.
Collaboration
You may work on this project in collaboration with one or two partners as described on the main page.
You must not discuss the project with anyone other than your partners and course staff. You may use online resources for general reference, but not to search for solutions to specific questions posed in this project.
Preliminaries
Go to the GitHub Classroom page and accept the assignment.
- If you are working by yourself, create a new team (you can name it whatever you like).
- If you are working with a partner (which I recommend), one of you should create a new team and the other should join the team.
Warning: Do not join any team other than your partners’. There’s no way to change teams for the assignment so just don’t do it! (If you do join the wrong team by mistake, let me know immediately.)
Virtual machine modifications
Since this project requires you to run a web site in the VM, you’re going to need to start by modifying the start.sh (or start.ps1) script that runs the VM to forward port 4280 on the host to port 80 (the standard, insecure, HTTP port) on the VM.
Specifically, modify the line that starts -netdev to include hostfwd=tcp:127.0.0.1:4280-:80 similar to how you did it for Project 2.
Installing DVWA
Start the VM and, from either the same terminal window or after SSHing in from a second terminal window, run the following commands to download the Install-DVWA.sh installation script and run it.
sudo apt install curl
curl -LO https://raw.githubusercontent.com/IamCarron/DVWA-Script/main/Install-DVWA.sh
sudo bash Install-DVWA.sh
When the installation script asks you to Enter SQL user, just press enter without entering any information. When it asks you to Enter SQL password (press Enter for no password), again, just press enter without entering anything.
After the installation has completed, open your browser (which, presumably is already open because you’re reading these instructions) and go to http://localhost:4280/DVWA/. Log in using the credentials
Username: admin
Password: password
Once you’ve logged in, click the Create / Reset Database button at the bottom of the page. After a moment, the page will return to the login page. Log in again using the same credentials.
Do not skip this step: Click on DVWA Security near the bottom of the side bar. Change the Security Level from Impossible to Low. If you fail to do this, DVWA is not vulnerable to any attacks (so far as the developers know).
The Security Level setting does not change the state of DVWA. What it does is set a cookie in your browser with the selected security level. Every request that your browser makes to the server will include that cookie. The server will serve pages that contain vulnerabilities for the selected security level.
The parts of this project that require you to programmatically interact with the server will set this cookie. (See the provided login.py for details.)
The tasks
There are five tasks for you to complete. Tasks 2–5 have a programming component and each have an extra credit opportunity.
For each of the 4 programming tasks, the corresponding Python script contains a SECURITY_LEVEL variable. If you want to implement the extra credit versions of the exploits, the Python script must contain code for both versions (assuming the same solution does not work for both versions). For grading purposes, I’m going to set the SECURITY_LEVEL variable to the required level for the task, run the script, and then set it to the extra credit level and run the script a second time. I will make no other modifications to your script. It must use appropriate if statements based on the SECURITY_LEVEL variable to work with that security level. In particular, I will not be commenting or uncommenting your code.
You can use something like
if SECURITY_LEVEL == 'low':
# Implement the exploit for the low security level
elif SECURITY_LEVEL == 'high':
# Implement the exploit for the high security level
else:
raise Exception("Invalid security level", SECURITY_LEVEL)
to support both implementations.
Task 1 Command Injection (low)
Log in to DVWA and make sure the Security Level is set to Low as described in the installation instructions.
Click on Command Injection in the side bar. By entering either an IP address or a domain name into the provided text box, you can ping a server. Give it a try by entering cs.oberlin.edu in the box and clicking Submit.
After a few seconds, you should see something like
PING cs.oberlin.edu (132.162.201.24) 56(84) bytes of data.
64 bytes from occs.cs.oberlin.edu (132.162.201.24): icmp_seq=1 ttl=255 time=3.49 ms
64 bytes from occs.cs.oberlin.edu (132.162.201.24): icmp_seq=2 ttl=255 time=7.27 ms
64 bytes from occs.cs.oberlin.edu (132.162.201.24): icmp_seq=3 ttl=255 time=3.36 ms
64 bytes from occs.cs.oberlin.edu (132.162.201.24): icmp_seq=4 ttl=255 time=4.47 ms
--- cs.oberlin.edu ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3009ms
rtt min/avg/max/mdev = 3.357/4.646/7.267/1.573 ms
Your task is to exploit this page to inject a shell command that will print out the username of the “user” that is running the web server as well as the hostname of the server.
Once you have learned the username and hostname, enter those along with the full contents of the IP address box you used to learn them in the README.md in your repository.
Hint: For each vulnerable webpage, there are two buttons, View Source and View Help. View Source will show you the PHP source code for the page at the current security level. You’re definitely going to want to look at that for each exploit! The View Help page will provide you with information about what you’re trying to do, the technique you should use to do it, and for many of the pages, most or all of a solution. You should read the help. I recommend you try to exploit the page on your own first. If you get stuck, look at the spoilers in the help.
Intermezzo
Several of the tasks will require you to programmatically interact with DVWA. Let’s set up a Python virtual environment and install some packages that we can use to make HTTP GET and POST requests as well as parse the returned HTML.
On your computer (not in the VM!), cd into your assignment repository and run the following commands.
python3 -m venv venv
source venv/bin/activate
pip install requests bs4
(If you’re using Windows, then you will need to run Activate.ps1 instead of sourcing venv/bin/activate. See the documentation for details.)
The first command creates a new virtual environment in a venv directory that will allow you to locally install packages. The second command activates the virtual environment by prepending some directories to your shell’s PATH. If you open a new shell, you’ll need to rerun the source venv/bin/activate. You can read more about Python’s virtual environments here. The third command installs the Python requests package and the Beautiful Soup package, bs4. Click on those links for documentation.
With that, you’re all set to continue to the next task.
Task 2 Command injection (medium/high)
In this task, you’re going to do the same thing you did for Task 1 except that you’re going to change the security level to Medium and you’re going to perform the exploit programmatically rather than by interacting with the web page via the browser.
But first, you’ll want to look at the PHP source code for the page you want to exploit. Start by changing the DVWA Security to Medium and then return to the Command Injection page. Click on View Source.
Notice that for the Medium security, && and ; are removed from the input string by the str_replace() PHP function. If you used either of those constructs for Task 1, you’ll have to come up with a different command to inject.
Your task is to edit exec.py and change the ip parameter to include your shell injection. The output of exec.py should be two lines containing the username and the hostname and nothing else.
For example, if the username were dvwa and the hostname were sovulnerable (they are not), then the output of your program should be
$ python3 exec.py
dvwa
sovulnerable
Once you have successfully implemented exec.py, answer the questions in the README.md.
There are several things to notice about the PHP source code and the starter exec.py that is provided for you. These may be important for later tasks.
- The PHP source uses
$_POST[ 'Submit' ] and $_REQUEST[ 'ip' ]. You should read up on what $_POST and $_REQUEST (and $_GET) are. Here’s the manual page that describes them. You can find additional information by looking at StackOverflow or other sources on the Internet. Submit and ip come from the HTML form element that you would click on the page. You can see this by viewing the DOM (the document object model) of the web page. In Firefox or Google Chrome, right click on any element in a web page (such as the Submit button) and click Inspect in the contextual menu.- If you’re unfamiliar with HTML forms, I recommend reading about the input and form elements.
- The starter
exec.py first logs into DVWA by using the provided login() function which returns a cookie jar containing the cookies. This cookie jar contains the security level cookie as well as the session ID cookie necessary to interact with the page. It is passed to the requests.post() function. - Since the PHP source expects an HTTP POST and not an HTTP GET (see
$_POST above), the starter code uses requests.post() and it passes the parameters (i.e., the form data) using the data keyword argument. If it were a GET request, then the function call would be requests.get(url=URL, params=params, cookies=COOKIE_JAR) which uses the params keyword instead. - The parameters include
Submit which is the name of the Submit button and its value, also Submit, gets sent by the browser and the PHP code is looking for it.
Hints:
- If you cannot think of something to use other than
&& or ;, click the Compare All Levels button in the View Source window and compare what the High security level filters out compared to the Medium security level. If you’re still unsure, the View Help window gives a suggestion. - Remember from CSCI 241 that you can use shell redirection to redirect the output of a command to
/dev/null if you don’t want to see its output. - I recommend playing around with the web page to find a command that works prior to implementing it in
exec.py.
Extra credit: For a small amount of extra credit, exploit the High security level by changing the value of SECURITY_LEVEL to 'high' in exec.py. Make sure you follow the instructions above regarding the extra credit.
Task 3 SQL Injection (low/high)
Log in to DVWA and make sure the Security Level is set to Low as described in the installation instructions and go to the SQL Injection page.
The database contains five users with IDs 1 through 5. You can put one of those IDs in the text box and click Submit to see the First name and Surname of each user.
Your task is to modify sqli.py to get all of the users’ passwords from the database using a single SQL injection.
As a warmup, figure out a User ID you can put in the input box to get it to show you all users’ names.
Once you have figured that out, modify your approach—and implement it in sqli.py—to print out each user ID and its corresponding password.
The output of ./sqli.py should match the following output except that each password should include the entire password, not just the first 2 characters as shown below.
$ ./sqli.py
id=1 password=5f...
id=2 password=e9...
id=3 password=8d...
id=4 password=0d...
id=5 password=5f...
Once you have successfully implemented sqli.py, answer the questions in the README.md.
Hints:
- Multiple SQL queries can be executed and their results combined into a single output. See the
UNION SQL set operator. - The legitimate query is expecting results that have two columns:
first_name and last_name. Luckily, the data you want, the user ID and password, also comes in two columns.
Extra credit: For a small amount of extra credit, exploit the High security level. You still need to use a single database query, but you may use multiple HTTP GET or POST requests.
Task 4 Blind SQL Injection (low/medium)
Log in to DVWA and make sure the Security Level is set to Low as described in the installation instructions and go to the SQL Injection (Blind) page.
Your task is to edit sqli_blind.py to perform a blind SQL injection to learn the version number of the database, one character at a time.
You’ll want to use the VERSION() function which returns a version in the form x.y.z-MariaDB. We’re interested in the x.y.z portion.
The output of sqli_blind.py must match the following except that the x.y.z must be replaced by the actual version.
$ ./sqli_blind.py
The database version is x.y.z
Once you have successfully implemented sqli_blind.py, answer the questions in the README.md.
Hints:
- Play around with the
User ID field to figure out how to tell the difference between a successful query (i.e., one for which the supplied user ID is in the database) from an unsuccessful query (i.e., one for which it is not). - Make multiple requests to the server to learn the version string one character at a time. My solution loops over the characters
0123456789.-, and uses the blind SQL injection to learn if the that character is correct. Once the - is correct, it prints the version and exits. Other approachers are possible. - You’ll probably want to use one or more of the string functions.
Extra credit: For a small amount of extra credit, exploit the Medium security level.
Task 5 XSS (medium/high)
Log in to DVWA and make sure the Security Level is set to Medium as described in the installation instructions and go to the XSS (Reflected) page.
Your task is to edit xss.py to do two things.
- First, print to standard out a URL of the form
http://localhost:4280/DVWA/vulnerabilities/xss_r/... that contains reflected cross-site scripting (XSS) content. When a logged in DVWA user clicks on the link, the XSS should send the contents of the user’s cookies to 127.0.0.1:12345 in some fashion. - Open a listening socket on port
12345 and wait for a request to come in containing the contents of the cookies at which point the connection can be closed and the cookies should be printed to standard out.
Note that xss.py makes no connections to DVWA. Instead, it waits for an incoming connection from a user who has clicked on the URL that xss.py prints out in the first step.
(If this were a real attack, the attacker would not send the cookies to 127.0.0.1 but instead to a computer reachable from the Internet that was under the attacker’s control.)
The output of xss.py must match the following format. Note that the second line of output only appears after clicking on the link printed out in the first line.
$ ./xss.py
http://localhost:4280/DVWA/vulnerabilities/xss_r/...
security=medium; PHPSESSID=sptj6m3i4vaq7fcmngi6er3oqk
The PHPSESSID cookie will have a different value.
Once you have successfully implemented xss.py, answer the questions in the README.md.
Hints:
- Your XSS should include some JavaScript that takes the values of the cookies and crafts a request to
http://127.0.0.1:12345 that contains the cookie value. - You will probably want to use the JavaScript function
encodeURI() to perform URL encoding on the request to 127.0.0.1. - Your JavaScript can use
document.write() to add a new HTML element to the page that will make the request. An <img> element is a good choice. - In
xss.py, the http://localhost:4280/DVWA/... URL that gets printed out can use the URL Quoting functions in the urllib.parse library to perform the proper escaping. There’s a distinction between urllib.parse.quote() and urllib.parse.quote_plus(). You should investigate how the various parts of URLs are encoded and pick the correct function. - In
xss.py, after receiving the request from the browser, you should extract the path and/or query from the HTTP request line. If you encoded it using the JavaScript function encodeURI() as suggested, you can decode it using urllib.parse.unquote() in Python.
Extra credit: For a small amount of extra credit, exploit the High security level. Hint: URLs in a HTML document can start with // rather than http:// or https:// to mean use the same scheme the browser used to fetch the document. When testing, make sure you’ve set the security level to High in your browser. If the printed cookie contains security=high, then you’ve done it correctly.