Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/wilsonibekason/headset-shopme
An advanced ecommerce website
https://github.com/wilsonibekason/headset-shopme
context-api jsx-syntax nextjs reactjs sanity-io schema scss-framework
Last synced: about 1 month ago
JSON representation
An advanced ecommerce website
- Host: GitHub
- URL: https://github.com/wilsonibekason/headset-shopme
- Owner: wilsonibekason
- Created: 2022-04-27T17:01:23.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2023-02-22T12:24:20.000Z (over 1 year ago)
- Last Synced: 2024-10-13T02:04:34.906Z (about 1 month ago)
- Topics: context-api, jsx-syntax, nextjs, reactjs, sanity-io, schema, scss-framework
- Language: JavaScript
- Homepage:
- Size: 37.1 MB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
Preventing XSS attacks when embedding JSON in HTML
2012-08-03T00:00:00-07:00
http://sophiebits.com/2012/08/03/preventing-xss-jsonAt Khan Academy, we recently took the time to go through our 200+ Jinja2 templates and turn on autoescape to reduce the likelihood of falling prey to an XSS attack. This gave us an excuse to audit all of our pages for injection holes: Here’s one hole Jamie Wong pointed out that you might run into when using Ruby’s
.to_json
or Python’sjson.dumps
.Suppose you’re writing a web app and you want to pass down an untrusted string
username
from the server to your client-side JavaScript code. If you create a Rails template that looks like the following, are you safe from XSS attacks?<script> Profile.init({ username: <%=raw username.to_json %> }); </script>
(Here we use
raw
because we don’t want HTML entities in the JavaScript code.) Though we’re not exactly including unescaped HTML, there’s a subtle injection bug here that has to do with how browsers parse<script>
tags.The HTML spec says:
Markup and entities must be treated as raw text and passed to the application as is. The first occurrence of the character sequence “</” (end-tag open delimiter) is treated as terminating the end of the element’s content.
Where’s the security hole? Consider if
username
was set to:</script><script>evil()</script>
This will give us the following HTML:
<script> Profile.init({ username: "</script><script>evil()</script>" }); </script>
Though the first
<script>
tag doesn’t contain valid JavaScript, it doesn’t matter – the second script tag will be read and soevil()
will be executed.So what’s the fix? In addition to the common character escapes
\"
,\\
,\b
,\f
,\n
,\r
,\t
, and\uXXXX
, the JSON spec states that\/
will be interpreted as a literal slash. That is, in a JSON string literal, you can add a backslash before a slash character without otherwise changing the string. To prevent against this hole, you should replace every occurrence of</
in your JSON with<\/
so that the<script>
tag remains open. (The characters<
and/
are valid only within a string literal so the replacement can’t affect anything else.)Regardless of which language you use, you’ll probably want to make a helper function to encapsulate this logic:
# Ruby def jsonify(obj) obj.to_json.gsub('</', '<\/') end
# Python def jsonify(obj): return json.dumps(obj).replace('</', '<\\/')
As long as you always remember to use the
jsonify
wrapper instead of the built-in JSON serialization, you should be safe from this particular attack.
Update (2018/03/10): Replacing
</
with<\/
works for JSON but isn’t necessarily correct when run over arbitrary JavaScript. Imagine the (nonsensical but valid) codelet x = foo < /bar/;
. When minified, you’ll end up with</
adjacent but since the/
starts a regex, it isn’t correct to add a backslash.Instead, Erling Ellingsen and I concluded that escaping the “s” in script is a safe, general solution, since the same escaping (
\u0073
) is valid in all the places a letter can appear: strings, regexes, and JS identifiers. The following should work:# Ruby def scriptify(code) escaped = code.gsub(/(?<=<\/)s(?=cript)/i) { |m| "\\u%04x" % m.ord } "<script>#{escaped}</script>" end
# Python def scriptify(code): escaped = re.sub( r'(?<=</)s(?=cript)', lambda m: f'\\u{ord(m.group(0)):04x}', code, flags=re.I) return f'<script>{escaped}</script>'