MAKERERE UNIVERSITY
College of Computing and Information Sciences
Department of Computer Science
Bachelor of Science in Computer Science
WEB APPLICATION SECURITY ASSESSMENT
Google Gruyere Vulnerability Testing & Remediation Report
Submitted by
SEBUNYA RONALDO
Student ID: 2300724496
Registration No.: 23/U/24496/PS
1. Executive Summary
This report presents the findings of a hands-on penetration test performed on Google Gruyere, a
deliberately vulnerable web application designed by Google to serve as a training platform for
web security practitioners.
Six vulnerability categories were evaluated in total: Cross-Site Scripting (XSS), Denial of Service
(DoS), Privilege Escalation via Client-State Manipulation, Sensitive Information Disclosure, SQL
Injection, and Buffer Overflow. Of these, four were actively demonstrated through live exploitation
within the Gruyere environment, with each successful attack followed by a code-level fix. The
remaining two SQL Injection and Buffer Overflow could not be exploited directly against Gruyere
due to its technical architecture; however, both are discussed in full, including how they would
manifest in susceptible applications and how they should be mitigated.
The testing revealed that Gruyere harbours several serious weaknesses. An attacker with a basic
user account could steal session cookies from other users, reset the server and deny access to
all legitimate visitors, grant themselves administrative privileges with a single URL request, and
retrieve the complete user database including plaintext passwords without logging in at all. Every
confirmed vulnerability was remediated at the source-code level and verified to be non-exploitable
after the fix was applied.
2. Introduction
2.1 Purpose of this Assessment
The objective of this exercise was to engage with a real (intentionally buggy) web application as
a security tester would probing the attack surface, demonstrating exploits, tracing vulnerabilities
back to their root causes in the source code, and applying practical fixes. The outcome is this
report, which documents every step taken throughout the process.
2.2 Scope of Testing
The following vulnerability classes were included in the assessment:
• Cross-Site Scripting (XSS) both stored and reflected variants
• Denial of Service (DoS)
• Client-State Manipulation (Privilege Escalation)
• Information Disclosure / Sensitive Data Exposure
• SQL Injection
• Buffer Overflow
Page 2
2.3 Target Application — Google Gruyere
Google Gruyere is a compact, self-contained web application intentionally riddled with security
holes, developed by Google as part of their Web Application Exploits and Defenses codelab. It
simulates a simple content-sharing platform where users can post short text snippets and upload
files. Because it is purposely built to be exploited, Gruyere provides a safe and legal environment
for learning offensive and defensive web security techniques.
2.4 Testing Environment
Application Google Gruyere ([Link])
Instance IDs 454852654541379131237720148345361815159
Browsers Used Google Chrome,
Testing Approach Black-box and White-box Penetration Testing
Date April 2026
3. Vulnerability Findings
3.1 Findings Overview
# Vulnerability Severity Status Exploited Live
1 Stored XSS via Snippet Critical Patched Yes
2 File Upload XSS Critical Patched Yes
3 DoS — Unprotected High Patched Yes
Quit Endpoint
4 Privilege Escalation Critical Patched Yes
5 Sensitive Data Critical Patched Yes
Exposure (DB Dump)
6 SQL Injection N/A Theoretical No
7 Buffer Overflow N/A Theoretical No
3.2 Cross-Site Scripting (XSS)
What it is: Cross-Site Scripting (XSS) occurs when an application includes user-supplied content
in its HTML output without adequately sanitising it first. A malicious actor can inject JavaScript
Page 3
that subsequently executes inside the browser of any user who views the affected page.
Depending on the payload used, this can lead to session cookie theft, account hijacking, or actions
performed on behalf of the victim without their knowledge.
3.2.1 Exploit A — Stored XSS Through Snippet Submission
How the Attack Was Performed
The Gruyere application was accessed using the built-in author account (username: cheddar,
password: orange). Navigating to the "New Snippet" page revealed a text area where users can
submit short posts — and notably, the form description hints that a limited subset of HTML tags
is supported. This immediately flagged the input field as a potential injection point.
The following payload was submitted through the snippet form:
After submission, the payload appeared in the list of published snippets exactly as entered,
confirming that the application stored the raw HTML without neutralising the event handler
attribute. When any user visited the page and moved their mouse cursor over the link, a
JavaScript alert dialog appeared in the browser — proving that the injected script executed in the
context of another user's browser session.
The immediate proof-of-concept used alert(1), but a real attacker would replace this with a script
that silently exfiltrates the victim's session cookie to an attacker-controlled server.
Page 4
Figure 2: Alert popup triggered by the onmouseover event, confirming successful
script execution
Impact
• Every user who views the snippets page is at risk of script execution
• Session cookies can be stolen and used to impersonate victims without knowing their
passwords
• Any user with a Gruyere account can execute this attack — no elevated privileges
needed
Root Cause
The sanitisation logic in [Link] maintains two lists: one for allowed HTML tags and one for
disallowed (dangerous) attribute names. The disallowed_attributes list was missing the
onmouseover event handler, meaning it passed through the filter unchanged and was rendered
as live HTML.
Fix Applied
The onmouseover attribute was added to the disallowed_attributes list in [Link]:
Page 5
Figure 3: The corrected disallowed attributes list with ’onmouseover’ added
Outcome After Fix
After restarting Gruyere with the updated code, the onmouseover attribute is stripped from any
HTML submitted through the snippet form. Existing malicious snippets are also rendered
harmlessly, as the event handler is no longer included in the page output.
3.2.2 Exploit B — XSS via HTML File Upload
How the Attack Was Performed
The Gruyere upload feature allows authenticated users to attach files to their profile. No
restrictions were placed on which file types could be uploaded. An HTML file was crafted locally
containing the following script:
Figure 4: The malicious HTML file created locally containing JavaScript to expose
session cookies
After logging in and uploading the file, navigating to its URL caused the script to execute in the
browser. Because the file was served from the same domain as the Gruyere application, it had
unrestricted access to cookies set for that domain. The victim's session cookie — including their
username, session token, and role — was displayed in the alert box.
In a real attack, the script would silently send this data to an external endpoint under the attacker's
control, allowing them to hijack the session entirely.
Impact
• The exposed session cookie reveals the user's identity, authentication token, and
privilege level
• Any authenticated user can carry out this attack — no admin access required
• The stolen cookie can be used to take over the victim account without ever knowing the
password
Root Cause
Page 6
The file upload handler in [Link] accepted all file types without filtering. Additionally, uploaded
files were served directly from the application's own domain rather than a sandboxed subdomain,
which gave the scripts same-origin access to authentication cookies.
Fix Applied
A blocklist of dangerous file extensions was added to the upload handler:
bloc
1 k ed _ex t ens io ns = [’. html ’, ’. htm ’, ’. js’, ’. svg ’, ’.
gtl’]
2 if any( filename . endswith ( ext) for ext in
blocked_extension
3 s ):
4 self. _Send Err or( ’ File type not allowed ’, cookie , specials ,
params) return
Figure 5: File type restriction added to upload handler
Outcome After Fix
Attempting to upload an HTML file now returns an error response and the file is rejected. As an
additional long-term recommendation, uploaded content should be served from a separate
domain or subdomain so that even if a script were to be uploaded through another bypass, it
would not have same-origin access to the application's cookies.
3.3 Denial of Service (DoS) Unprotected Server Shutdown
What it is: A Denial of Service attack is any action that renders a service unavailable to its
intended users. In web applications, this can happen through resource exhaustion, crashes
induced by malformed input, or — as in this case — an exposed administrative endpoint that can
be triggered by anyone.
How the Attack Was Performed
Browsing to the following URL in any browser, with no authentication whatsoever, caused the
Gruyere server to shut itself down instantly:
Figure 6: Server quit message displayed after accessing the /quitserver endpoint
The browser displayed a brief confirmation message ("Server quit.") and all subsequent requests
to the application returned connection errors, confirming that the server had stopped responding.
Every user connected to that Gruyere instance was immediately affected. The server had to be
manually recovered using the instance reset endpoint.
Impact
• The server becomes completely unreachable for all users the moment the URL is
accessed
Page 7
• No login or special knowledge is required — the attack is open to anyone, including
anonymous visitors
• Manual intervention is required to restore the service
Root Cause
Gruyere defines a _PROTECTED_URLS list that specifies which endpoints require administrative
access before they can be used. The entry in the list read '/quit' rather than '/quitserver' — a
straightforward typo. Because the actual path /quitserver never matched anything in the protection
list, the authorisation check was never invoked and the shutdown proceeded freely.
Figure 7: The PROTECTED URLS list showing the incorrect ’/quit’ entry instead of
’/quitserver’
Fix Applied
_PROTECTED_URLS
1 = [
2 "/ quitserver" , # THIS was missing - that was the
3 bug "/ reset"
]4
Figure 8: Corrected protected URLs list
Outcome After Fix
With the typo corrected, accessing /quitserver without an active administrator session now returns
an authorisation error. The endpoint is no longer reachable by regular or anonymous users.
3.4 Privilege Escalation via Parameter Manipulation
What it is: Privilege escalation occurs when a lower-privileged user manages to gain capabilities
or access rights that should be restricted to higher-privileged roles. One common cause is trusting
client-supplied data to determine what a user is allowed to do, rather than enforcing access rules
on the server side.
How the Attack Was Performed
Page 8
Figure 9: Regular user homepage showing no administrative link (no ”Manage
this server” option)
After logging in as a standard (non-admin) user, the homepage showed no administrative options
— no "Manage this server" link was visible. A crafted URL was then visited that injected the
is_admin=True flag directly into a profile update request:
Figure 10: Homepage after privilege elevation showing the ”Manage this
server” admin- istrative link
The server accepted and stored this parameter without verifying that the requesting user actually
had the authority to change their own administrative status. After signing out and signing back in,
the homepage showed the full administration panel including the "Manage this server" link —
confirming that the regular account had been successfully promoted to administrator.
Impact
• Any authenticated user can grant themselves full administrative control with a single
URL visit
• No technical expertise or access to special tools is required
Page 9
• An attacker with admin access can manipulate all users, server settings, and data
Root Cause
The profile update handler in [Link] accepted is_admin from any incoming request parameter
and stored it directly into the user's profile record, without first verifying whether the person making
the request was themselves an administrator:
Figure 11: Vulnerable code section showing the is admin parameter being accepted
with- out authorisation check
Fix Applied
A server-side check was introduced so that administrative fields can only be modified by a user
who is already an administrator:
#1 VULNERABLE - accepts is_admin from
anyone
2 : self. _Add Parameter (’ is_admin ’, params ,
pr
3 ofile_data )
4
#5 FIXED - only allow admin changes if requester is already
admin
6
: if cookie . get( COOKI E_AD MIN ):
7
self. _Add Parameter (’ is_admin ’, params ,
pr ofile_data ) self. _Add Parameter (’ is_author ’,
params , prFigure 12: ta
ofile_da Fixed
) code with authorisation check
Outcome After Fix
Page 10
Regular users attempting the same URL manipulation now have the is_admin parameter ignored.
Administrative privileges can only be granted by a user who is already operating with
administrator-level authentication.
3.5 Sensitive Data Exposure — Debug Template Left in Production
What it is: Information disclosure vulnerabilities arise when an application unintentionally makes
sensitive data accessible to users who should not be able to see it. A particularly damaging
category involves debug or diagnostic pages that were never intended to be publicly accessible
but were accidentally left in the deployed application.
How the Attack Was Performed
Navigating to the following URL, without any authentication:
[Link]
Figure 13: Database dump showing all users, plain-text passwords, and private
snippets
returned a full dump of the application's entire internal database rendered in the browser. The
output included every user account, each user's password stored in plain text, private snippets,
profile settings, and session-related data. No login was required to access this page.
The plain-text password storage was particularly damaging: an attacker who obtained this dump
would immediately have valid credentials for every account in the system and could log in as any
user, including administrators.
Page 11
Impact
• Complete disclosure of all user records, including usernames and passwords
• Accessible to unauthenticated visitors — no account needed
• Passwords stored in plain text mean every account is compromised the moment this
URL is accessed
Root Cause
A debug file named [Link] was included in the application's deployment by mistake. The file
was not restricted to any user role and was publicly accessible. Combined with the practice of
storing passwords without hashing, this created a severe data-exposure vulnerability.
Fix Applied
Three corrective measures are required:
1. Remove [Link] entirely from the deployed application
2. Hash all passwords before storing them:
# Instead of storing the password directly:
# profile_data['pw'] = newpw <-- never do this
# Store the hashed value:
import hashlib
profile_data['pw'] = hashlib.sha256([Link]()).hexdigest()
3. Any remaining diagnostic endpoints should require administrator authentication and
should never be deployed to production environments
3.6 SQL Injection
What it is: SQL Injection is a technique where an attacker inserts malicious SQL statements into
an input field or URL parameter with the goal of manipulating the database queries that the
application constructs and executes. A successful injection attack can bypass authentication,
retrieve unauthorised data, modify records, or even delete entire tables.
Status in Gruyere
Gruyere is not susceptible to SQL injection because it does not use a relational database of any
kind. User data is stored in an in-memory Python dictionary and serialised to disk using Python's
pickle module. Since no SQL is generated anywhere in the codebase, there is no query to inject
into.
How SQL Injection Works in Vulnerable Applications
In a typical web application backed by a SQL database, a login form might construct its
authentication query by concatenating user input directly into the SQL string:
# Vulnerable approach — input is concatenated directly:
query = "SELECT * FROM users WHERE username = '" + username + "'
AND password = '" + password + "'"
# Attacker enters the following as the username:
# ' OR '1'='1
Page 12
# The resulting query becomes:
# SELECT * FROM users WHERE username = '' OR '1'='1' AND password = ''
# This always evaluates to TRUE — authentication is bypassed entirely.
Recommended Mitigation
The standard defence against SQL injection is the use of parameterised queries (also called
prepared statements), which separate SQL logic from user-supplied data entirely:
# Safe approach — parameterised query:
[Link](
'SELECT * FROM users WHERE username = ? AND password = ?',
(username, hashed_password)
)
Additional layers of defence include input validation, using an ORM with built-in parameterisation,
and applying the principle of least privilege to database accounts.
3.7 Buffer Overflow
What it is: A buffer overflow occurs when a program writes more data into a memory buffer than
the buffer was allocated to hold. The excess data spills into adjacent memory regions, potentially
overwriting critical values such as return addresses, function pointers, or other variables.
Depending on how an attacker crafts the overflow, this can cause the program to crash, execute
arbitrary code, or grant elevated privileges.
Status in Gruyere
Gruyere cannot be exploited through buffer overflow attacks because it is written entirely in
Python. Python is a memory-managed language with the following built-in protections:
• Array and string bounds are enforced automatically — any attempt to access an index
outside a valid range raises an IndexError exception and stops execution
• Python integers have arbitrary precision and will never silently overflow
• Memory allocation and deallocation are managed entirely by the Python runtime; there is
no direct pointer arithmetic or manual memory management in the codebase
How Buffer Overflow Works in Vulnerable Applications
Buffer overflows are a classic vulnerability in lower-level languages such as C and C++. Consider
the following example:
// Vulnerable C code:
void login(char *user_input) {
char buffer[16];
strcpy(buffer, user_input); // No length check!
}
// If user_input is longer than 16 characters, the excess bytes
// overwrite the stack frame — including the return address.
// An attacker can redirect execution to shellcode of their choice.
Recommended Mitigations
Page 13
• Validate the length of all user-supplied input before copying it into fixed-size buffers
• Use length-limiting functions (strncpy rather than strcpy; snprintf rather than sprintf)
• Enable compiler-level protections: stack canaries, Address Space Layout Randomisation
(ASLR), and Data Execution Prevention (DEP/NX)
• Choose memory-safe languages (Python, Java, Rust, Go) wherever performance
requirements allow
4. Conclusion
This assessment of Google Gruyere demonstrated the practical impact of several well-known web
application vulnerability classes. Four vulnerabilities were confirmed through live exploitation,
each traced back to a specific defect in the source code and resolved with a targeted fix:
Cross-Site Scripting (XSS): Two distinct XSS attack paths were identified and exploited. The
first involved injecting a malicious event handler attribute into a stored snippet; the second
leveraged the unrestricted file upload feature to serve a cookie-stealing script from the
application's own domain. Both were fixed through input sanitisation improvements and file type
restrictions.
Denial of Service (DoS): A single-character typo in the protected URL list left the server
shutdown endpoint open to anyone. The fix involved correcting the path string so that the
authorisation check is properly triggered.
Privilege Escalation: A lack of server-side validation on the is_admin parameter meant any
authenticated user could promote themselves to administrator by crafting a URL. A conditional
check was added to ensure only existing administrators can modify privileged fields.
Sensitive Data Exposure: A debugging template accidentally included in the deployment
exposed the entire user database including plain text passwords to unauthenticated visitors. The
remediation involves removing the file, hashing all stored passwords, and restricting any future
diagnostic pages to administrator only access.
SQL Injection and Buffer Overflow could not be exploited against Gruyere due to its architecture
,it stores data in Python dictionaries rather than a SQL database, and Python's memory
management prevents buffer overflow conditions. Both vulnerability types were discussed
theoretically with applicable mitigations documented.
Taken together, the findings reinforce several foundational principles of secure software
development: user input must never be trusted, privilege checks must be enforced on the server,
debug artifacts must be removed before deployment, passwords must always be stored as salted
hashes, and the choice of programming language and framework has a significant impact on the
default security posture of an application.
Page 14
Page 15