pineappleman pineappleman - 1 year ago 66
Javascript Question

XSS prevention and .innerHTML

When I allow users to insert data as an argument to the JS

function like this:

element.innerHTML = “User provided variable”;

I understood that in order to prevent XSS, I have to HTML encode, and then JS encode the user input because the user could insert something like this:

<img src=a onerror='alert();'>

Only HTML or only JS encoding would not help because the
method as I understood decodes the input before inserting it into the page. With HTML+JS encoding, I noticed that the
decodes only the JS, but the HTML encoding remains.

But I was able to achieve the same by double encoding into HTML.

My question is: Could somebody provide an example of why I should HTML encode and then JS encode, and not double encode in HTML when using the

Answer Source

Could somebody provide an example of why I should HTML encode and then JS encode, and not double encode in HTML when using the .innerHTML method?


Assuming the "user provided data" is populated in your JavaScript by the server, then you will have to JS encode to get it there.

This following is pseudocode on the server-side end, but in JavaScript on the front end:

var userProdividedData = "<%=serverVariableSetByUser %>";
element.innerHTML = userProdividedData;

Like ASP.NET <%= %> outputs the server side variable without encoding. If the user is "good" and supplies the value foo then this results in the following JavaScript being rendered:

var userProdividedData = "foo";
element.innerHTML = userProdividedData;

So far no problems.

Now say a malicious user supplies the value "; alert("xss attack!");//. This would be rendered as:

var userProdividedData = ""; alert("xss attack!");//";
element.innerHTML = userProdividedData;

which would result in an XSS exploit where the code is actually executed in the first line of the above.

To prevent this, as you say you JS encode. The OWASP XSS prevention cheat sheet rule #3 says:

Except for alphanumeric characters, escape all characters less than 256 with the \xHH format to prevent switching out of the data value into the script context or into another attribute.

So to secure against this your code would be

var userProdividedData = "<%=JsEncode(serverVariableSetByUser) %>";
element.innerHTML = userProdividedData;

where JsEncode encodes as per the OWASP recommendation.

This would prevent the above attack as it would now render as follows:

var userProdividedData = "\x22\x3b\x20alert\x28\x22xss\x20attack\x21\x22\x29\x3b\x2f\x2f";
element.innerHTML = userProdividedData;

Now you have secured your JavaScript variable assignment against XSS.

However, what if a malicious user supplied <img src="xx" onerror="alert('xss attack')" /> as the value? This would be fine for the variable assignment part as it would simply get converted into the hex entity equivalent like above.

However the line

element.innerHTML = userProdividedData;

would cause alert('xss attack') to be executed when the browser renders the inner HTML. This would be a DOM Based XSS attack.

This is why you would need to HTML encode too. This can be done via a function such as:

function escapeHTML (unsafe_str) {
    return unsafe_str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/\"/g, '&quot;')
      .replace(/\'/g, '&#39;')
      .replace(/\//g, '&#x2F;')

making your code

element.innerHTML = escapeHTML(userProdividedData);

or could be done via JQuery's text() function.

Update regarding question in comments

I just have one more question: You mentioned that we must JS encode because an attacker could enter "; alert("xss attack!");//. But if we would use HTML encoding instead of JS encoding, wouldn't that also HTML encode the " sign and make this attack impossible because we would have: var userProdividedData ="&quot;; alert(&quot;xss attack!&quot;);&#x2F;&#x2F;";

If the attacker entered \ then they could force the browser to miss the closing quote (as \ is the escape character in JavaScript).

This would render as:

var userProdividedData = "\";

which would trigger a JavaScript error because it is not a properly terminated statement. This could cause a Denial of Service to the application if it is rendered in a prominent place.

Additionally say there were two pieces of user controlled data:

var userProdividedData = "<%=serverVariableSetByUser1 %>" + ' - ' + "<%=serverVariableSetByUser2 %>";

the user could then enter \ in the first and ;alert('xss');// in the second. This would change the string concatenation into one big assignment, followed by an XSS attack:

var userProdividedData = "\" + ' - ' + ";alert('xss');//";

Because of edge cases like these it is recommended to follow the OWASP guidelines as they are as close to bulletproof as you can get. You might think that adding \ to the list of HTML encoded values solves this, however there are other reasons to use JS followed by HTML when rendering content in this manner because this method also works for data in attribute values:

<a href="javascript:void(0)" onclick="myFunction('<%=JsEncode(serverVariableSetByUser) %>'); return false">

Despite whether it is single or double quoted:

<a href='javascript:void(0)' onclick='myFunction("<%=JsEncode(serverVariableSetByUser) %>"); return false'>

Or even unquoted:

<a href=javascript:void(0) onclick=myFunction("<%=JsEncode(serverVariableSetByUser) %>");return false;>

If you HTML encoded like mentioned in your comment an entity value:

onclick='var userProdividedData ="&quot;;"' (shortened version)

the code is actually run via the browser's HTML parser first, so userProdividedData would be


instead of


so when you add it to the innerHTML call you would have XSS again. Note that <script> blocks are not processed via the browser's HTML parser, except for the closing </script> tag, but that's another story.

It is always wise to encode as late as possible such as shown above. Then if you need to output the value in anything other than a JavaScript context (e.g. an actual alert box does not render HTML, then it will still display correctly).

That is, with the above I can call


just as easily as setting HTML

element.innerHTML = escapeHTML(userProdividedData);

In both cases it will be displayed correctly without certain characters from disrupting output or causing undesirable code execution.