Project 2: WebFall 2023
This project counts for 9% of your course grade. Late submissions will be penalized by 10% of the maximum attainable score, plus an additional 10% every 4 hours until received. Late work will not be accepted after the start of the next lab (of any section) following the day of the deadline. If you have a conflict due to travel, interviews, etc., please plan accordingly and turn in your project early.
This is optionally a group project; you may work in teams of two and submit one project per team. You may also work alone. Note that the final exam will cover project material, so you and your partner should collaborate closely on each part.
The code and other answers you submit must be entirely your team’s own work, and you are bound by the Honor Code. You may discuss the conceptualization of the project and the meaning of the questions, but you may not look at any part of someone else’s solution or collaborate with anyone other than your partner. You may consult published references, provided that you appropriately cite them (e.g., with program comments). Visit the course website for the full collaboration policy.
Solutions must be submitted electronically to the autograder per the submission checklist below.
In this project, we provide an insecure website, and your job is to attack it by exploiting three common classes of vulnerabilities: SQL injection, cross-site scripting (XSS), and cross-site request forgery (CSRF). You are also asked to exploit various flawed defenses meant to prevent these attacks. Understanding how these attacks work will help you better defend your own web applications.
- Learn to spot common vulnerabilities in websites and to avoid them in your own projects.
- Understand the risks these problems pose and the weaknesses of naive defenses.
Read this First
This project asks you to develop attacks and test them, with our permission, against a target website that we are providing for this purpose. Attempting the same kinds of attacks against other websites without authorization is prohibited by law and university policies and may result in fines, expulsion, and jail time. You must not attack any website without authorization! Per the course ethics policy, you are required to respect the privacy and property rights of others at all times, or else you will fail the course. See the “Ethics, Law, and University Policies” section on the homepage for more info.
A startup named BUNGLE! is about to launch its first product—a web search engine—but their investors are nervous about security problems. Unlike the Bunglers who developed the site, you took EECS 388, so the investors have hired you to perform a security evaluation before it goes live.
BUNGLE! is available for you to test at https://project2.eecs388.org/. For “security” reasons, the site is only accessible when using the version of Firefox provided by the Docker container for this project.
The site is written in Python using the Bottle web framework. Although Bottle has built-in mechanisms that help guard against some common vulnerabilities, the Bunglers have circumvented or ignored these mechanisms in several places. If you wish, you can inspect the Python source code, but this is not necessary to complete the project.
In addition to providing search results, the site accepts logins and tracks users’ search histories. It stores usernames, passwords, and search history in a MySQL database.
Passwords used on BUNGLE! may be exposed to others. Never use an important password to test an insecure site! This especially includes your personal passwords.
Before being granted access to the source code, you reverse engineered
the site and determined that it replies to five main URLs:
/create. The function of these
URLs is explained below, but if you want an additional challenge, you
can skip the rest of this section and do the reverse engineering
Main page (
The main page accepts
GETrequests and displays a search form. When submitted, this form issues a
/search, sending the search string as the parameter “
If no user is logged in, the main page also displays a form that gives the user the option of logging in or creating an account. The form issues
Search results (
The search results page accepts
GETrequests and prints the search string, supplied in the “
q” query parameter, along with the search results. If the user is logged in, the page also displays the user’s recent search history in a sidebar.
Note: Since actual search is not relevant to this project, you might not receive any results.
Login handler (
The login handler accepts
POSTrequests and takes plaintext “
username” and “
password” query parameters. It checks the user database to see if a user with those credentials exists. If so, it sets a login cookie and redirects the browser to the main page. The cookie tracks which user is logged in; manipulating or forging it is not part of this project.
Logout handler (
The logout handler accepts
POSTrequests. It deletes the login cookie, if set, and redirects the browser to the main page.
Create account handler (
The create account handler accepts
POSTrequests and receives plaintext “
username” and “
password” query parameters. It inserts the username and password into the database of users, unless a user with that username already exists. It then logs the user in and redirects the browser to the main page.
Note: The password is neither sent nor stored securely; however, none of the attacks you implement should depend on this behavior. You should choose a password that others will not guess, but again, never use an important password to test an insecure site!
This project has been tested and will be graded using Firefox 91 on Linux,
which is shipped with the project’s Docker container.
When you open the project in the container in VS Code, you can navigate to
http://localhost:38802 in your browser to use this specific version of Firefox.
(It’s a bit of browser-ception.) If you’d prefer to use VNC, you can also connect to
Firefox’s developer tools, when enabled, appears at the bottom of the browser window by default. You can paste text into the browser’s clipboard by clicking on the clipboard button in the pop-out menu on the left side of the screen. You can adjust the text size within the developer tools frame by clicking anything on it, and then pressing (Ctrl, +) for larger text and (Ctrl, -) for smaller text.
The Bunglers have been experimenting with some naive defenses, and you need to demonstrate that these provide insufficient protection. In Parts 2 and 3, the site includes drop-down menus at the top of each page that let you change the CSRF and XSS defenses that are in use. When you are testing your solution, ensure that BUNGLE! has the correct defense levels set. You may not attempt to subvert the mechanism for changing the level of defense in your attacks. Be sure to test your solutions with the appropriate defense levels!
In all parts, you should implement the simplest attack you can think of that defeats the given set of defenses. In other words, do not simply attack the highest level of defense and submit that attack as your solution for all defenses. You do not need to combine the vulnerabilities, unless explicitly stated.
When grading, the correct defense levels will be set on BUNGLE! before the autograder runs your solution; don’t worry about setting this through your code.
Some of the defense levels or targets are exceptionally difficult, and therefore completing them successfully will count as extra credit. However, the course staff are not able to provide help on the parts labeled as extra credit.
Although general purpose tools are permitted, you are not allowed to use tools that are designed to automatically test for vulnerabilities. Additionally, your solutions may not use any libraries other than jQuery, which has already been included on BUNGLE!.
- SQL Tutorial
- SQL Statement Syntax
- SQL Operators
- Introduction to HTML
- Using jQuery Core
- jQuery API Reference
- HTTP Made Really Easy
To learn more about SQL Injection, CSRF, and XSS attacks, and for tips on exploiting them, see:
Part 1. SQL Injection
Your first goal is to demonstrate SQL injection attacks that log you in
as an arbitrary user without knowing the password. In order to protect
other students’ accounts, we’ve made a series of separate login forms
for you to attack that aren’t part of the main
For each of the following defenses, provide inputs to the target login
form that successfully log you in as the user “
1.0 No defenses
You can assume that the password field is simply enclosed in single quotes.
1.1 Simple escaping
The server escapes single quotes (
') in the inputs by replacing
them with two single quotes.
1.2 Escaping and Hashing
The server uses the following Python code, which escapes the username and applies the SHA-256 hash function to the password.
from hashlib import sha256 from flask import request @app.route("/sqlinject/2", methods=["POST"]) def login(): username = request.form["username"] escaped_username = mysql_real_escape_string(username) password_bytes = ("bungle-" + request.form["password"]).encode("latin-1") password_digest = sha256(password_bytes).digest().decode("latin-1") query = "SELECT * FROM users WHERE username='" + escaped_username + "' AND password='" + password_digest + "'" selected_users = mysql.execute(query).fetchall() if len(selected_users) > 0: return "Login successful!" else: return "Incorrect username or password."
This is more difficult than the previous two defenses. You will need to write a script to produce a working exploit. You can use any language you like, but we recommend Python 3.
Hint: If your script is taking a really long time to run, it may be worth designing an SQL injection that it will have an easier time finding. Consider looking at the W3 Schools SQL Operators page for some inspiration.
1.3 The SQL Extra credit
This target uses a different database. Your job is to use SQL injection to retrieve:
- The name of the database
- The version of the SQL server
- All of the names of the tables in the database
- A secret string hidden in the database
For this part, the text file you submit should start with a list of all the queries you made to learn the answers. Follow this with the values specified above, using this format:
*QUERY* *QUERY* *QUERY* ... Name: *DB name* Version: *DB version string* Tables: *comma separated names* Secret: *secret string*
What to submit
For 1.0, 1.1, and 1.2, when you successfully log in as
server will provide a URL-encoded string of your form inputs. Submit a
text file with the specified filename containing only this string. For
1.2, also submit the source code for the program you wrote by placing it
in a folder, then creating a zip file of the folder named
For 1.3, submit a text file as specified.
Part 2. Cross-site Scripting (XSS)
Your next task is to demonstrate XSS attacks against the
box, which does not properly filter search terms before echoing them to
the results page. For each of the defenses below, your goal is to
construct a URL that, when loaded in the victim’s browser, correctly
executes the specified payload. We recommend that you begin by testing
with a simple payload (e.g.,
alert(0);), then move on to the full
payload. Note that you should be able to implement the payload once,
then use different means of encoding it to bypass the different
Note: jQuery is embedded on BUNGLE!. Please do not reload it in your scripts for Part 2.
The payload (the code that the attack tries to execute) will steal the username and the most recent search the real user has performed on the BUNGLE! site. When a victim visits the URL you create, this stolen data should be sent to the attacker’s server for collection.
For purposes of grading, your attack should report this data by
sending a GET request to URLs of the form
<last_search> represent the corresponding data.
You can test receiving this data by running this command within the Docker container:
$ python3 -m http.server 31337
and observing the HTTP GET request that your payload generates in the server log.
For full credit, make sure your payload functions exactly as specified above. We cannot accept solutions that load an incorrect URL, use an incorrect request method, or send extraneous data.
Hint: In most cases, scripts are loaded alongside other content in the same order as they appear within a page’s source code. Your payload may need to account for the possibility that not all elements of the page will be accessible until everything in the DOM is ready!
There are four levels of defense. In each case, you should submit the simplest attack you can find that works against that defense; you should not simply attack the highest level and submit your solution for that level for every level. You must use a different technique for each defense. The Python code that implements each defense is shown below, along with the target URL and the filename you should submit.
Note that the defenses are not cumulative: 2.2, for example, does not cover the sanitation done for 2.1.
2.0 No defenses
For 2.0 only, also submit a human-readable version of your payload
code (as opposed to the URL-encoded version). Save it in a
2.1 Remove “script”
filtered = re.sub(r"(?i)script", "", input)
2.2 Remove several tags
filtered = re.sub(r"(?i)script|<img|<body|<style|<meta|<embed|<object", "", input)
2.3 Remove some punctuation
filtered = re.sub(r"[;'\"]", "", input)
2.4 Encode < and > Extra credit
filtered = input.replace("<", "<").replace(">", ">")
This challenge is hard. We think it requires finding a 0-day vuln or a bug in our code.
What to submit
Apart from the human-readable (non–URL-encoded) payload submitted
for part 2.0, submit a text file for each level of defense
that contains a single line consisting of a URL. When
this URL is loaded in a victim’s browser, it should execute a payload against
it should include the
stealer URL given
earlier in this spec!
Part 3. Cross-site Request Forgery (CSRF)
Your final goal is to demonstrate CSRF vulnerabilities against the login
form, and BUNGLE! has
provided two variations of their implementation for you to test. Your
goal is to construct attacks that surreptitiously cause the victim to
log in to an account you control, thus allowing you to monitor the
victim’s search queries by viewing the search history for this account.
For each of the defenses below, create an HTML file that, when opened by
a victim, logs their browser into BUNGLE! under
attacker and password
Your solutions should not require any user action beyond simply loading the page once. The browser should just display a blank page, with no evidence of an attack. (If the victim later visits BUNGLE!, it will say “logged in as attacker”, but that’s fine for the purpose of this project. After all, most users won’t immediately notice.)
To test your solution, you will have to act like the victim. With BUNGLE!
open in Firefox, open a new tab. Pressing (Ctrl, O) will prompt Firefox to open
a file browser. You can navigate to
Other Locations -> Computer -> workspaces -> project2
to find the files in your Project 2 directory. Alternatively, you can input
file:///workspaces/project2/(...) in the URL bar, where the
(...) is replaced
by the name of a file in your Project 2 directory, such as
/login handler only accepts POST requests, you will not be able to visit
the following target URLs in your browser directly—GET requests will return a
405 Method Not Allowed error.
3.0 No defenses
3.1 Token validation
The server sets a cookie named
csrf_token to a random 16-byte
value and also includes this value as a hidden field in the login
form. When the form is submitted, the server verifies that the
client’s cookie matches the value in the form. You are allowed to
exploit the XSS vulnerability from Part 2 to accomplish your goal.
3.2 Token validation, without XSS Extra credit
Accomplish the same task as in 3.1 without using XSS.
This challenge is hard. We think it requires finding a 0-day vuln or a bug in our code.
What to submit
For each part, submit an HTML file with the given
name that accomplishes the specified attack against the specified target
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js, but they must otherwise be self-contained.
If you choose to use jQuery, make sure to use this exact URL. Otherwise, your solution may not function correctly within the Docker environment or the autograder.
Note: Since you’re sharing the
attacker account with other students,
we’ve hard-coded it so the search history won’t actually update. You can
test with a different account you create to see the history change.
Tips and advice
In the past, students have lost credit on this part for various preventable reasons. Here are some tips on how to avoid common pitfalls:
Double check to make sure you use the exact target endpoints specified. Note that these endpoints use HTTPS!
When obtaining cookie data, avoid hardcoding any values beyond those given above. Search online to find general best practices for these tasks.
Your solution must work quickly and discreetly to evade detection. This means no automatic redirects, in any part of your HTML page or in any of its frames.
Create a repo using the GitHub template. Make sure that the repo you create is private.
Establish a team on the autograder. Only teams created on the autograder will be able to join the online office hours queue.
Although the autograder submission screen shows the 6 p.m. deadline, you can still submit after this time subject to a lateness penalty, up until the start of the first lab following the deadline. Any submissions after the posted deadline will result in a late deduction, even if your best submission occurred before the deadline. The autograder will not warn you of this. (You don’t get to attempt a higher score after the deadline with no risk.)
Ensure the following items are completed and submitted by the deadline:
Part 1: SQL Injection
|1.0 No defenses|
|1.1 Simple escaping|
|1.2 Escaping and Hashing|
|1.2 Escaping and Hashing|
|1.3 The SQL Extra credit|
Part 2: XSS
|2.0 No defenses|
|2.0 No defenses|
|2.1 Remove “script”|
|2.2 Remove several tags|
|2.3 Remove some punctuation|
|2.4 Encode < and > Extra credit|
Part 3: CSRF
|3.0 No defenses|
|3.1 Token validation|
|3.2 Token validation, without XSS Extra credit|
Once you’re done with the project, you can delete the project 2 Docker container to reclaim resources on your computer. Don’t delete the entire Docker installation (you’ll need it for future projects), just the container.