✅🐴🔋[

CVE-2020-15169

Potential XSS Vulnerability in Action View

If you allow users to control default options of missing translations on affected versions of Rails, it is possible for attackers to execute cross site scripting attacks.

Related Code Affected Versions Patched Versions
ActionView < 6.0.3.3, 5.2.4.4 6.0.3.3, 5.2.4.4

Vulnerability1

The default argument to a translation helper by design, marks content as html_safe if the key has the suffix “_html”.

A vulnerable call to the helper will look something like this:

<p>
  According to my star sign <strong><%= @user.star_sign %></strong>, I am
  <%= t(@user.star_sign, default: @user.star_sign) %>
</p>

Weakness

This vulnerability lets an attacker persist malicious content which can subsequently be executed later as dynamic content.

Attack

The malicious content will look something like this: <script>alert('DEADBEEF');</script>_html

Since this content is marked as HTML safe, the content is not escaped or neutralized. When this content is then later viewed in a browser, various forms of cross-site scripting attacks can be made.

Analysis2

The ActionView::Helpers module has a translate method, which acts as a helper that “delegates to I18n#translate but also performs three additional functions”. It first ensures that any thrown MissingTranslation messages will be rendered as inline spans. It then checks if the key starts with a period so it can scope the key by the current partial. Lastly, the translation will be marked as html_safe if the key has the suffix “_html” or the last element of the key is “html”. This is the weakness that can be compromised.

If the key itself is suffixed with “_html” and the default takes user input, then it can be compromised like this:

<%= t('footer_html', default: "No widget provided for account #{user.email}") %>

Alternatively, if the key itself takes user input, and the default is a fallback as best effort to the original string when there is a missing translation, it can be compromised like this:

<%= t(user.favorite_color, default: user.favorite_color) %>

In both cases, the attacker is exposing the weakness around marking content as html_safe when this method is called by the helper:

def html_safe_translation_key?(key)
  /(\b|_|\.)html$/.match?(key.to_s)
end

This means the following block is executed in the method, with the translation being marked as translation.html_safe

if html_safe_translation_key?(key)
  html_safe_options = options.dup
  options.except(*I18n::RESERVED_KEYS).each do |name, value|
    unless name == :count && value.is_a?(Numeric)
      html_safe_options[name] = ERB::Util.html_escape(value.to_s)
    end
  end
  translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))

  translation.respond_to?(:html_safe) ? translation.html_safe : translation
...

Fix

The best fix for this, is to upgrade Rails to a patched version.

The patch itself, sets a new MISSING_TRANLATION constant on the default options argument (unless it’s blank) and then takes the first argument, the user provided content without marking it as html_safe:

+          html_safe_options[:default] = MISSING_TRANSLATION unless html_safe_options[:default].blank?
+          if translation.equal?(MISSING_TRANSLATION)
+            options[:default].first
+          else
+            translation.respond_to?(:html_safe) ? translation.html_safe : translation
+          end

The vulnerability can also be mitigated by making sure you do not trust user provided content in general. Be careful when you pay that external organization to translate your applications ;)

Research

You can re-create this in a CVE laboratory environment using Docker.

CVE=CVE-2020-15169 RAILS_VERSION=5.2.4.3 make lab

References

Thanks to the CVE author Jonathan Hefner for identifying and patching this issue and also helping me understand the attack vector.