AppVuln


XSS Tricky: innerText's HTML Decoding

XSS is in the air, everywhere I look around. A simplified version of the code:

<div id="foo">
<?php echo htmlspecialchars($input, ENT_QUOTES, 'UTF-8'); ?>
</div>

<script>
var bar = document.getElementById('foo');
var text = bar.innerText;
bar.innerHTML = '<b>' + text + '</b>';
</script>

So our user-supplied $input is echoed after having HTML entities encoded. Then its DOM element is read with innerText. This property returns only the text from a DOM element. Next, some decorative tags are added to the text and the whole thing is written back as markup, using innerHTML. Although there’s usually little reason to do this in JavaScript, our original input is encoded, so it doesn’t appear to be vulnerable to XSS.

And yet… it is. But why?

Lets feed a common image-based XSS test to $input:

<img src=x onerror=alert(1)>

The $input is encoded with htmlspecialchars(), effectively preventing our XSS from functioning:

 &lt;img src=x onerror=alert(1)&gt;

Our encoded $input is read by innerText, which should remove any existing tags and return the rendered text contents of HTML elements. So, at this point, we could assume our $input would still be:

&lt;img src=x onerror=alert(1)&gt;

Or, if it also removes encoded tags, possibly:

alert(1); 

But wait, there’s more! innerText actually decodes HTML entities and will happily return valid HTML, restoring our $input to its original state:

<img src=x onerror=alert(1)>

The decorative formatting tags are now added to the $input:

<b><img src=x onerror=alert(1)></b>

The $input is then written back to the page via the innerHTML property. innerHTML won’t execute script tags on its own but our image tag writes with no problem, and the event handler will launch our test payload.

So, keep an eye out for the next time you see innerText handling user input, XSS Rangers.

And just as a bonus…

The word on the street is that jQuery’s helper methods both use the same underlying logic. Which means the trick works with the following code, too:

<script>
var bar = $('#foo').text();
$('#foo').html('<b>' + bar + '</b>');
</script>

html() will even execute JavaScript for you, so even a standard <script>alert(1)</script> will work. Not too bad.

#Javascript #Programming #Xss #Appsec