The go-to resource for upgrading Ruby, Rails, and your dependencies.

Understanding and Fixing CVE-2007-3227: The ActiveRecord to_json XSS Vulnerability


When we pack physical goods for transit, we rely on the container to keep those items isolated from their environment. If a package opens during shipment and its contents interact with other cargo, the container has failed its core purpose. The JSON serialization mechanism in web applications serves a comparable role: we pack data structures so they can be transported and interpreted safely on the other side. When the mechanism packing that data fails to neutralize dangerous contents, we expose our systems to vulnerabilities like CVE-2007-3227.

CVE-2007-3227 was a moderate severity Cross-Site Scripting (XSS) vulnerability affecting Ruby on Rails versions prior to 1.2.4 (and edge revisions prior to 9606). It demonstrated a critical flaw in how ActiveRecord::Base#to_json handled untrusted user input during serialization. While this vulnerability was patched in October 2007, the underlying mechanism provides valuable lessons for modern web development.

Before we get into that, though, we need to understand how JSON intersects with HTML in a browser context.

How JSON Enables Cross-Site Scripting

Strictly speaking, JSON is a data representation format, not an executable language. A browser parsing a JSON payload via an API endpoint will not execute the strings contained within it. This, of course, raises the question of exactly how an XSS vulnerability exists within a JSON serialization method. The answer lies in how that serialized data is consumed by the application layer.

Often, Rails applications pass serialized data directly into the view layer. A common pattern in earlier iterations of Rails involved rendering JSON directly into an inline JavaScript block to bootstrap frontend state.

For example, if you had a user record, you might pass it to the frontend like this:

<script>
  var currentUser = <%= @user.to_json %>;
</script>

If the @user object contains safe data, this works as intended. However, if a user’s name or bio attribute contains malicious input, and the to_json method outputs that field without properly escaping it for a web context, the browser will interpret the resulting string as executable HTML.

The Mechanics of CVE-2007-3227

In vulnerable versions of Rails, to_json performed straightforward serialization without considering the hostile environment of a web browser. Let’s look at a conceptual example to see what happens when we serialize a record containing a malicious payload.

user = User.new(name: "</script><script>alert('XSS')</script>")

# In vulnerable versions of Rails:
puts user.to_json
# => {"name": "</script><script>alert('XSS')</script>"}

When this exact output is embedded into the inline script block we saw earlier, the resulting HTML document looks like this:

<script>
  var currentUser = {"name": "</script><script>alert('XSS')</script>"};
</script>

The browser parses the HTML document linearly. When it encounters the </script> tag inside the JSON string, it does not understand that it is part of a string literal. It interprets the tag as the end of the script block. It then immediately executes the next <script> block it sees, firing the injected alert('XSS') payload. The remaining "};</script> is parsed as invalid JavaScript or stray HTML.

Mitigating the Vulnerability

To resolve this issue, the Rails core team updated the JSON serialization mechanism to ensure that dangerous characters were properly escaped when converting strings to JSON format. The solution involved converting characters like angular brackets (< and >) and ampersands (&) into their unicode escape sequences (\u003c, \u003e, \u0026).

Let’s see if we can replicate the safe behavior using modern Rails:

user = User.new(name: "</script><script>alert('XSS')</script>")

puts user.to_json
# => {"name":"\u003c/script\u003e\u003cscript\u003ealert('XSS')\u003c/script\u003e"}

When the browser encounters \u003c/script\u003e within a JavaScript string, it safely parses it as a string containing literal angle brackets, rather than treating it as a closing HTML tag. This ensures the data remains data, preserving the integrity of the container.

Long-Term Implications

While CVE-2007-3227 is a legacy vulnerability, the pattern of injecting malicious payloads through unescaped data serialization remains relevant. Modern Rails applications typically rely on safer patterns, such as fetching JSON through separate API requests or using the json_escape helper method (often aliased as j) to safely inject JSON into data attributes.

For example, passing data via an HTML data attribute is generally considered a safer approach:

<!-- A modern, safer approach -->
<div id="app-data" data-user="<%= h(@user.to_json) %>"></div>

By ensuring that the application escapes output according to the context it will be rendered in, we prevent the serialization layer from inadvertently becoming an attack vector.

Sponsored by Durable Programming

Need help maintaining or upgrading your Ruby on Rails application? Durable Programming specializes in keeping Rails apps secure, performant, and up-to-date.

Hire Durable Programming