I Found XSS Security Flaws in Rails – Here's What Happened.

I Found XSS Security Flaws in Rails – Here's What Happened

Take a look at this code:

JS_ESCAPE_MAP = {
  "'"    => "\\'",
  '"'    => '\\"',
  '\\'   => '\\\\',
  "\r\n" => '\n',
  "\n"   => '\n',
  "\r"   => '\n',
}

def escape_javascript(javascript)
  # replace every unsafe character with safe version
  return javascript.gsub(/(\\|\r\n|[\n\r"'])/u, JS_ESCAPE_MAP)
end

This is a simplified version of what protects JavaScript strings in the Ruby on Rails framework from attacks.

Except for the past 5 years, there've been 2 things missing that could still allow attackers a way in.

So what's wrong with it?

Ruby on Rails is a web framework used for quickly building web applications with minimal setup and configuration.

Although it's isn't so trendy anymore, it's stable and reliable in most cases so it's still used by a lot of companies like Github, Shopify, Airbnb, Soundcloud and even here at Chef Secure.

Here's the problem

The code I showed you earlier is a function used by developers to escape untrusted data for safe use in JavaScript strings.

You can use it like this:

string = '<%= j untrusted_data %>'

or like this

string = '<%= escape_javascript untrusted_data %>'

And it'll stop untrusted data from running malicious code in XSS attacks.

As you know, JavaScript strings can be created with single or double quotes. However when ES6 was introduced in 2015, it added a new way to create strings using what's called template literals.

'string1'
"string2"
`string3`

The difference: template literals allow you to build strings while including variables and expressions inside.

Walking through the vulnerability

Here's an example. Let's say you want to show a user's online status using their name and a getStatus function that takes in their id so the end result looks like this:

Jesse is Online

The old way would be to add the different pieces of the string together.

'<%= j user.name %> is ' + getStatus(<%= user.id %>)

The new way with template literals allows you to combine these pieces together inside a single string:

`<%= j user.name %> is #{getStatus(<%= user.id %>)}`

So how do we exploit this?

As I go over in the Attacks inside JavaScript recipe in my XSS course, the most common way attackers achieve XSS in JavaScript is by breaking out of strings in order to get into the execution context - the place where your code gets run.

Now, in contrast to HTML where you can have invalid syntax without much consequence, JavaScript is very picky and will stop working with even the smallest error, so you need to make sure you end up with valid JavaScript after your injection.

The pattern I present in the recipe is simple:

  • start with your payload (alert())
  • surround it with matching string characters
  • and finally add a plus in between each part

stringChar+alert()+stringChar

This allows you to break out of JS strings without errors – just like adding in a new variable.

Do you see what's missing now?

The first attack

The escaping doesn't account for backticks being used to create strings via template literals. In this case, this means we can run our exploit using the exact same pattern with backticks for the string characters.

`+alert()+`

Okay, so let's add protection from backticks.

JS_ESCAPE_MAP = {
  "'"    => "\\'",
  '"'    => '\\"',
  '`'    => '\\`',
  ...

def escape_javascript(javascript)
  # replace every unsafe character with safe version
  return javascript.gsub(/(\\|\r\n|[\n\r"'`])/u, JS_ESCAPE_MAP)
end

There's still another problem

It turns out that the same benefit offered by template literals to combine expressions inside strings, also allows attackers to execute malicious code without even having to break out of the string!

So to launch an attack this time, we'd just surround our payload with the ${} interpolation piece and we don't have to worry about any extra parts.

${alert()}

And now the second fix is to escape the $ character to stop this.

JS_ESCAPE_MAP = {
  "'"    => "\\'",
  '"'    => '\\"',
  '`'    => '\\`',
  '$'    => '\\$',
  ...

def escape_javascript(javascript)
  # replace every unsafe character with safe version
  return javascript.gsub(/(\\|\r\n|[\n\r"'`$])/u, JS_ESCAPE_MAP)
end

We were warned

It turns out this scenario was already discussed 8 years ago.

After the Rails patch was released, James Kettle, Director of Research at PortSwigger, sent me a message on Twitter linking to a discussion on adding template literals to JavaScript where Gareth Heyes warns about the holes that will open up as a result.

Gareth warns:

... this will introduce a new class of DOM based XSS attacks since developers in their infinite wisdom will use this feature to place user input inside ...

Read the full discussion here.

What else is vulnerable?

Considering that I appear to be the first to report this type of vulnerability and how long it's remained unpatched, it makes me wonder how often it's been exploited in the wild and what other frameworks and modules share this same problem.

Timeline

This issue has taken over a year to fix since originally reporting to the Rails team.

Awkward Silence Rails Logo At least HackerOne has a badge for Patience

  • January 3, 2019: Submitted issue with solution to fix on HackerOne
  • January 3, 2019: Received response on addressing issue
  • February 6, 2019: Follow up for status and offered dev assistance
  • May 21, 2019: Follow up for status
  • January 21, 2020: Follow up for status and verified issue on most recent release
  • February 5, 2020: Received assistance from HackerOne staff to contact Rails team
  • March 5, 2020: Follow up for status
  • March 6, 2020: HackerOne staff escalated issue internally
  • March 12, 2020: Patch created by Tenderlove and fix was verified
  • March 19, 2020: Official patch released
  • March 24, 2020: Request made to close issue
  • April 8, 2020: Follow up for status
  • April 9, 2020: Received assistance from HackerOne staff
  • May 4, 2020: Follow up for status
  • May 5, 2020: Issue closed and $500 bounty awarded

P.S. Despite the long timeframe for getting this fixed, the hard work of the Rails team and HackerOne staff is still appreciated ✌️

-Jesse

Start learning

Hacking Websites With Cross-Site Scripting

WATCH NOW

Like | Subscribe | Follow

Ready to master XSS?

The Ultimate XSS Training Course gives you the full, uncensored picture of Cross-Site Scripting from the perspectives of criminal hackers and the engineers whose job it is to stop them.

Cross-Site Scripting icon

Cross-Site Scripting

Cross-Site Scripting (XSS) is the #1 most common appsec vulnerability that allows attackers to steal private data, hijack accounts and spread ransomware on your sites. This course teaches students to:

Discover critical XSS vulnerabilities in web applications.

Create, analyze and stop malicious exploits used by criminal hackers.

Fix XSS vulnerabilities in routine and emergency situations.

Stop costly vulnerabilities before they reach production using the latest best practices and techniques.