A recent vulnerability report and the blog post behind it brought my attention back to the escape_javascript Ruby on Rails helper method.

It’s bad form to drop blanket statements without explanation or evidence, so here it is:

Escaping HTML

Part of the danger of escape_javascript is the name and apparent relationship to html_escape.

HTML is a markup language for writing documents. Therefore, it must have a method for representing itself in text. In other words, there must be a way to encode <b> such that the browser displays <b> and does not interpret it as HTML.

As a result, HTML has a well-defined HTML encoding strategy. In the context of security and cross-site scripting, if a value output in an HTML context is HTML escaped, it is safe - the value will not be interpreted as HTML.

(See my post all about escaping!)

Escaping Javascript

On the other hand, JavaScript has no such escaping requirements or capabilities.

Therefore, the “escaping” performed by escape_javascript is limited. The vulnerability report states the method is for “escaping JavaScript string literals”.

In particular, escape_javascript is only useful in one, single context: inside JavaScript strings!

For example:

# ERb Template
<script>
  var x = '<%= escape_javascript some_value %>';
</script>

Use of escape_javascript in any other context is incorrect and dangerous!

This is and always has been dangerous (note the missing quotes):

# ERb Template
<script>
  var x = <%= escape_javascript some_value %>;
</script>

some_value could be a payload like 1; do_something_shady(); // which would result in the following HTML:

<script>
  var x = 1; do_something_shady(); //; 
</script>

The escape_javascript helper does not and cannot make arbitrary values inserted into JavaScript “safe” in the same way html_escape makes values safe for HTML.

CVE-2020-5267

Jesse’s post has more details, but here’s the gist: JavaScript added a new string literal. Instead of just single and double-quotes, now there are also backticks ` which support string interpolation (like Ruby!).

This meant it was simple to bypass escape_javascript and execute arbitrary JavaScript by using a backtick to break out of the string or just #{...} to execute code during interpolation.

For example, if this were our code:

# ERb Template
<script>
  var x = `<%= escape_javascript some_value %>`;
</script>

Then if some_value had a payload of ```; do_something_shady(); //``, the resulting HTML would be:

<script>
  var x = ``; do_something_shady(); //`
</script>

This is because escape_javascript was not aware of backticks for strings.

Dangers of Dynamic Code Generation

As I have talked about before, web applications are essentially poorly-defined compilers generating code with untrusted inputs. In the end, the server is just returning a mishmash of code for the browser to interpret.

However, directly trying to generate safe code in a Turing-complete language like JavaScript or Ruby via string manipulation is a risky game. Methods like escape_javascript make it tempting to do so because the name sounds like it will make the code safe.

If at all possible, avoid dynamic code generation!