XSS Prevention Cheatsheet
How to prevent XSS in Java, Python, Node.js, C#, Go, and Scala
Join the DZone community and get the full member experience.
Join For FreeXSS, or Cross-Site Scripting, is one of the most common vulnerabilities found in applications. In bug bounty programs of different organizations, XSS consistently ranks as the most common vulnerability found. Today, let’s learn how these attacks work, how they manifest in code, and how to prevent them in your programming language. Let’s dive right in!
Anatomy of an XSS attack
XSS happens whenever an attacker can execute malicious scripts on a victim’s browser.
Applications often use user input to construct web pages. For example, a site might have a search functionality where the user can input a search term, and the search results page will include the term at the top of the results page. If a user searches “abc”, the source code for that page might look like this:
<h2>You searched for abc; here are the results!</h2>
But what if that application cannot tell the difference between user input and the legitimate code that makes up the original web page?
Attackers might be able to submit executable scripts and get that script embedded on a victim’s webpage. These malicious scripts can be used to steal cookies, leak personal information, change site contents, or redirect the user to a malicious site. There are three main types of XSS attacks: reflected XSS, stored XSS, and DOM XSS.
Reflected XSS
For example, if the application also allows users to search via URLs:
https://example.com/search?q=abc
If an attacker can trick victims into visiting this URL:
https://example.com/search?q=<script> some malicious script</script>
The script in the URL will become embedded in the page the victim is visiting, making the victim’s browser run the JS code contained within the <script>
tags. This is called a “reflected XSS” attack.
<h2>You searched for <script> some malicious script</script>; here are the results!</h2>
Stored XSS
During a stored XSS attack, the attacker places the malicious script into a database before it gets returned to the victim. Let’s say that example.com also allows users to post status updates for others to see. An attacker can post this status update:
POST /status/updatestatus=<script> some malicious script </script>
This malicious script will become embedded on the attacker’s profile page, attacking anyone who visits the attacker’s profile page.
DOM XSS
Finally, DOM-based XSS is similar to reflected XSS, except that in DOM-based XSS, the user input never leaves the user’s browser. Since the malicious input is never sent to the server, this type of XSS is harder to detect and prevent.
As in reflected XSS, attackers submit DOM-based XSS payloads via the victim’s user input. Unlike reflected XSS, a DOM-based XSS script doesn’t require server involvement, because it executes when user input modifies the source code of the page in the browser directly. Say a website allows the user to change their locale by submitting it via a URL parameter:
https://example.com?locale=north+america
The URL parameter isn’t submitted to the server. Instead, it’s used to change the language of the webpage by a client-side script of the application. But if the website doesn’t validate the user-submitted parameter, an attacker can trick victims into visiting a URL like this one:
https://example.com?locale=<script> some malicious script </script>
The site will embed the payload on the user’s web page, and the victim’s browser will execute the malicious script.
Defeating XSS
The key to preventing XSS is output encoding. You should never insert user-submitted data directly into an HTML document. Instead, you should encode any untrusted input that ends up on an HTML page so that browsers know the input should be treated as content and not raw HTML. This will make sure that attackers cannot influence the way browsers interpret the information on the page by submitting dangerous characters or character sequences. For example, if someone submits <script>alert(1)</script>
, browsers should treat <script>
and </script>
as user content, not HTML script tags.
To prevent XSS, you should encode characters that have special meaning in HTML, such as the &
character, angle brackets, single and double quotes, and the forward-slash character. In our example, you can encode the left and right angle brackets can be encoded into HTML characters < and > to prevent browsers from treating the content as script tags.
However, there are many ways attackers can use to smuggle executable Javascript code into a victim's browser. And a blocklist-based encoding scheme like this one might miss a few characters that will allow attackers to achieve XSS. So, it might be worth it to consider stricter approaches to input validation. For example, you can validate user input against a list of allowed values, or only allow a limited set of characters (such as only alphanumeric characters in usernames) in user input.
The prevention of DOM-based XSS requires a different approach. Since the malicious user input won’t pass through the server, sanitizing the data that enters and departs from the server won’t work. Instead, you should avoid rewriting the HTML document based on user input, and implement client-side input validation before user input is inserted into the DOM.
Defense in Depth
You can also take measures to mitigate the impact of XSS flaws if they do happen. First, set the HttpOnly
flag on sensitive cookies that your site uses. This prevents attackers from stealing cookies via XSS. You should also implement the Content-Security-Policy
HTTP response header. This header lets you restrict how resources such as JavaScript, CSS, or images load on your web pages. To prevent XSS, you can instruct the browser to execute only scripts from a list of sources.
Preventing XSS in your Programming Language
Now, let’s talk about how you can prevent XSS vulnerabilities in your programming language!
Java
If you are using Java Server Pages (JSP), you need to be aware that JSP templates do not escape dynamic content by default. Let’s say that you want to display a message this way:
<h1>${message}</h1>
You will need to use the <c:out>
tag or fn:escapeXml()
function to escape potentially dangerous content in untrusted input:
<h1><c:out value="${message}"/></h1><h1>${fn:escapeXml(message)}</h1>
Python
Most Python template languages will take care of output encoding for you. For example, Jinja2 will automatically encode any input placed within curly braces. This should prevent XSS in most cases.
{{ user-input }}
Node.js
Most Javascript template languages escape dynamic content by default. For example, the Nunjucks template language will automatically escape anything between curly braces:
{{ "<script>alert(1)</script>" }}
This will output in HTML:
<script>alert(1)</script>
C#
The Razor template language that uses C# automatically escapes dynamic content automatically and protects against most potential XSS attacks:
@{
string message = "<script>alert(1)</script>";
}<h1>@message</h1>
This code will render the HTML code:
<h1><script>alert(1)</script></h1>
Go
The Go package html/template
will automatically escape dynamic content, protecting you from most XSS:
import "html/template"t, err := template.New("foo").Parse(`{{define "T"}}{{.}}{{end}}`)err = t.ExecuteTemplate(out, "T", "<script>alert(1)</script>")
This code will write the escaped string <script>alert(1)</script> to the output variable out
.
On the other hand, the "text/template"
package does not offer this protection.
Scala
Most template languages in Scala will also encode dynamic content by default. For instance, in the Play framework, this user input will be displayed safely:
<p>
@(user-input)
</p>
Overriding Safe Defaults
For each template language that automatically encodes dynamic content, there are ways of overriding the safe default, which might render the application vulnerable. And if you are constructing HTML code manually from user input and not using templates to render dynamic content, then you’ll need to find a way to encode the user input before inserting it into HTML strings. See our vulnerability database (TODO: add vulnerability database link) for some example scenarios that might make an application vulnerable and for some tips for escaping content manually in different programming languages.
Opinions expressed by DZone contributors are their own.
Comments