Friday, May 24, 2019

XSS Filter Evasion - No !#$()*;<>| or New Line? No Problem!


Before reading, try the challenge I created!

XSS Filter Evasion Challenge 1
Difficulty: Moderate

Recently, while working on some private bug bounty programs, I ran into similar cross-site scripting (XSS) filters.  These filters triggered on !#$()*;<>| and new line characters.  When triggered, these filters would replace the entire user input string with an empty string.  To illustrate this, and one example where I achieved XSS, I threw together a little challenge I will be walking through in this post.

This is a rather simple example and a bit contrived to illustrate the filter evasion but, similar to the challenge, I had to derive the input parameter from a pretty obvious place in the HTML source code.  In the challenge, if you view the source code, it will reveal an input parameter:

<!-- Sample Usage https://www.pwntheinter.net/XSSFilterEvasion/evasion1.php?redirect=https://www.pwntheinter.net/index.php -->

Using "redirect=test" reveals where our user input string is reflected:



Just like the real world finding, it was a very obvious DOM-based open redirect.  The only real "check" was that the redirect parameter begin with "https://www.pwntheinter.net" which could be easily circumvented using the "@" character.  The browser will treat everything before the "@" as "Basic" HTTP authentication and redirect to anything after the "@".  An example of exploiting this to redirect to https://www.cnn.com:

https://www.pwntheinter.net/XSSFilterEvasion/evasion1.php?redirect=https://www.pwntheinter.net@www.cnn.com

Based on where I saw my input reflected in the HTML source code, I thought I could trivially achieve XSS that bypassed browser protections, such as Chrome's XSS Auditor.  I started with the tried and true simple payload of ";alert(1);// for the quick win and found my entire input string was replaced with an empty string.


At this point, it's pretty obvious we are up against some sort of server-side XSS protections.  One easy way to see what characters trigger the protections and erase the input string is to use Burp Intruder with a payload list of all printable characters.  For fun, and some coding practice, you could write a quick script in python to achieve the same results.  Below is a link to one such script.  This is a simple example without threading or the use of a regex which would make it faster and more robust.

https://gist.github.com/reigningshells/191d1a7b3af2433bd552eb94120986b9

Running the script against the target reveals the bad characters that cannot be used in our payload:


Now we need to figure out how we are going to end the variable assignment and run our own JavaScript code.  We can use a double quote to end the string but new lines and semicolon are filtered out.  What can we do?



Well, we're in luck because our payload is reflected within a function.  We can end the string with a double quote, end the function with a closing bracket, and start a block statement with an opening bracket: "}{

This would also benefit us if we were injecting into a JavaScript function that wasn't being called because our newly created block statement would run automatically.  Seems like we are in good shape, but how do we call a function without parentheses?


This admittedly had me stumped for a little while.  I did some googling and nothing I found was working.  Then it hit me!  What if I encoded the parentheses?  How would I call a function using encoded parentheses?   Maybe, document.location?  My next thought was to try something like this:

"}{document.location="javascript:alert\50document.domain)\51

Where the parentheses are octal encoded.  But when I did...FILTERED!


The XSS sanitization wasn't just looking for bad characters, but common function names used in XSS POCs.  You can do something similar to what we did to look for bad characters with common JavaScript function names.  The ones I found that were filtered were alert, confirm, eval, prompt and the word script.  This led me to believe these XSS defenses may have been implemented after a fellow hunter reported it and was mistakenly closed despite a bypass to this solution.  Since we already encoded the parentheses we can just do the same for random characters in the bad strings to get by the filter.  Final payload:

"}{document.location="j\141v\141scri\160t:\141lert\50document.domain\51

https://www.pwntheinter.net/XSSFilterEvasion/evasion1.php?redirect=%22}{document.location=%22j\141v\141scri\160t:\141lert\50document.domain\51


And this bypasses Chrome's XSS Auditor!


That's all for this entry.  Nothing earth shattering but I hadn't seen this method for bypassing XSS filters before and thought it was worth sharing.  Feel free to comment with any alternative solutions.
Copyright © 2015 Reigning Shells