June 20, 2026
When a Link Becomes a Weapon: My Fifth XSS Lab on PortSwigger
This one felt different from the previous DOM XSS labs. Instead of a search box reflecting input back into the page, the vulnerability…
Diya
4 min read
This one felt different from the previous DOM XSS labs. Instead of a search box reflecting input back into the page, the vulnerability lives inside a "Back" link on a feedback form and the payload isn't injected into HTML content at all. It's injected directly into an href attribute, turning a harmless navigation link into something that executes JavaScript the moment a user clicks it.
Category: Cross-Site Scripting (XSS) — DOM-based
Difficulty: Apprentice
Lab Link: PortSwigger Web Security Academy
Objective
The goal of this lab is to make the "Back" link on the feedback page call alert(document.cookie) when clicked, by exploiting a jQuery sink that sets an anchor's href attribute using data from location.search.
Vulnerability Overview
This lab simulates a blog application with a feedback submission page. The feedback page has a "Back" link that's supposed to return the user to wherever they came from, using a returnPath query parameter in the URL to determine the destination. A client-side jQuery script reads this parameter from location.search and uses it to set the href attribute of the anchor element with id="backLink".
Because the script assigns the returnPath value directly to the href attribute without any validation or encoding, an attacker can replace the intended path with a javascript: URI, a special URL scheme that executes JavaScript code when the link is clicked. Unlike the previous labs, this vulnerability doesn't execute automatically on page load; it requires the victim to click the "Back" link.
Steps to Reproduce
- Navigate to the lab and access the blog's Submit feedback page. Observe the URL, it contains a
returnPathparameter:
/feedback?returnPath=//feedback?returnPath=/
- Scroll to the bottom of the feedback form and right-click the "< Back" link → Inspect. In the DevTools Elements tab, the anchor element is visible:
<a id="backLink" href="/">Back</a><a id="backLink" href="/">Back</a>The href value / matches the current returnPath parameter confirming that the jQuery script is dynamically setting this attribute from the URL.
- Modify the
returnPathparameter in the address bar to inject ajavascript:URI:
/feedback?returnPath=javascript:alert(document.cookie)/feedback?returnPath=javascript:alert(document.cookie)
Press Enter to reload the page with the new parameter.
- Inspect the "< Back" link again. The
hrefattribute has now been updated to reflect the injected payload:
<a id="backLink" href="javascript:alert(document.cookie)">Back</a><a id="backLink" href="javascript:alert(document.cookie)">Back</a>
- Click the "< Back" link. Because the browser treats
javascript:URIs as executable code, clicking the link triggers the JavaScript and fires analert()popup displaying the current page's cookies.
Technical Evidence
The root cause is in the jQuery script loaded by the feedback page (/resources/js/submitFeedback.js). It reads the returnPath parameter from location.search and passes it directly to jQuery's attr() method to set the href of the back link:
$(function() {
$('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});$(function() {
$('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});No validation is performed on the returnPath value before it's assigned. Because href attributes accept javascript: URIs as valid values, this allows an attacker to turn the back link into an arbitrary JavaScript execution trigger.
The DevTools comparison below shows the before and after states of the href attribute — from the safe default / to the injected javascript:alert(document.cookie).
Result
The lab is successfully solved once the "Back" link is clicked and the alert(document.cookie) popup fires, confirming that the victim's cookies are accessible to the attacker's injected script.
Root Cause
The vulnerability stems from using attacker-controlled data (location.search) to set an HTML attribute specifically hrefwithout validating the URL scheme. jQuery's attr() method assigns the value as-is, with no awareness of the javascript: pseudo-protocol. Because browsers treat javascript: as an executable URI scheme in anchor tags, any user who clicks the manipulated link unknowingly executes the attacker's script.
Impact
This vulnerability requires user interaction (clicking the link), making it slightly harder to exploit than auto-executing XSS. However, in a realistic attack scenario, an attacker would craft a malicious URL and send it to a target the victim would land on the feedback page with the payload already in the URL, and clicking "Back" (a natural action) would trigger execution. If successful, an attacker could:
- Steal session cookies, potentially leading to account takeover
- Access other sensitive data exposed via
document.cookie - Perform actions on behalf of the victim within the application
- Redirect the victim to a malicious site
Remediation
To prevent this type of vulnerability, the application should:
- Validate the URL scheme before assigning any user-controlled value to an
hrefattribute — reject anything that doesn't start withhttps://,http://, or /. - Avoid using raw
location.searchvalues in DOM sink operations without sanitization, even via jQuery helpers likeattr(). - Use an allowlist approach for redirect/return path parameters — only accept known safe paths rather than arbitrary user-supplied values.
- Implement a strict Content Security Policy (CSP) that blocks
javascript:URI execution as a defense-in-depth measure.
Conclusion
This lab was a good reminder that XSS doesn't always look like angle brackets and script tags. Sometimes it's a javascript: URI quietly sitting in an href, waiting for a single click. The sink here jQuery's attr()is widely used and easy to overlook, especially when the input looks like a harmless navigation path. Five labs in, and the pattern is becoming clearer: anywhere user-controlled data meets a DOM sink without validation, there's potential for XSS regardless of what the sink looks like on the surface.