From a technical point of view, XSS is an injection-class vulnerability, in which the attacker manipulates the logic of the web application in a browser. So to prevent such vulnerabilities, one needs to thoroughly check any data that enters the application from the outside. To do so, the application must implement a number of approaches, which we describe here.
Force inputs to the same data type
User input is initially presented in string form. This data should be transformed into objects of a specified type. This functionality is often implemented at the framework level, so that the action is transparent to the user. One such transparent process takes place in the following Java code:
@GetMapping(value = "/result")
public void getJobResult(
@RequestParam(name = "scan-id") Integer scanId,
@RequestParam(name = "artifact") String artifact,
HttpServletResponse response) throws ServiceUnavailableException {
// Real controller code skipped
This example shows the declaration of an HTTP GET request handler. It's accessible at the relative path "/result" and takes two required parameters as input: integer scan-id and string artifact. During initial processing of the request, the framework performs the actions needed to determine the correctness of the inputs.
In the event of a type mismatch, the requesting party sees an error message even before control transfers to the application code. This would happen if, for instance, a scan-id parameter contains a non-numeric value.
Input validation
During validation, input data is checked against both grammatical and semantic criteria. The user's year of birth, say, can be checked for grammar with the regular expression "^[0-9]{4}$". This expression verifies that the string truly consists of four (and only four) digits. Once the string is converted to a number, we then need to check semantics: the year of birth should not be 1000 or 9876.
It also makes sense to apply allowlist and blocklist validation. For a blocklist, you need to define certain patterns that shouldn't be found in the input data.
However, blocklist approaches have a number of serious disadvantages. Patterns tend to be needlessly complex and become obsolete quickly. Creating patterns to cover all possible permutations of malicious data is far from easy. Developers will inevitably find themselves struggling to catch up with attackers. This is why it is more efficient to apply an allowlist that defines rules with which input data must comply.
Output sanitization
Regardless of how well (or not) the previous two techniques are implemented, the key action for preventing XSS is to check and convert output data. It is important to ensure that untrusted data cannot be passed to an HTML document. The exceptions are certain contexts in which the data still needs to follow certain rules. These rules must ensure that the web browser treats the output as data, not as code. These contexts include:
For each of these contexts, you should apply individual verification and conversion rules. For the content of an HTML element (such as inside <div>, <p>, <b>, <td>, and similar tags), XML and HTML special characters should be replaced with safe variants. Replace "&" with "&", "<" with "<", ">" with ">", double quotes with """, and single quotes with ""'".
The "/" character, which can close HTML tags, is replaced by "/". For example, when using the StringEscapeUtils.escapeHtml4 function from the Apache Commons Text library on the server side, user-entered data is made safe like so:
String data = "<script>alert(1)</script>";
String safeData = StringEscapeUtils.escapeHtml4(data);
The result of the conversion will be the string "<script>alert(1)</script>", which will not be parsed by the browser as an HTML escape sequence.
The LibProtection library allows automatically determining context and sanitizing data. In addition, the library can signal when input data contains an attack vector.
For example, the following code fragment contains three points at which user data can be embedded:
- Value attribute of HTML element (a)
- Value of JavaScript parameter (b)
- Content of HTML element ©
Response.Write($"<a href='{a}' onclick='alert("{b}");return false'>{c}</a>");
Let's assume that the attacker has passed the following variables as input:
a = 'onmouseover='alert(`XSS`)
b = ");alert(`XSS`)
c = <script>alert(`XSS`)</script>
If we do not check the input, the response will look like this:
<a href=''onmouseover='alert(`XSS`)' onclick='alert("");alert(`XSS`)");return false'><script>alert(`XSS`)</script></a>
Thus an attacker can perform an XSS attack in three different ways. LibProtection converts data, determining the context and applying rules in a way transparent to the developer:
Response.Write(SafeString.Format<Html>($"<a href='{a}' onclick='alert("{b}");return false'>{c}</a>"));
The resulting string is converted to:
<a href='%27onmouseover%3d%27alert(%60XSS%60)' onclick='alert("\");alert(`XSS`)");return false'><script>alert(`XSS`)</script></a>
LibProtection supports C #, Java, and C ++ and allows sanitizing inputs against other types of attacks. This protection extends to SQL injection and vulnerabilities based on improper handling of URLs and path directories.
Client- and server-side sanitization
Data can be sanitized in different ways based on the application architecture and programming languages used. Because technologies and languages can be so different, it is hard to recommend one single way for checking on the server side. But since JavaScript has become the de facto standard on the client side, we can still try to formulate universal principles for a number of contexts:
All sanitizing algorithms have limitations. For instance, even if you apply rules to sanitize the href HTML attribute, an attacker can still pass URL values that start with "javascript:".
Additional XSS prevention measures
Checking inputs and outputs is the main way to protect against XSS attacks—but not the only one. You should also consider:
- Standardization at the header level of Content-Type and X-Content-Type-Options. Ensure that the server response type is not "text / html" and prevent the browser from automatically detecting the data type (X-Content-Type-Options: nosniff).
- Use of a Content Security Policy (CSP) to minimize negative consequences when malicious code is injected.
XSS cheat sheet
This cheat sheet provides guidance against a huge number of XSS attack vectors. Even just basic rules will be enough to stop the majority of attacks. Here are the most important ones:
- Deny all untrusted data unless it's inserted in allowed locations.
- Use HTML escaping before putting untrusted data into the HTML body.
- Use escaping in HTML attributes before adding untrusted data into HTML common attributes.
- Escape JavaScript before putting untrusted data inside data values.
- Escape CSS before inserting untrusted data into HTML style property values.
- Escape URLs before passing untrusted data to HTML URL parameter values.
- Use a library to parse and sanitize HTML formatted text.
Since XSS can be used to infiltrate a website and attack users in many ways, it's important to approach security from multiple perspectives. Developers need to receive training on secure coding and best practices. Scan the codebase as early and frequently as possible to detect potential flaws and breaches. Pay special attention to accounts that have administrative privileges and the ability to modify page contents. To get a better angle on website security, refer to trustworthy sources like the OWASP Cheat Sheet.