Reputation: 5178
How do I escape a collection when I send it via to_json
from the controller action directly client-side?
When I say the request is sent from the controller action and then directly to client-side (skips pre-processing) it looks like this:
app/assets/javascripts/blogs.js
This is as opposed to the request being sent to a controller_action, then to a server-side view for pre-processing, then the results being sent to the requester. Looks like this:
app/views/blogs/index.js.erb
Short Example:
def some_action
@blogs = Blog.all
respond_to do |format|
format.json {render json: @blogs} # currently dangerous, @blogs is not sanitized.
end
end
As an example: assume one of the @blogs
records in that collection has this data input from a hacker:
@blogs.first.title
=> <script>alert('Site now hacked if this javascript runs!')</script>
When the @blogs
get rendered in the browser: I want to escape the @blogs
content so that the javascript will not get triggered from that hacked entry.
Longer Example:
blogger
in a select box. blogs
associated to that selected blogger
. blogs
which will now list as options all the blogs
that belong_to that selected blogger
.For the code: the controller action code above would be exactly the same. Below is the client-side javascript:
app/assets/javascripts/blogs.js
$("body").on('change', '[data-action="blogger_sel"]', function() {
var blogs_selection = $(this).closest('[data-parent-for="blogs_sel"]').find('[data-action="blogs_sel"]');
$.ajax({
url: "/blogs",
type: "GET",
data: {blogger_id: $(this).val()},
success: function (data) {
blogs_selection.children().remove();
$.each(data, function (index, item) {
blogs_selection.append('<option value=' + item.id + '>' + item.title + '</option>');
});
}
})
});
So up above: the part that I am concerned about is value.id
and value.title
. Those are things that could be dangerous if I do not escape them. I want to make sure it is escaped so that any dangerous input will be rendered harmless.
Upvotes: 1
Views: 905
Reputation: 5178
Below is a solution. Keep in mind that it is often times a good idea to sanitize data before it gets persisted into the database as well. Also: it is preferable to sanitize server-side before sending the response to the requester:
app/assets/javascripts/blogs.js
$("body").on('change', '[data-action="blogger_sel"]', function() {
var blog_sel = $(this).closest('[data-parent-for="blog_sel"]').find('[data-action="blog_sel"]');
$.ajax({
url: "/blogs",
type: "GET",
data: {blogger_id: $(this).val()},
success: function (data) {
blog_sel.children().remove();
$.each(data, function (index, item) {
blog_sel.append($('<option>', {
value: item.id,
text : item.title
}));
});
}
})
});
Do not append options the following way because it will execute dangerous hacks:
blogs_selection.append('<option value=' + value.id + '>' + value.title + '</option>');
Upvotes: 1
Reputation: 2750
You're dealing with an unsanitized HTML string like any other. You need to make it safe before inserting it on your page. All you really need to do is replace the <
and >
characters with <
and >
.
Using this controller as an example:
class SomethingController < ApplicationController
def jsontest
render json: { blogs: ["<script>alert(1);</script>"] }
end
end
jQuery can do this for you, if you don't mind the clunkiness:
$.ajax({
type: 'GET',
url: '/something/jsontest',
dataType: 'json',
success: function (data) {
console.log($('<div>').text(data['blogs'][0]).html());
}
})
> <script>alert(1);</script>
You could also look at using ActionView::Helpers::SanitizeHelper#sanitize
on the controller side, which will clobber any HTML tags it finds. (Normally this is only available to views, so you could either make a view to render your JSON or include ActionView::Helpers::SanitizeHelper
in your controller). Or do both!
Upvotes: 0