# JSON keys are not properly escaped

Rails to_json escapes hash values but not keys, so a user-controlled key containing </script> breaks out of a javascript_tag and executes XSS.

- Payout: hidden
- Severity: high
- Program: Ruby on Rails
- Found by: einstein_
- Disclosed: 2015-06-16
- CVE: CVE-2015-3226
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/47280
- Library page: https://bugbountylibrary.com/r/h1%3A47280
- Tags: area:web domain:web impact:integrity kind:bounty platform:hackerone sev:high tech:ruby technique:source-review vuln:xss

**Target:** ActiveSupport::JSON.encode / Hash#to_json rendered in views
**Root cause:** The json gem calls .to_s on hash keys, converting ActiveSupport's EscapedString back to a plain String so keys bypass the HTML-entity escaping applied to values.
**Steps:**
1. Observe `{"a"=>"<>"}.to_json` escapes values to `\u003c\u003e` but `{"<>"=>"a"}.to_json` returns `{"<>":"a"}` unescaped.
2. Trace: json gem calls `.to_s` on keys, defeating EscapedString escaping used for values.
3. Render user-controlled hash in a view: `javascript_tag "var json=#{params.to_json}"`.
4. Supply params key `{"</script><script>alert(1)//"=>"xss"}` so the emitted script tag contains `</script><script>alert(1)//` as a key, terminating the tag and executing JS.
**How found:** Compared to_json output for values vs keys, noticed values escaped but keys not, then traced into the json gem's generator calling .to_s on keys, bypassing ActiveSupport's EscapedString escaping.
**Exploitation:** Attacker controls a hash key reflected into an inline script via to_json; the unescaped `</script>` closes the script element and injects a new script executing attacker JS in the victim's browser.
**Insight:** When a serializer escapes values but not keys, test attacker-controlled keys specifically; trace whether a wrapper type (EscapedString) survives into the underlying C/JSON generator or gets coerced via to_s.

## Proof of concept (copied exactly from the original report)

**Hash key not escaped in to_json**

```
irb(main):002:0> {"<>"=>"a"}.to_json
=> "{\"<>\":\"a\"}"
```

**XSS via script tag in JSON key**

```
javascript_tag "var json=#{params.to_json}"
When params is something like `{"</script><script>alert(1)//"=>"xss"}` then `<>` are not escaped as they should and the javascript tag looks like this:
<script>
//<![CDATA[
var json={"</script><script>alert(1)//":"xss"}
```

