How to safely embed JSON with </script> in HTML document?
In a Rails 3.1 app, how can I safely embed some JSON data into an HTML document?
Suppose I have this in a controller action:
@tags = [
{name:"tag1", color:"green"},
{name:"</script><开发者_StackOverflow社区;b>I can do something bad here</b>", color:"red"}
]
And this in a corresponding view:
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var tags_list = <%= @tags.to_json %>;
// ]]>
</script>
Then I get this in resulting HTML:
var tags_list = [
{"name":"tag1","color":"green"},
{"name":"</script><b>I can do something bad here</b>","color":"red"}
];
which triggers a SyntaxError: Unexpected token &
in Chrome
If I remove the Rails' default HTML escaping with <%=raw tags.to_json
%>
, then it returns this:
var tags_list = [
{"name":"tag1","color":"green"},
{"name":"</script><b>I can do something bad here</b>","color":"red"}
];
which, of course, breaks the HTML document with </script>
.
Can I somehow tell to_json() method to return something more like this:
var tags_list = [
{"name":"tag1","color":"green"},
{"name":"</script><b>I can do something bad here</b>","color":"red"}
];
I asked this question on rubyonrails-talk mailing list, and I understand now that some people think that's a very bad idea to begin with, but in my case it works very nicely, as long as there are no HTML special chars in the data. So I just want to make the string returned by to_json
HTML safe and still have JavaScript parse it properly.
UPDATE: Based on @coreyward comment, I did make it a JS string literal, and that seems to be working great now. Its not quite as elegant of a solution as I was hoping for, but its not too bad either. Here is the code that is working for me:
<% tags = [{name:"tag1", color:"green"}, {name:"</script><b>I can \n\ndo something bad here</b>", color:"red"}] %>
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var tags_list = $.parseJSON('<%=j tags.to_json.html_safe %>');
// ]]>
</script>
which results in:
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var tags_list = $.parseJSON('[{\"name\":\"tag1\",\"color\":\"green\"},{\"name\":\"<\/script><b>I can \\n\\ndo something bad here<\/b>\",\"color\":\"red\"}]');
// ]]>
</script>
Your code using just @tags.to_json
works in rails3, if you enable it with:
ActiveSupport.escape_html_entities_in_json = true
Otherwise, your other option is this:
var tags_list = <%= raw @tags.to_json.gsub("</", "<\\/") %>;
This saves the client having to parse the whole thing through $
The proper way in 2019 is to wrap obj.to_json
with json_escape
function. json_escape
is directly intended for escaping specific HTML symbols inside JSON strings. Example below from the documentation:
json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
# => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
json_escape(json)
# => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
JSON.parse(json) == JSON.parse(json_escape(json))
# => true
It seems this page appears on top of Google Search results, that's why I decided to provide a comment with an update :)
btw, this works but is not a good solution in my opinion:
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var tags_list = <%=raw @tags.to_json.gsub('/', '\/') %>;
// ]]>
</script>
I think that if you try this it will work:
var tags_list = "<%== @tags.to_json.gsub('/', '\/') %>";
(Notice the double == and the " ")
For instance with this in app/layouts/application.html.slim
:
javascript:
window.translations = #{raw t("js").to_json};
And this in the translations:
js:
name:
must_be_present: Must be present<script>alert(1)</script>
The result will be escaped:
<script>window.translations = {"name":{"must_be_present":"Must be present\u003cscript\u003ealert(1)\u003c/script\u003e"}};</script>
精彩评论