Stored XSS into href Attribute with Double Quotes HTML-Encoded
Stored XSS into href Attribute with Double Quotes HTML-Encoded
Stored Cross-Site Scripting (Stored XSS) is one of the most dangerous classes of web vulnerabilities because the malicious payload is persisted on the server and served to multiple users. Unlike reflected XSS, which requires a victim to click a crafted link, stored XSS executes whenever a user visits the affected page. In this lab scenario, the vulnerability occurs inside an anchor (<a>) element’s href attribute, where user input is stored and later rendered with double quotes encoded, but without proper validation of the URL scheme. This creates a subtle yet powerful attack vector.
To understand the issue, consider a typical application feature such as a comment system or profile field where users can submit a website link. The application stores this input and later renders it inside an anchor tag like this:
<a href="USER_INPUT">View profile</a>
In an attempt to prevent injection, the application encodes double quotes (" → "). At first glance, this seems like a reasonable defense. Encoding quotes prevents an attacker from breaking out of the attribute and injecting additional attributes such as event handlers (onclick, onmouseover, etc.). However, this protection only addresses attribute-breaking attacks, not malicious values inside the attribute itself.
This is where the vulnerability lies. Even if the attacker cannot escape the href attribute, they can still control its entire value. Browsers do not restrict href attributes to safe protocols like http or https. They also support other URI schemes, including the dangerous javascript: scheme. When a link uses this scheme, clicking it causes the browser to execute the JavaScript code directly instead of navigating to a new page.
An attacker can exploit this by submitting a payload such as:
javascript:alert(1)
Because the application encodes double quotes, the rendered HTML might look like this:
<a href="javascript:alert(1)">View profile</a>
Notice that the payload remains fully intact. The encoding of quotes has no impact here because the attacker never needed to break out of the attribute. Instead, they are leveraging the browser’s built-in behavior for handling JavaScript URLs. When a victim clicks this link, the JavaScript executes in the context of the application, leading to XSS.
The fact that this is a stored vulnerability makes it significantly more severe. Once the payload is saved, it can affect any user who views the page containing the malicious link. For example, if this link appears in a public comment section, every visitor who clicks it becomes a potential victim. This persistence also enables attackers to combine the vulnerability with social engineering techniques, making the link appear legitimate or enticing to click.
A key detail in this lab is the mention that double quotes are HTML-encoded. This often misleads developers into believing the application is secure. In reality, encoding quotes only protects against one specific type of injection: breaking out of the attribute context. It does not sanitize the content of the attribute itself. This highlights an important principle in web security: Proper defense requires both context-aware encoding and input validation.
In this case, encoding alone is insufficient because the browser still interprets the href value as a navigable URL. Since javascript: is a valid URI scheme, the browser executes it without hesitation.
Another important aspect to consider is how modern browsers handle such links. While some security mechanisms and browser features attempt to mitigate abuse of javascript: URLs (for example, restrictions in certain contexts or Content Security Policy rules), they are not universally reliable defenses. If no strict Content Security Policy (CSP) is in place, or if inline script execution is allowed, the payload will execute as expected.
The root cause of this vulnerability is the application’s failure to validate user input before storing it. Instead of ensuring that the input conforms to safe URL formats, the application blindly accepts any string and embeds it into the href attribute. This lack of validation allows attackers to supply malicious schemes that the browser later interprets as executable code.
To properly mitigate this issue, developers must implement strict validation of URL inputs. One effective approach is to enforce an allowlist of safe schemes, such as http and https. Any input that does not begin with an approved scheme should be rejected or sanitized. For example, the application can parse the URL and explicitly check its protocol before storing or rendering it.
Additionally, developers should avoid relying solely on encoding as a defense mechanism. While encoding is essential for preventing context-breaking attacks, it does not address all forms of XSS. In this scenario, even perfectly encoded quotes do nothing to stop a javascript: payload.
Another layer of defense can be added through a strong Content Security Policy (CSP). A properly configured CSP can block the execution of inline JavaScript and restrict the use of dangerous URI schemes. For instance, disallowing unsafe-inline scripts and limiting allowed sources can reduce the impact of XSS vulnerabilities. However, CSP should be considered a defense-in-depth measure, not a primary fix.
It is also worth noting that safer alternatives exist when handling user-provided URLs. Instead of directly embedding raw input into HTML, developers can use APIs that construct URLs safely or sanitize them using well-tested libraries. For example, parsing the URL using built-in browser APIs and reconstructing it ensures that only valid and safe components are used.
From a penetration testing perspective, this type of vulnerability is relatively straightforward to identify once you understand the pattern. Whenever you see user input reflected inside an href attribute, especially in a stored context, you should test whether the application allows dangerous schemes like javascript:. If it does, and no additional protections are in place, the application is likely vulnerable.
In conclusion, this lab demonstrates how a seemingly minor oversight failing to validate URL schemes can lead to a full stored XSS vulnerability. Encoding double quotes may prevent certain injection techniques, but it does not eliminate the risk of malicious attribute values. By understanding how browsers interpret href attributes and URI schemes, both developers and security professionals can better identify and mitigate these subtle yet impactful vulnerabilities.
Comments