1

I've been reading a bundle of documents about DOM based XSS but I still can't figure it out. Let's take a look at my example.

var html = `
    <a class="url" href="${untrustedURL}">
        <img src="${untrustedSource}">
    </a>
    <span class="name" data-value="${untrustedValue}">${untrustedText}</span>
`;
document.querySelector('#user').innerHTML = html;

How can an attacker exploit the vulnerabilities of this code? And what are the solutions?

SilverlightFox
  • 32,436
  • 11
  • 76
  • 145
Lewis
  • 14,132
  • 12
  • 66
  • 87

3 Answers3

3

Through the url and through the image's source, an untrusted value would be, for instance:

javascript:evilStuff()

In the case of the link, the code will run when the user clicks it, and in the case of the image's source, it'll run when the browser attempts to load the image. Note that the image src's technique only applies to older browsers, modern ones will ignore it. Another issue I see with the link, is that, for instance, you could get a link that directs you to a phishing site!

The data-value attribute is made vulnerable only if you use that value somewhere else in your code, and in a way that could be harmful, otherwise, I don't see the danger in there.

And as for the contents of the span, pretty much anything can be inserted in there if you don't escape HTML characters. Script tags, iframes, images, etc... Note that this is true for all unsafe values you insert anywhere.

A malicious person can insert any HTML anywhere where you don't escape HTML entities.

The solutions, I think, are always to escape/strip tags and certain values. For instance, to prevent an user from inserting a dangerous url in a dynamic href, you can apply a regex that removes the word javascript: from the start of a string, or check for non-valid urls (different domain, unusual characters, malformed url, etc).

  • I now understand that XSS strongly depends on the execution context. Thanks for your nice explanation. BTW, OWASP recommends to `encode Javascript` after escaping HTML entities. Encoding Javascript is about escaping all non-alphanumeric characters with `\xHH` format. Do you have any idea how to achieve this in Javascript? – Lewis May 18 '15 at 13:13
  • 1
    The `data-value` attribute is just as vulnerable as everything else if it is not escaped: you can break out of the attribute (and of the tag). – Bergi May 19 '15 at 05:59
3

While I agree with the vulnerabilities raised in @Alfonso's answer, the situation is actually worse: All of your untrusted variables are vulnerable to an XSS attack here.

For example,

say untrustedURL contained the following text

"><img src="http://example.com" onerror=alert(/xss/) data-x="

this would cause the following to be rendered:

<a class="url" href=""><img src="http://example.com" onerror="alert(/xss/)" data-x="">

which will cause the JavaScript alert to show instantly:

ESS XSS

As your code is all in a JavaScript context already, you need to follow Rule #1 of the OWASP XSS cheat sheet and HTML encode the data. A simple conversion of the following characters is sufficient:

 & --> &amp;
 < --> &lt;
 > --> &gt;
 " --> &quot;
 ' --> &#x27;     &apos; not recommended because its not in the HTML spec (See: section 24.4.1) &apos; is in the XML and XHTML specs.
 / --> &#x2F;     forward slash is included as it helps end an HTML entity

Note that OWASP recommend Rule #2 for HTML attribute values, however if you are quoting all attributes then the above will be enough. Rule #2 works everywhere, including unquoted, so if you have a mixture Rule #2 will be simpler.

I've read your comment regarding that you say you should encode Javascript after escaping HTML entities.

Yes, this applies to where the value is initially from (say from the server side), but you should do this encoding in the language that your server side code uses, not JavaScript. Also, do the JavaScript escaping first to get the server side variable into JavaScript, then later use HTML escaping in JavaScript ready for insertion into the DOM.

e.g. JavaScript escaping in ASP.NET C#:

<script>
var untrustedURL = "<%=HttpUtility.JavaScriptEncodeString(usersUrl)%>";
</script>

See my answer here for greater detail on this.

Then you need to HTML encode using a function:

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;')
}

So your code could just be

<script>
var untrustedURL = escapeHTML("<%=HttpUtility.JavaScriptEncodeString(usersUrl)%>");
</script>

untrustedURL and untrustedSource

Note that these are special cases where validation should take place as well. You should do this on the server side and make sure that they either start with http://, https:// or // (protocol relative URL). A whitelisting approach ensures that the user cannot enter a javascript: scheme URL and will also protect against different schemes from being entered that may be unique to the user's browser, operating system, device, configuration, etc. Only allowing HTTP is much safer.

Community
  • 1
  • 1
SilverlightFox
  • 32,436
  • 11
  • 76
  • 145
  • I think I have a much better understanding about XSS now. Big thanks for your answer. BTW, if those attributes are unquoted, should I follow OWASP XSS Cheat Sheet Rule #2? – Lewis May 19 '15 at 08:04
  • @Tresdin: No problem. Yes, that is correct. The reason I didn't mention it here was because in your code they are quoted and I wanted to keep things simple and to the point. You could adapt that `escapeHTML` function to encode everything except alphanumerics. – SilverlightFox May 19 '15 at 08:07
0

Assuming that ${untrustedText} is not HTML-escaped, try setting it to, say:

<div style="position:fixed;left:0;right:0;top:0;bottom:0;"
  onmousemove="this.style.display='none';alert('XSS');"></div>
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153